From 5dcefff28dd639d394fb1d4605f9c37b3babae94 Mon Sep 17 00:00:00 2001 From: acrojason Date: Thu, 4 Jun 2020 08:29:01 -0700 Subject: [PATCH 001/382] Updated Warehouse to accommodate Naval transport --- .../Moose/Functional/Warehouse.lua | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 06b8c67c4..1a2fce39e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4342,7 +4342,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!") + -- Spawn Ship in port zone + spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone) return elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -4471,6 +4472,9 @@ 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 + BASE:T("Big 'ol board radius") + _boardradius=5000 end -- Empty cargo group set. @@ -4535,6 +4539,22 @@ 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) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.portzone) + + -- 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 @@ -6928,8 +6948,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 +8076,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") From fdcdf52d9a45a65e71ae82e1cae743dca489d831 Mon Sep 17 00:00:00 2001 From: acrojason Date: Thu, 4 Jun 2020 08:46:30 -0700 Subject: [PATCH 002/382] Initial creation of AI_Cargo_Ship --- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 265 +++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 Moose Development/Moose/AI/AI_Cargo_Ship.lua 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..5b7a4c9b4 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -0,0 +1,265 @@ +--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo. + +-- @field #AI_CARGO_SHIP +AI_CARGO_SHIP = { + ClassName = "AI_CARGO_SHIP", + Coordinate = nil -- Core.Point#COORDINATE +} + +--- Creates a new AI_CARGO_SHIP object. +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( CombatRadius ) + 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 AICargoTroopsIs( "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 + 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 + +function AI_CARGO_SHIP:onafterFollow( Ship, From, Event, To ) + self:F( { Ship, From, Event, To } ) + + self:F( "Follow" ) + if Ship and Ship:IsAlive() then + for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + if Cargo:IsUnLoaded() then + self:FollowToCarrier( self, ShipUnit, Cargo ) + ShipUnit:RouteResume() + end + end + end + +end + +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 + + +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 + +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 = {} + + for i=1, #lane do + local coord = lane[i] + local Waypoint = coord:WaypointGround(_speed) + table.insert(Waypoints, Waypoint) + end + + --local TaskFunction = self:_SimpleTaskFunction( "warehouse:_Arrived", Ship ) + + local Waypoint = Waypoints[#Waypoints] + --Ship:SetTaskWaypoint( Waypoint, TaskFunction ) + + Ship:Route(Waypoints, 1) + + else + BASE:T("ERROR: No shipping lane defined for Naval Transport!") + end + end +end From 18fd9cdc3d5e1520700016d2495c31a609da5c4b Mon Sep 17 00:00:00 2001 From: acrojason Date: Thu, 4 Jun 2020 21:09:35 -0700 Subject: [PATCH 003/382] Initial AI_Cargo_Dispatcher_Ship --- .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua 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..ed4984381 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -0,0 +1,38 @@ +--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships + +-- @field #AI_CARGO_DISPATCHER_SHIP +AI_CARGO_DISPATCHER_SHIP = { + ClassName = "AI_CARGO_DISPATCHER_SHIP" + } + + 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, 3000 ) + self:SetDeployRadius( 500, 3000 ) + + 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 From 4b8b13dd685df956ec32beeade9a932f8ee9883b Mon Sep 17 00:00:00 2001 From: acrojason Date: Wed, 23 Sep 2020 09:40:42 -0700 Subject: [PATCH 004/382] Initial changes to support Naval Cargo --- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 183 ++++++++++-------- .../Moose/Functional/Warehouse.lua | 78 ++++++-- 2 files changed, 161 insertions(+), 100 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua index 5b7a4c9b4..7f3f6f5aa 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -40,7 +40,7 @@ function AI_CARGO_SHIP:SetCarrier( CargoCarrier ) self:F({AICargoTroops=AICargoTroops}) if AICargoTroops then self:F({}) - if not AICargoTroopsIs( "Loaded" ) then + if not AICargoTroops:Is( "Loaded" ) then -- Better hope they can swim! AICargoTroops:Destroyed() end @@ -92,6 +92,7 @@ function AI_CARGO_SHIP:SetCombatRadius( CombatRadius ) return self end + --- Follow Infantry to the Carrier -- @param #AI_CARGO_SHIP self -- @param #AI_CARGO_SHIP Me @@ -99,6 +100,7 @@ end -- @param Cargo.CargoGroup#CARGO_GROUP Cargo -- @return #AI_CARGO_SHIP function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) + BASE:T("DEBUGGING*** AI_CARGO_SHIP:FollowToCarrier") local InfantryGroup = CargoGroup:GetGroup() self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) @@ -107,35 +109,35 @@ function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) -- 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() + -- 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 = {} + if InfantryGroup:IsAlive() then - -- Calculate new route - local FromCoord = InfantryGroup:GetCoordinate() - local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) - self:F({FromGround=FromGround}) - table.insert( Waypoints, FromGround ) + self:F( { InfantryGroup = InfantryGroup:GetName() } ) + local Waypoints = {} - local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) - local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) - self:F({ToGround=ToGround}) - table.insert( Waypoints, ToGround ) + -- Calculate new route + local FromCoord = InfantryGroup:GetCoordinate() + local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) + self:F({FromGround=FromGround}) + table.insert( Waypoints, FromGround ) - local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup ) + local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) + local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) + self:F({ToGround=ToGround}) + table.insert( Waypoints, ToGround ) - self:F({Waypoints=Waypoints}) - local Waypoint = Waypoints[#Waypoints] - InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone + local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup ) - InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details - end + 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 @@ -146,76 +148,57 @@ function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To ) if self.CombatRadius > 0 then 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 + 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 end end - end - end - end - + self.CarrierCoordinate = Ship:GetCoordinate() end - self.CarrierCoordinate = Ship:GetCoordinate() - end - self:__Monitor( -5 ) end - end -function AI_CARGO_SHIP:onafterFollow( Ship, From, Event, To ) - self:F( { Ship, From, Event, To } ) - - self:F( "Follow" ) - if Ship and Ship:IsAlive() then - for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsUnLoaded() then - self:FollowToCarrier( self, ShipUnit, Cargo ) - ShipUnit:RouteResume() - end - end - end - -end function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) - Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } ) if Ship:IsAlive() then @@ -233,6 +216,39 @@ function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone ) end end +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 + +function AI_CARGO_SHIP:onafterPickedUp( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) + + 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 Waypoint = Waypoints[#Waypoints] + Ship:Route(Waypoints, 1) + + else + BASE:T("ERROR: No shipping lane defined for Naval Transport!") + end + end +end + + function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone ) if Ship and Ship:IsAlive() then @@ -251,15 +267,12 @@ function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, He table.insert(Waypoints, Waypoint) end - --local TaskFunction = self:_SimpleTaskFunction( "warehouse:_Arrived", Ship ) - local Waypoint = Waypoints[#Waypoints] - --Ship:SetTaskWaypoint( Waypoint, TaskFunction ) Ship:Route(Waypoints, 1) else - BASE:T("ERROR: No shipping lane defined for Naval Transport!") + BASE:T("ERROR: No shipping lane defined for Naval Transport!") end end end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1a2fce39e..d61c33984 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, @@ -2709,6 +2710,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 @@ -4344,7 +4357,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn Ship in port zone spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone) - return elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -4473,8 +4485,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet elseif Request.transporttype==WAREHOUSE.TransportType.APC then --_boardradius=nil elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then - BASE:T("Big 'ol board radius") - _boardradius=5000 + _boardradius=6000 end -- Empty cargo group set. @@ -4485,7 +4496,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Find asset belonging to this group. local asset=self:FindAssetInDB(_group) - + BASE:T("DEBUGGING*** load radius: "..asset.loadradius) -- New cargo group object. local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius) @@ -4494,6 +4505,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Add group to group set. CargoGroups:AddCargo(cargogroup) + end ------------------------ @@ -4543,6 +4555,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Pickup and deploy zones. local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) + PickupZoneSet:AddZone(self.harborzone) local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.portzone) -- Get the shipping lane to use and pass it to the Dispatcher @@ -4561,17 +4574,43 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- 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 + --BASE:T("DEBUGGING*** Let's try to move these units") + --[[for _,_group in pairs(CargoGroupSet:GetSetObjects()) do + local group=GROUP:FindByName( _group:GetName() ) --Wrapper.Group#GROUP + + + --local _speed = group:GetSpeedMax()*0.7 + BASE:T("DEBUGGING*** Group ".._.." coordinate is "..CargoTransport:GetCoordinate()) + --local FromCoord = group:GetCoordinate() + local ToCoord = CargoTransport:GetCoordinate() + + local FromWP = FromCoord:WaypointGround() + local ToWP = ToCoord:WaypointGround( 15, "Vee" ) + + group:Route( { FromWP, ToWP }, 10 ) + end]]-- + 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) @@ -4600,6 +4639,15 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Dispatcher Event Functions -- -------------------------------- + --- Function called before carrier loads something + function CargoTransport:OnBeforeMonitor(From, Event, To, Carrier, Cargo, PickupZone) + -- Need to get the cargo over to the portzone + -- But what if the cargo can't move on it's own? + BASE:T("DEBUGGING*** CargoTransport:OnBeforeMonitor") + + end + + --- Function called after carrier picked up something. function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone) @@ -4650,7 +4698,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. From 7a55ba105c2716621c2b2efc1ff6fd89af13c9e2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 20 Nov 2020 10:34:53 +0100 Subject: [PATCH 005/382] Update AI_A2A_Dispatcher.lua Wrap player messages, added function to switch on/off --- .../Moose/AI/AI_A2A_Dispatcher.lua | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 05ef48758..7f13ec21f 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -929,6 +929,8 @@ do -- AI_A2A_DISPATCHER self.DefenderTasks = {} -- The Defenders Tasks. self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. + self.SetSendPlayerMessages = false --#boolean Flash messages to player + -- TODO: Check detection through radar. self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) --self.Detection:InitDetectRadar( true ) @@ -949,7 +951,6 @@ do -- AI_A2A_DISPATCHER self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron. - self:AddTransition( "Started", "Assign", "Started" ) --- OnAfter Transition Handler for Event Assign. @@ -2356,6 +2357,12 @@ do -- AI_A2A_DISPATCHER return self end + --- Set flashing player messages on or off + -- @param #AI_A2G_DISPATCHER self + -- @param #boolean onoff Set messages on (true) or off (false) + function AI_A2A_DISPATCHER:SetSendMessages( onoff ) + self.SetSendPlayerMessages = onoff + end --- Sets flights to take-off in the air, as part of the defense system. -- @param #AI_A2A_DISPATCHER self @@ -3240,7 +3247,9 @@ do -- AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) + end AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling end end @@ -3252,10 +3261,10 @@ do -- AI_A2A_DISPATCHER self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) + if Squadron and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3271,7 +3280,7 @@ do -- AI_A2A_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron then + if Squadron and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3459,10 +3468,10 @@ do -- AI_A2A_DISPATCHER local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) if DefenderTarget then - if Squadron.Language == "EN" then + if Squadron.Language == "EN" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup ) + elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеÑ�а вверх.", DefenderGroup ) end --Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit @@ -3480,11 +3489,11 @@ do -- AI_A2A_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if Squadron.Language == "EN" then + if Squadron.Language == "EN" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "DE" then + elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват Ñ�амолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) end end @@ -3502,10 +3511,10 @@ do -- AI_A2A_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - if Squadron.Language == "EN" then + if Squadron.Language == "EN" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие Ñ�амолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) end end self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit ) @@ -3520,10 +3529,10 @@ do -- AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - if Squadron.Language == "EN" then + if Squadron.Language == "EN" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup ) + elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаÑ�Ñ�ÑŒ на базу.", DefenderGroup ) end end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3551,10 +3560,10 @@ do -- AI_A2A_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron.Language == "EN" then + if Squadron.Language == "EN" and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) - elseif Squadron.Language == "RU" then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup ) + elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие Ñ�амолеты в поÑ�адка на базу.", DefenderGroup ) end if Action and Action == "Destroy" then @@ -4523,5 +4532,5 @@ do return self end - + end From 824a98d7a843b8aa79e9d69327f53d0e84ad35cf Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 20 Nov 2020 10:43:39 +0100 Subject: [PATCH 006/382] Update AI_A2G_Dispatcher.lua Wrapper for player messages and function to set this on/off --- .../Moose/AI/AI_A2G_Dispatcher.lua | 79 ++++++++++++------- 1 file changed, 51 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 91d990afc..9fdb76834 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -999,7 +999,9 @@ do -- AI_A2G_DISPATCHER -- self.Detection:InitDetectRadar( false ) -- self.Detection:InitDetectVisual( true ) -- self.Detection:SetRefreshTimeInterval( 30 ) - + + self.SetSendPlayerMessages = false --flash messages to players + self:SetDefenseRadius() self:SetDefenseLimit( nil ) self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random ) @@ -3699,7 +3701,9 @@ do -- AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) + end AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -3711,7 +3715,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron then + if Squadron and self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) end @@ -3730,8 +3734,9 @@ do -- AI_A2G_DISPATCHER if Squadron and AttackSetUnit:Count() > 0 then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + end end end @@ -3745,8 +3750,9 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + end end end @@ -3757,8 +3763,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) + end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3770,7 +3777,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." ) + end if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3785,8 +3794,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) + end if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3842,7 +3852,9 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) + end AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3857,8 +3869,9 @@ do -- AI_A2G_DISPATCHER if Squadron then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + end end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3873,8 +3886,9 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + end end end @@ -3884,8 +3898,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) - + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) + end self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3899,8 +3914,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - --Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + end if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3915,8 +3931,9 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) - + if self.SetSendPlayerMessages then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) + end if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3956,7 +3973,7 @@ do -- AI_A2G_DISPATCHER local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) -- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates. - -- (y1 – y2)x + (x2 – x1)y + (x1y2 – x2y1) = 0 + -- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0 local c1 = DefenseCoordinate local c2 = AttackCoordinate @@ -4017,7 +4034,7 @@ do -- AI_A2G_DISPATCHER for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do -- Here we check if the defenders have a defense line to the attackers. - -- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage. + -- If the attackers are behind enemy lines or too close to an other defense line; then don't engage. local DefenseCoordinate = DefenderGroup:GetCoordinate() local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem ) @@ -4320,7 +4337,7 @@ do -- AI_A2G_DISPATCHER -- Show tactical situation local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) + Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then @@ -4537,7 +4554,7 @@ do -- AI_A2G_DISPATCHER if self.TacticalDisplay then -- Show tactical situation local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) + Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then @@ -4706,6 +4723,12 @@ do local PatrolTaskType = PatrolTaskTypes[math.random(1,3)] self:Patrol( SquadronName, PatrolTaskType ) end - + + --- Set flashing player messages on or off + -- @param #AI_A2G_DISPATCHER self + -- @param #boolean onoff Set messages on (true) or off (false) + function AI_A2G_DISPATCHER:SetSendMessages( onoff ) + self.SetSendPlayerMessages = onoff + end end From 9573d92cc043083c12555511ce25d9019c250818 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 Nov 2020 12:07:49 +0100 Subject: [PATCH 007/382] Update Controllable.lua Add option to control a2a attack ranges for AIR fighter units. See https://wiki.hoggitworld.com/view/DCS_option_missileAttack --- .../Moose/Wrapper/Controllable.lua | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 7332271ab..8705f9fbe 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -173,6 +173,9 @@ -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- +-- ## 5.5) Air-2-Air missile attack range: +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -3659,3 +3662,23 @@ function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) end end + +--- Sets Controllable Option for A2A attack range for AIR FIGHTER units. +-- @param #CONTROLLABLE self +-- @param #number Defines the range: MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack +function CONTROLLABLE:OptionAAAttackRange(range) + self:F2( { self.ControllableName } ) + -- defaults to 3 + local range = range or 3 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsAir() then + self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) + end + end + return self + end + return nil +end From 3e0db056649b58f9c411457fb304bf82652593e6 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 Nov 2020 13:01:26 +0100 Subject: [PATCH 008/382] Update Controllable.lua Added control to keep within limits of the options --- Moose Development/Moose/Wrapper/Controllable.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 8705f9fbe..1d31fe206 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3669,7 +3669,10 @@ end function CONTROLLABLE:OptionAAAttackRange(range) self:F2( { self.ControllableName } ) -- defaults to 3 - local range = range or 3 + local range = range or 3 + if range < 0 or range > 4 then + range = 3 + end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() From 309e1208c2ba4804ab13cacc234c987a11c3e39d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 Nov 2020 14:17:42 +0100 Subject: [PATCH 009/382] Update Sead.lua Feature Request #1355 --- Moose Development/Moose/Functional/Sead.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 6edb99312..3dcf432de 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -44,7 +44,8 @@ SEAD = { High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } }, - SEADGroupPrefixes = {} + SEADGroupPrefixes = {}, + EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. @@ -74,6 +75,20 @@ function SEAD:New( SEADGroupPrefixes ) return self end +--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355 +-- @param #SEAD self +-- @param #number range Gives the engagement range in percent, e.g. 50 +-- @return self +function SEAD:SetEngagementRange(range) + self:F( { range } ) + range = range or 75 + if range < 0 or range > 100 then + range = 75 + end + self.EngagementRange = range + return self +end + --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD @@ -174,7 +189,10 @@ function SEAD:OnEventShot( EventData ) local SuppressedGroups = {} local function SuppressionEnd(id) + local range = self.EngagementRange -- Feature Request #1355 + --env.info(string.format("*** SEAD - Engagement Range is %d", range)) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 SuppressedGroups[id.groupName] = nil end From e5c57269bb3906df239cd4e38ac1aa0549efded5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 Nov 2020 17:24:56 +0100 Subject: [PATCH 010/382] Update Controllable.lua Added option OptionAAAttackRange and OptionEngageRange --- .../Moose/Wrapper/Controllable.lua | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 7332271ab..ded28c6bd 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -173,6 +173,12 @@ -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- +-- ## 5.5) Air-2-Air missile attack range: +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. +-- +-- ## 5.6) GROUND units attack range: +-- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically. +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -3659,3 +3665,50 @@ function CONTROLLABLE:OptionRestrictBurner(RestrictBurner) end end + +--- Sets Controllable Option for A2A attack range for AIR FIGHTER units. +-- @param #CONTROLLABLE self +-- @param #number Defines the range: MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack +function CONTROLLABLE:OptionAAAttackRange(range) + self:F2( { self.ControllableName } ) + -- defaults to 3 + local range = range or 3 + if range < 0 or range > 4 then + range = 3 + end + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsAir() then + self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) + end + end + return self + end + return nil +end + +--- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically. +-- @param #CONTROLLABLE self +-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionEngageRange(EngageRange) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + EngageRange=EngageRange or 100 + if EngageRange < 0 or EngageRange > 100 then + EngageRange = 100 + end + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) + end + end + return self + end + return nil +end From f034e3680c24d38c5eeee449d6409eb073d8f79f Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 22 Nov 2020 17:39:56 +0100 Subject: [PATCH 011/382] Update Controllable.lua --- Moose Development/Moose/Wrapper/Controllable.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index ded28c6bd..6282d409c 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3668,7 +3668,8 @@ end --- Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self --- @param #number Defines the range: MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack +-- @param #number range Defines the range +-- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack function CONTROLLABLE:OptionAAAttackRange(range) self:F2( { self.ControllableName } ) -- defaults to 3 From aab2f20280c47da23aed42861d4d429dc1de0008 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 23 Nov 2020 12:32:00 +0100 Subject: [PATCH 012/382] Update Sead.lua Feature Request #1355 Added a couple of new missiles, SD-10 and AGM84 Cleaned up the logic --- Moose Development/Moose/Functional/Sead.lua | 125 ++++++++++---------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 6edb99312..62f71e137 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -39,12 +39,14 @@ SEAD = { ClassName = "SEAD", TargetSkill = { - Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } , - Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } , - High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } , - Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } } + Average = { Evade = 30, DelayOn = { 40, 60 } } , + Good = { Evade = 20, DelayOn = { 30, 50 } } , + High = { Evade = 15, DelayOn = { 20, 40 } } , + Excellent = { Evade = 10, DelayOn = { 10, 30 } } }, - SEADGroupPrefixes = {} + SEADGroupPrefixes = {}, + SuppressedGroups = {}, + EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. @@ -70,25 +72,41 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot ) - + self:I("*** SEAD - Started Version 0.2.0") return self end +--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355 +-- @param #SEAD self +-- @param #number range Set the engagement range in percent, e.g. 50 +-- @return self +function SEAD:SetEngagementRange(range) + self:F( { range } ) + range = range or 75 + if range < 0 or range > 100 then + range = 75 + end + self.EngagementRange = range + self:T(string.format("*** SEAD - Engagement range set to %s",range)) + return self +end + --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData function SEAD:OnEventShot( EventData ) - self:F( { EventData } ) + self:T( { EventData } ) local SEADUnit = EventData.IniDCSUnit local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeapon = EventData.Weapon -- Identify the weapon fired local SEADWeaponName = EventData.WeaponName -- return weapon type - -- Start of the 2nd loop - self:T( "Missile Launched = " .. SEADWeaponName ) + + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T({ SEADWeapon }) - --if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD + --check for SEAD missiles if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired @@ -107,27 +125,35 @@ function SEAD:OnEventShot( EventData ) or SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired or + SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired + or SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired then local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimgroupName = _targetMimgroup:getName() - local _targetMimcont= _targetMimgroup:getController() + local _targetMimname = Unit.getName(_targetMim) -- Unit name + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted grouo + local _targetMimgroupName = _targetMimgroup:getName() -- group name local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill self:T( self.SEADGroupPrefixes ) self:T( _targetMimgroupName ) + -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then SEADGroupFound = true - self:T( 'Group Found' ) + self:T( '*** SEAD - Group Found' ) break end end - if SEADGroupFound == true then + if SEADGroupFound == true then -- yes we are being attacked if _targetskill == "Random" then -- when skill is random, choose a skill local Skills = { "Average", "Good", "High", "Excellent" } _targetskill = Skills[ math.random(1,4) ] @@ -136,63 +162,36 @@ function SEAD:OnEventShot( EventData ) if self.TargetSkill[_targetskill] then if (_evade > self.TargetSkill[_targetskill].Evade) then - self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) - - local _targetMim = Weapon.getTarget(SEADWeapon) - local _targetMimname = Unit.getName(_targetMim) + self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimcont= _targetMimgroup:getController() routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - local SuppressedGroups1 = {} -- unit suppressed radar off for a random time - - local function SuppressionEnd1(id) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - SuppressedGroups1[id.groupName] = nil - end - - local id = { + --tracker ID table to switch groups off and on again + local id = { groupName = _targetMimgroup, ctrl = _targetMimcont } - - local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) - - if SuppressedGroups1[id.groupName] == nil then - - SuppressedGroups1[id.groupName] = { - SuppressionEndTime1 = timer.getTime() + delay1, - SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } - - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) - end - - local SuppressedGroups = {} - - local function SuppressionEnd(id) + + local function SuppressionEnd(id) --switch group back on + local range = self.EngagementRange -- Feature Request #1355 + self:T(string.format("*** SEAD - Engagement Range is %d", range)) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - SuppressedGroups[id.groupName] = nil + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done end - - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - + -- randomize switch-on time local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - - if SuppressedGroups[id.groupName] == nil then - SuppressedGroups[id.groupName] = { - SuppressionEndTime = timer.getTime() + delay, - SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function - } - - timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) + local SuppressionEndTime = timer.getTime() + delay + --create entry + if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet + self.SuppressedGroups[id.groupName] = { + SuppressionEndTime = delay + } + Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function end end end From 5842562ce15dfea6fea11e75ea43ff17afa32540 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Nov 2020 16:23:22 +0100 Subject: [PATCH 013/382] Update AI_Air.lua Resolves AI reeping low back to base, and possible also issue #1305 --- Moose Development/Moose/AI/AI_Air.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 213e58757..413baa5c8 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -595,19 +595,24 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Calculate the target route point. local FromCoord = AIGroup:GetCoordinate() - local ToTargetCoord = self.HomeAirbase:GetCoordinate() - - if not self.RTBMinSpeed and not self.RTBMaxSpeed then + local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!) + local ToTargetVec3 = ToTargetCoord:GetVec3() + ToTargetVec3.y = ToTargetCoord:GetLandHeight()+1000 -- let's set this 1000m/3000 feet above ground + local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 ) + + if not self.RTBMinSpeed or not self.RTBMaxSpeed then local RTBSpeedMax = AIGroup:GetSpeedMax() - self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 ) + self:SetRTBSpeed( RTBSpeedMax * 0.2, RTBSpeedMax * 0.5 ) end local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) - local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) ) + --local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord2 ) ) - local Distance = FromCoord:Get2DDistance( ToTargetCoord ) + local Distance = FromCoord:Get2DDistance( ToTargetCoord2 ) - local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) + --local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) + local ToAirbaseCoord = ToTargetCoord2 + if Distance < 5000 then self:I( "RTB and near the airbase!" ) self:Home() From ff4927dbb77d256dabf661c31c096bf33555ec3e Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 25 Nov 2020 12:38:31 +0100 Subject: [PATCH 014/382] Update AI_Air.lua --- Moose Development/Moose/AI/AI_Air.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 413baa5c8..6c8c9579c 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -602,7 +602,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) if not self.RTBMinSpeed or not self.RTBMaxSpeed then local RTBSpeedMax = AIGroup:GetSpeedMax() - self:SetRTBSpeed( RTBSpeedMax * 0.2, RTBSpeedMax * 0.5 ) + self:SetRTBSpeed( RTBSpeedMax * 0.5, RTBSpeedMax * 0.6 ) end local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) From c486167b01cd05a26b65b37820a827dc9f7b1501 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 26 Nov 2020 10:28:51 +0100 Subject: [PATCH 015/382] Update Task_A2G_Dispatcher.lua Option to suppress flashing messages --- .../Moose/Tasking/Task_A2G_Dispatcher.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index b7ae6ba17..448d7f545 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -451,6 +451,7 @@ do -- TASK_A2G_DISPATCHER self.Detection = Detection self.Mission = Mission + self.FlashNewTask = true --set to false to suppress flash messages self.Detection:FilterCategories( { Unit.Category.GROUND_UNIT } ) @@ -471,6 +472,12 @@ do -- TASK_A2G_DISPATCHER return self end + --- Set flashing player messages on or off + -- @param #TASK_A2G_DISPATCHER self + -- @param #boolean onoff Set messages on (true) or off (false) + function TASK_A2G_DISPATCHER:SetSendMessages( onoff ) + self.FlashNewTask = onoff + end --- Creates a SEAD task when there are targets for it. -- @param #TASK_A2G_DISPATCHER self @@ -616,7 +623,9 @@ do -- TASK_A2G_DISPATCHER if not DetectedItem then local TaskText = Task:GetName() for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) + if self.FlashNewTask then + Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2G task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup ) + end end Task = self:RemoveTask( TaskIndex ) end @@ -686,7 +695,7 @@ do -- TASK_A2G_DISPATCHER -- Now we send to each group the changes, if any. for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do local TargetsText = TargetsReport:Text(", ") - if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" then + if ( Mission:IsGroupAssigned(TaskGroup) ) and TargetsText ~= "" and self.FlashNewTask then Mission:GetCommandCenter():MessageToGroup( string.format( "Task %s has change of targets:\n %s", Task:GetName(), TargetsText ), TaskGroup ) end end @@ -805,7 +814,7 @@ do -- TASK_A2G_DISPATCHER local TaskText = TaskReport:Text(", ") for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then + if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and self.FlashNewTask then Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) end end @@ -815,4 +824,4 @@ do -- TASK_A2G_DISPATCHER return true end -end \ No newline at end of file +end From e7efd89d7a7493f3a94b6db17d2abc4d4b61684d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 26 Nov 2020 10:30:07 +0100 Subject: [PATCH 016/382] Update Task_A2A_Dispatcher.lua Added user function to change flash message behaviour --- Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index 573b8c4b0..76c7225a7 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -253,7 +253,12 @@ do -- TASK_A2A_DISPATCHER return self end - + --- Set flashing player messages on or off + -- @param #TASK_A2A_DISPATCHER self + -- @param #boolean onoff Set messages on (true) or off (false) + function TASK_A2A_DISPATCHER:SetSendMessages( onoff ) + self.FlashNewTask = onoff + end --- Creates an INTERCEPT task when there are targets for it. -- @param #TASK_A2A_DISPATCHER self @@ -610,7 +615,7 @@ do -- TASK_A2A_DISPATCHER local TaskText = TaskReport:Text(", ") for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do - if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and ( not not self.FlashNewTask) then + if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and (self.FlashNewTask) then Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup ) end end From 1bd5193786760a838a9134bc39b639b23005f272 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 1 Dec 2020 16:10:55 +0100 Subject: [PATCH 017/382] Taskinfo.lua - Issue #1388 - don't just assume this is a string fixes the catchall at the end in case a task is unassigned - sometimes it's not a string. #1388 --- Moose Development/Moose/Tasking/TaskInfo.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua index 5ece5f005..8d825e309 100644 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ b/Moose Development/Moose/Tasking/TaskInfo.lua @@ -344,7 +344,9 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task ) Text = DataText else local DataText = Data.Data -- #string - Text = DataText + if type(DataText) == "string" then --Issue #1388 - don't just assume this is a string + Text = DataText + end end if Line < math.floor( Data.Order / 10 ) then From 82bfb0cc55efc6f01150b21d27ce2f823129305d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 1 Dec 2020 19:47:29 +0100 Subject: [PATCH 018/382] Issue ##1383 never ending flash messages Messages appears once and then goes away. --- Moose Development/Moose/Tasking/Task.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 7efa0962d..1ed3aee09 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -1252,7 +1252,7 @@ function TASK:MenuFlashTaskStatus( TaskGroup, Flash ) self.FlashTaskStatus = Flash if self.FlashTaskStatus then - self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 ) + self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60, 0, 61 ) --Issue #1383 never ending flash messages else if self.FlashTaskScheduler then self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID ) From e165cb156c7e960a7bd0c90d9fee9c093e0b7376 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:25:57 +0100 Subject: [PATCH 019/382] Update Task.lua -- stop message flashing, if any #1383 & #1312 --- Moose Development/Moose/Tasking/Task.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 1ed3aee09..7cd79f453 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -881,6 +881,9 @@ do -- Group Assignment local Mission = self:GetMission() local CommandCenter = Mission:GetCommandCenter() CommandCenter:SetMenu() + + self:MenuFlashTaskStatus( TaskGroup, false ) -- stop message flashing, if any #1383 & #1312 + end end @@ -1252,7 +1255,7 @@ function TASK:MenuFlashTaskStatus( TaskGroup, Flash ) self.FlashTaskStatus = Flash if self.FlashTaskStatus then - self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60, 0, 61 ) --Issue #1383 never ending flash messages + self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60) --Issue #1383 never ending flash messages else if self.FlashTaskScheduler then self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID ) From 2500cfb3c7ea09cfca91feb24086434f4c311487 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Dec 2020 23:17:40 +0100 Subject: [PATCH 020/382] RANGE v2.3.0 - Performance optimizations --- Moose Development/Moose/Core/Point.lua | 12 +- Moose Development/Moose/Functional/Range.lua | 156 +++++++++--------- .../Moose/Wrapper/Positionable.lua | 11 +- 3 files changed, 90 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ca1529950..31669d146 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -770,16 +770,14 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3. -- @return DCS#Distance Distance The distance in meters. - function COORDINATE:Get2DDistance( TargetCoordinate ) + function COORDINATE:Get2DDistance(TargetCoordinate) local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} - return UTILS.VecNorm(a) - - --local TargetVec3 = TargetCoordinate:GetVec3() - --local SourceVec3 = self:GetVec3() - - --return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + local d=UTILS.VecNorm(a) + + return d + end --- Returns the temperature in Degrees Celsius. diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 0f4b164fe..fc850743c 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -53,6 +53,7 @@ -- @type RANGE -- @field #string ClassName Name of the Class. -- @field #boolean Debug If true, debug info is send as messages on the screen. +-- @field #boolean verbose Verbosity level. Higher means more output to DCS log file. -- @field #string id String id of range for output in DCS log. -- @field #string rangename Name of the range. -- @field Core.Point#COORDINATE location Coordinate of the range location. @@ -289,6 +290,7 @@ RANGE={ ClassName = "RANGE", Debug = false, + verbose = 0, id = nil, rangename = nil, location = nil, @@ -518,7 +520,7 @@ RANGE.MenuF10Root=nil --- Range script version. -- @field #string version -RANGE.version="2.2.3" +RANGE.version="2.3.0" --TODO list: --TODO: Verbosity level for messages. @@ -556,7 +558,6 @@ function RANGE:New(rangename) -- Debug info. local text=string.format("Script version %s - creating new RANGE object %s.", RANGE.version, self.rangename) self:I(self.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) -- Defaults self:SetDefaultPlayerSmokeBomb() @@ -723,7 +724,6 @@ function RANGE:onafterStart() -- Starting range. local text=string.format("Starting RANGE %s. Number of strafe targets = %d. Number of bomb targets = %d.", self.rangename, self.nstrafetargets, self.nbombtargets) self:I(self.id..text) - MESSAGE:New(text,10):ToAllIf(self.Debug) -- Event handling. if self.eventmoose then @@ -757,6 +757,7 @@ function RANGE:onafterStart() -- Radio queue. self.rangecontrol=RADIOQUEUE:New(self.rangecontrolfreq, nil, self.rangename) + self.rangecontrol.schedonce=true -- Init numbers. self.rangecontrol:SetDigit(0, RANGE.Sound.RC0.filename, RANGE.Sound.RC0.duration, self.soundpath) @@ -781,7 +782,8 @@ function RANGE:onafterStart() if self.instructorfreq then -- Radio queue. - self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) + self.instructor=RADIOQUEUE:New(self.instructorfreq, nil, self.rangename) + self.instructor.schedonce=true -- Init numbers. self.instructor:SetDigit(0, RANGE.Sound.IR0.filename, RANGE.Sound.IR0.duration, self.soundpath) @@ -1148,7 +1150,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe -- Neither unit nor static object with this name could be found. local text=string.format("ERROR! Could not find ANY strafe target object with name %s.", _name) self:E(self.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) end @@ -1168,7 +1169,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe if ntargets==0 then local text=string.format("ERROR! No strafe target could be found when calling RANGE:AddStrafePit() for range %s", self.rangename) self:E(self.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) return end @@ -1238,7 +1238,6 @@ function RANGE:AddStrafePit(targetnames, boxlength, boxwidth, heading, inversehe -- Debug info local text=string.format("Adding new strafe target %s with %d targets: heading = %03d, box_L = %.1f, box_W = %.1f, goodpass = %d, foul line = %.1f", _name, ntargets, heading, l, w, goodpass, foulline) self:T(self.id..text) - MESSAGE:New(text, 5):ToAllIf(self.Debug) return self end @@ -1567,13 +1566,13 @@ function RANGE:OnEventBirth(EventData) -- Debug output. local text=string.format("Player %s, callsign %s entered unit %s (UID %d) of group %s (GID %d)", _playername, _callsign, _unitName, _uid, _group:GetName(), _gid) self:T(self.id..text) - MESSAGE:New(text, 5):ToAllIf(self.Debug) -- Reset current strafe status. self.strafeStatus[_uid] = nil -- Add Menu commands after a delay of 0.1 seconds. - SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) + --SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1) + self:ScheduleOnce(0.1, self._AddF10Commands, self, _unitName) -- By default, some bomb impact points and do not flare each hit on target. self.PlayerSettings[_playername]={} --#RANGE.PlayerData @@ -1591,7 +1590,8 @@ function RANGE:OnEventBirth(EventData) -- Start check in zone timer. if self.planes[_uid] ~= true then - SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) + --SCHEDULER:New(nil, self._CheckInZone, {self, EventData.IniUnitName}, 1, 1) + self.timerCheckZone=TIMER:New(self._CheckInZone, self, EventData.IniUnitName):Start(1, 1) self.planes[_uid] = true end @@ -1674,15 +1674,12 @@ function RANGE:OnEventHit(EventData) if _unit and _playername then - -- Position of target. - local targetPos = _target:GetCoordinate() - - -- Message to player. - --local text=string.format("%s, direct hit on target %s.", self:_myname(_unitName), targetname) - --self:DisplayMessageToGroup(_unit, text, 10, true) - -- Flare target. if self.PlayerSettings[_playername].flaredirecthits then + + -- Position of target. + local targetPos = _target:GetCoordinate() + targetPos:Flare(self.PlayerSettings[_playername].flarecolor) end @@ -1724,9 +1721,6 @@ function RANGE:OnEventShot(EventData) self:T(self.id.."EVENT SHOT: Weapon name = ".._weaponName) self:T(self.id.."EVENT SHOT: Weapon cate = "..weaponcategory) - -- Special cases: - --local _viggen=string.match(_weapon, "ROBOT") or string.match(_weapon, "RB75") or string.match(_weapon, "BK90") or string.match(_weapon, "RB15") or string.match(_weapon, "RB04") - -- Tracking conditions for bombs, rockets and missiles. local _bombs = weaponcategory==Weapon.Category.BOMB --string.match(_weapon, "weapons.bombs") local _rockets = weaponcategory==Weapon.Category.ROCKET --string.match(_weapon, "weapons.nurs") @@ -1760,7 +1754,7 @@ function RANGE:OnEventShot(EventData) self:T(self.id..string.format("RANGE %s: Tracking %s - %s.", self.rangename, _weapon, EventData.weapon:getName())) -- Init bomb position. - local _lastBombPos = {x=0,y=0,z=0} + local _lastBombPos = {x=0,y=0,z=0} --DCS#Vec3 -- Function monitoring the position of a bomb until impact. local function trackBomb(_ordnance) @@ -1916,35 +1910,39 @@ end -- @param #string To To state. function RANGE:onafterStatus(From, Event, To) - local fsmstate=self:GetState() - - local text=string.format("Range status: %s", fsmstate) - - if self.instructor then - local alive="N/A" - if self.instructorrelayname then - local relay=UNIT:FindByName(self.instructorrelayname) - if relay then - alive=tostring(relay:IsAlive()) - end - end - text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive) - end - - if self.rangecontrol then - local alive="N/A" - if self.rangecontrolrelayname then - local relay=UNIT:FindByName(self.rangecontrolrelayname) - if relay then - alive=tostring(relay:IsAlive()) - end - end - text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) - end + if self.verbose>0 then - - -- Check range status. - self:I(self.id..text) + local fsmstate=self:GetState() + + local text=string.format("Range status: %s", fsmstate) + + if self.instructor then + local alive="N/A" + if self.instructorrelayname then + local relay=UNIT:FindByName(self.instructorrelayname) + if relay then + alive=tostring(relay:IsAlive()) + end + end + text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive) + end + + if self.rangecontrol then + local alive="N/A" + if self.rangecontrolrelayname then + local relay=UNIT:FindByName(self.rangecontrolrelayname) + if relay then + alive=tostring(relay:IsAlive()) + end + end + text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) + end + + + -- Check range status. + self:I(self.id..text) + + end -- Check player status. self:_CheckPlayers() @@ -2736,6 +2734,32 @@ function RANGE:_CheckInZone(_unitName) if _unit and _playername then + --- Function to check if unit is in zone and facing in the right direction and is below the max alt. + local function checkme(targetheading, _zone) + local zone=_zone --Core.Zone#ZONE + + -- Heading check. + local unitheading = _unit:GetHeading() + local pitheading = targetheading-180 + local deltaheading = unitheading-pitheading + local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 + + if towardspit then + + local vec3=_unit:GetVec3() + local vec2={x=vec3.x, y=vec3.z} --DCS#Vec2 + local landheight=land.getHeight(vec2) + local unitalt=vec3.y-landheight + + if unitalt<=self.strafemaxalt then + local unitinzone=zone:IsVec2InZone(vec2) + return unitinzone + end + end + + return false + end + -- Current position of player unit. local _unitID = _unit:GetID() @@ -2747,18 +2771,8 @@ function RANGE:_CheckInZone(_unitName) -- Get the current approach zone and check if player is inside. local zone=_currentStrafeRun.zone.polygon --Core.Zone#ZONE_POLYGON_BASE - local unitheading = _unit:GetHeading() - local pitheading = _currentStrafeRun.zone.heading - 180 - local deltaheading = unitheading-pitheading - local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 - local unitalt=_unit:GetHeight()-_unit:GetCoordinate():GetLandHeight() - - -- Check if unit is inside zone and below max height AGL. - local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit - - -- Debug output - local text=string.format("Checking still in zone. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _unitName, _playername, tostring(unitinzone), unitalt, deltaheading) - self:T2(self.id..text) + -- Check if unit in zone and facing the right direction. + local unitinzone=checkme(_currentStrafeRun.zone.heading, zone) -- Check if player is in strafe zone and below max alt. if unitinzone then @@ -2858,22 +2872,10 @@ function RANGE:_CheckInZone(_unitName) for _,_targetZone in pairs(self.strafeTargets) do -- Get the current approach zone and check if player is inside. - local zonenname=_targetZone.name local zone=_targetZone.polygon --Core.Zone#ZONE_POLYGON_BASE - -- Check if player is in zone and below max alt and flying towards the target. - local unitheading = _unit:GetHeading() - local pitheading = _targetZone.heading - 180 - local deltaheading = unitheading-pitheading - local towardspit = math.abs(deltaheading)<=90 or math.abs(deltaheading-360)<=90 - local unitalt =_unit:GetHeight()-_unit:GetCoordinate():GetLandHeight() - - -- Check if unit is inside zone and below max height AGL. - local unitinzone=_unit:IsInZone(zone) and unitalt <= self.strafemaxalt and towardspit - - -- Debug info. - local text=string.format("Checking zone %s. Unit = %s, player = %s in zone = %s. alt = %d, delta heading = %d", _targetZone.name, _unitName, _playername, tostring(unitinzone), unitalt, deltaheading) - self:T2(self.id..text) + -- Check if unit in zone and facing the right direction. + local unitinzone=checkme(_targetZone.heading, zone) -- Player is inside zone. if unitinzone then @@ -2882,7 +2884,7 @@ function RANGE:_CheckInZone(_unitName) local _ammo=self:_GetAmmo(_unitName) -- Init strafe status for this player. - self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false } + self.strafeStatus[_unitID] = {hits = 0, zone = _targetZone, time = 1, ammo=_ammo, pastfoulline=false} -- Rolling in! local _msg=string.format("%s, rolling in on strafe pit %s.", self:_myname(_unitName), _targetZone.name) @@ -3089,11 +3091,9 @@ function RANGE:_GetAmmo(unitname) local text=string.format("Player %s has %d rounds ammo of type %s", playername, Nammo, Tammo) self:T(self.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) else local text=string.format("Player %s has %d ammo of type %s", playername, Nammo, Tammo) self:T(self.id..text) - MESSAGE:New(text, 10):ToAllIf(self.Debug) end end end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index f7632d687..00f828cb7 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -590,26 +590,29 @@ end --- Returns the POSITIONABLE heading in degrees. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSITIONABLE heading --- @return #nil The POSITIONABLE is not existing or alive. +-- @return #number The POSITIONABLE heading in degrees or `nil` if not existing or alive. function POSITIONABLE:GetHeading() + local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() + if PositionablePosition then local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) + if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end + PositionableHeading = PositionableHeading * 180 / math.pi - self:T2( PositionableHeading ) + return PositionableHeading end end - BASE:E( { "Cannot GetHeading", Positionable = self, Alive = self:IsAlive() } ) + self:E({"Cannot GetHeading", Positionable = self, Alive = self:IsAlive()}) return nil end From 5aecd8c25584892968d586bf26b66c243795d138 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Dec 2020 18:53:49 +0100 Subject: [PATCH 021/382] Ops - Performance --- Moose Development/Moose/Functional/Warehouse.lua | 15 +++++++++++++++ Moose Development/Moose/Ops/AirWing.lua | 9 ++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index f6bb9c8da..ac3d9f758 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3060,6 +3060,21 @@ function WAREHOUSE:GetCoordinate() return self.warehouse:GetCoordinate() end +--- Get 3D vector of warehouse static. +-- @param #WAREHOUSE self +-- @return DCS#Vec3 The 3D vector of the warehouse. +function WAREHOUSE:GetVec3() + return self.warehouse:GetVec3() +end + +--- Get 2D vector of warehouse static. +-- @param #WAREHOUSE self +-- @return DCS#Vec2 The 2D vector of the warehouse. +function WAREHOUSE:GetVec2() + return self.warehouse:GetVec2() +end + + --- Get coalition side of warehouse static. -- @param #WAREHOUSE self -- @return #number Coalition side, i.e. number of @{DCS#coalition.side}. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 0ea78cdc2..8ffd7e6bf 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1288,9 +1288,11 @@ end -- @param #boolean includePayload If true, include the payload in the calulation if the asset has one attached. function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) - local TargetCoordinate=Mission:GetTargetCoordinate() + local TargetVec2=Mission:GetTargetVec2() - local dStock=self:GetCoordinate():Get2DDistance(TargetCoordinate) + --local dStock=self:GetCoordinate():Get2DDistance(TargetCoordinate) + + local dStock=UTILS.VecDist2D(TargetVec2, self:GetVec2()) -- Calculate distance to mission target. local distmin=math.huge @@ -1300,7 +1302,8 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) if asset.spawned then local group=GROUP:FindByName(asset.spawngroupname) - asset.dist=group:GetCoordinate():Get2DDistance(TargetCoordinate) + --asset.dist=group:GetCoordinate():Get2DDistance(TargetCoordinate) + asset.dist=UTILS.VecDist2D(group:GetVec2(), TargetVec2) else asset.dist=dStock end From 65174ceaae207a134f4804f5b46c6bbe648c02f3 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 13 Dec 2020 16:06:57 +0100 Subject: [PATCH 022/382] Create Mantis.lua New Mantis extended Air-Defense Class --- Moose Development/Moose/Functional/Mantis.lua | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 Moose Development/Moose/Functional/Mantis.lua diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua new file mode 100644 index 000000000..75bd2c96f --- /dev/null +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -0,0 +1,366 @@ +----------------------------------------------------------------------- +-- MANTIS System +----------------------------------------------------------------------- +-- +--- **MANTIS** - Moose derived *M*odular, *A*utomatic and *N*etwork capable *T*argeting and *I*nterception *S*ystem +-- +-- === +-- +-- MANTIS - Moose derived Modular, Automatic and Network capable Targeting and Interception System +-- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy +-- Leverage evasiveness from SEAD +-- Leverage attack range setup added by DCS in 11/20 +-- +-- === +-- +-- ### Authors : **applevangelist ** +-- +-- @module Functional.Mantis +-- Date: Dec 2020 +-- +------------------------------------------------------------------------- +--- **MANTIS** class, extends @{Core.Base#BASE} +-- @type MANTIS +-- @field #string Classname +-- @field #string name Name of this Mantis +-- @field #string SAM_Templates_Prefix Prefix to build the #GROUP_SET for SAM sites +-- @field @{Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET +-- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group +-- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET +-- @field #string SEAD_Template_CC The ME name of the HQ object +-- @field @{Tasking.CommandCenter#COMMANDCENTER} SEAD_CC The #COMMANDCENTER object +-- @field #table SAM_Table Table of SAM sites +-- @field #string lid Prefix for logging +-- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object +-- @field #boolean debug Switch on extra messages +-- @field #number checkradius Radius of the SAM sites +-- @field #number grouping Radius to group detected objects +-- @field #number acceptrange Radius of the EWR detection +-- @field #number detectinterval Interval in seconds for the target detection +-- @extends @{Core.Base#BASE} + +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +-- +-- Simple Class for a more intelligent Air Defense System +-- +-- #MANTIS +-- Moose derived Modular, Automatic and Network capable Targeting and Interception System. +-- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy. +-- Leverage evasiveness from @{Functional.Sead#SEAD}. +-- Leverage attack range setup added by DCS in 11/20. +-- @usage +-- Set up your SAM sites in the mission editor. Name the groups with common prefix like "Red SAM". +-- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. +-- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. +-- +-- Start up your MANTIS +-- +-- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` +-- [optional] Use `MANTIS:SetEWRGrouping(radius)`, `MANTIS:SetEWRRange(radius)`, `MANTIS:SetSAMRadius(radius)`, `MANTIS:SetDetectInterval(interval)` to fine-tune your setup. +-- +-- `myredmantis:Start()` +-- +-- @field #MANTIS +MANTIS = { + ClassName = "MANTIS", + name = "mymantis", + SAM_Templates_Prefix = "", + SAM_Group = nil, + EWR_Templates_Prefix = "", + EWR_Group = nil, + SEAD_Template_CC = "", + SEAD_CC = nil, + SAM_Table = {}, + lid = "", + Detection = nil, + debug = false, + checkradius = 25000, + grouping = 5000, + acceptrange = 80000, + detectinterval = 30 +} + +do + --- Function instantiate new class + --@param #MANTIS self + --@param #string name Name of this MANTIS for reporting + --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..." + --@param #string ewrprefix Prefixes for the EWR and AWACS groups from the ME, e.g. all groups starting with "Red EWR..." + --@param #string hq Group name of your HQ (optional) + --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" + --@param #boolean dynamic Use constant (true) filtering or just filer once (false, default) + --@return #MANTIS self + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic) + + -- DONE: Create user functions for these + -- TODO: Make HQ useful + -- TODO: Set SAMs to auto if EWR dies + + self.name = name or "mymantis" + self.SAM_Templates_Prefix = samprefix or "Red SAM" + self.EWR_Templates_Prefix = ewrprefix or "Red EWR" + self.SEAD_Template_CC = hq or nil + self.Coalition = coaltion or "red" + self.SAM_Table = {} + self.dynamic = dynamic or false + self.checkradius = 25000 + self.grouping = 5000 + self.acceptrange = 80000 + self.detectinterval = 30 + + -- @field #string version + self.version="0.2.1" + env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) + + -- Set some string id for output to DCS.log file. + self.lid=string.format("MANTIS %s | ", self.name) + + -- Debug trace. + if self.debug then + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + --BASE:TraceClass("SEAD") + BASE:TraceLevel(1) + end + + if self.dynamic then + -- get SAM SET_GROUP + self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() + -- get EWR SET_GROUP + self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterStart() + else + -- get SAM SET_GROUP + self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() + -- get EWR SET_GROUP + self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce() + end + + -- set up CC + if self.SEAD_Template_CC then + self.SEAD_CC = COMMANDCENTER:New(GROUP:FindByName(self.SEAD_Template_CC),self.SEAD_Template_CC) + end + -- Inherit everything from BASE class. + local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + + return self + end + +----------------------------------------------------------------------- +-- MANTIS helper functions +----------------------------------------------------------------------- + + --- [internal] Function to get the self.SAM_Table + -- @param #MANTIS self + -- @return #table table + function MANTIS:_GetSAMTable() + return self.SAM_Table + end + + --- [internal] Function to set the self.SAM_Table + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_SetSAMTable(table) + self.SAM_Table = table + return self + end + + --- Function to set the grouping radius of the detection in meters + -- @param #MANTIS self + -- @param #number radius Radius upon which detected objects will be grouped + function MANTIS:SetEWRGrouping(radius) + local radius = radius or 5000 + self.grouping = radius + end + + --- Function to set the detection radius of the EWR in meters + -- @param #MANTIS self + -- @param #number radius Radius of the EWR detection zone + function MANTIS:SetEWRRange(radius) + local radius = radius or 80000 + self.acceptrange = radius + end + + --- Function to set switch-on/off zone for the SAM sites in meters + -- @param #MANTIS self + -- @param #number radius Radius of the firing zone + function MANTIS:SetSAMRadius(radius) + local radius = radius or 25000 + self.checkradius = radius + end + + --- Function to set switch-on/off the debug state + -- @param #MANTIS self + -- @param #boolean onoff Set true to switch on + function MANTIS:Debug(onoff) + local onoff = onoff or false + self.debug = onoff + end + + --- Function to set the detection interval + -- @param #MANTIS self + -- @param #number interval The interval in seconds + function MANTIS:SetDetectInterval(interval) + local interval = interval or 30 + self.detectinterval = interval + end + + --- Function to check if no object is in the given SAM zone + -- @param #MANTIS self + -- @param #table dectset Table of coordinates of detected items + -- @param samcoordinate Core.Point#COORDINATE Coordinate object. + -- @return #boolean True if in any zone, else false + function MANTIS:CheckObjectInZone(dectset, samcoordinate) + self:F(self.lid.."CheckObjectInZone Called") + -- check if non of the coordinate is in the given defense zone + local radius = self.checkradius + local set = dectset + for _,_coord in pairs (set) do + local coord = _coord -- get current coord to check + -- output for cross-check + local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringLLDMS() + local targetdistance = samcoordinate:DistanceFromPointVec2(coord) + local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) + local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) + -- end output to cross-check + if targetdistance <= radius then + return true + end + end + return false + end + + --- Function to start the detection via EWR groups + -- @param #MANTIS self + -- @return Functional.Detection #DETECTION_AREAS The running detection set + function MANTIS:StartDetection() + self:F(self.lid.."Starting Detection") + + -- start detection + groupset = self.EWR_Group + local grouping = self.grouping or 5000 + local acceptrange = self.acceptrange or 80000 + local interval = self.detectinterval or 60 + + _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --group detected objects to 5000m zones + _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + _MANTISdetection:SetAcceptRange(acceptrange) + _MANTISdetection:SetRefreshTimeInterval(interval) + _MANTISdetection:Start() + + function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) + --BASE:I( { From, Event, To, DetectedItem }) + if DetectedItem.IsDetected then + local Coordinate = DetectedItem.Coordinate -- Core.Point#COORDINATE + local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS() + m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + end + end + return _MANTISdetection + end + + --- Function to set the SAM start state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:SetSAMStartState() + self:F(self.lid.."Setting SAM Start States") + -- get SAM Group + local SAM_SET = self.SAM_Group + local SAM_Grps = SAM_SET.Set --table of objects + local SAM_Tbl = {} -- table of SAM defense zones + local SEAD_Grps = {} -- table of SAM names to make evasive + --cycle through groups and set alarm state etc + for _i,_group in pairs (SAM_Grps) do + --for i=1,#SAM_Grps do + local group = _group + group:OptionAlarmStateGreen() + --group:OptionROEHoldFire() + --group:SetAIOn() + group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,75) --engagement will be 75% of firing range + if group:IsGround() then + local grpname = group:GetName() + local grpcoord = group:GetCoordinate() + local grpzone = ZONE_UNIT:New(grpname,group:GetUnit(1),5000) -- defense zone around each SAM site 5000 meters + table.insert( SAM_Tbl, {grpname, grpcoord, grpzone}) + table.insert( SEAD_Grps, grpname ) + end + end + self.SAM_Table = SAM_Tbl + -- make SAMs evasive + SEAD:New( SEAD_Grps ) + return self + end + +----------------------------------------------------------------------- +-- MANTIS main functions +----------------------------------------------------------------------- + + --- Function to set the SAM start state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:Start() + self:F(self.lid.."Starting MANTIS") + self:SetSAMStartState() + self.Detection = self:StartDetection() + + local function check(detection) + --get detected set + local detset = detection:GetDetectedItemCoordinates() + self:F("Check:", {detset}) + --[[report detections + if self.debug then + for _,_data in pairs (detset) do + local coord = _data + local text = "Target detect at " + text = text..coord:ToStringLLDMS() + m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + end --end for + end --end if ]] + -- switch SAMs on/off if (n)one of the detected groups is inside their reach + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates and i.3=zones of SAM sites + for _,_data in pairs (samset) do + local samcoordinate = _data[2] + local name = _data[1] + local samgroup = GROUP:FindByName(name) + if self:CheckObjectInZone(detset, samcoordinate) then --check any target in zone + if samgroup:IsAlive() then + -- switch off SAM + samgroup:OptionAlarmStateRed() + --samgroup:OptionROEWeaponFree() + --samgroup:SetAIOn() + text = string.format("SAM %s switched to alarm state RED!", name) + m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + end --end alive + else + if samgroup:IsAlive() then + -- switch off SAM + samgroup:OptionAlarmStateGreen() + --samgroup:OptionROEWeaponFree() + --samgroup:SetAIOn() + text = string.format("SAM %s switched to alarm state GREEN!", name) + m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + end --end alive + end --end check + end --for for loop + end --end function + -- timer to run the system + local interval = self.detectinterval + self.MantisTimer = TIMER:New(check,self.Detection) + self.MantisTimer:Start(5,interval,nil) + return self + end + + --- Function to stop MANTIS + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:Stop() + if self.MantisTimer then + self.MantisTimer:Stop() + end + return self + end + +end +----------------------------------------------------------------------- +-- MANTIS end +----------------------------------------------------------------------- From 47c461e5049f2b04107a870a8eeb65df18efaef8 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 14 Dec 2020 00:08:30 +0100 Subject: [PATCH 023/382] SPAWNSTATIC - Attempt to SPAWN FARPS. Does not work yet. --- Moose Development/Moose/Core/SpawnStatic.lua | 38 ++++++++++++++++---- Moose Development/Moose/Ops/Airboss.lua | 10 ++++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 815eef099..d01701010 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -241,6 +241,20 @@ function SPAWNSTATIC:InitShape(StaticShape) return self end +--- Initialize parameters for spawning FARPs. +-- @param #SPAWNSTATIC self +-- @param #number CallsignID Callsign ID. Default 1 (="London"). +-- @param #number Frequency Frequency in MHz. Default 127.5 MHz. +-- @param #number Modulation Modulation 0=AM, 1=FM. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:InitFARP(CallsignID, Frequency, Modulation) + self.InitFarp=true + self.InitFarpCallsignID=CallsignID or 1 + self.InitFarpFreq=Frequency or 127.5 + self.InitFarpModu=Modulation or 0 + return self +end + --- Initialize cargo mass. -- @param #SPAWNSTATIC self -- @param #number Mass Mass of the cargo in kg. @@ -420,22 +434,34 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 end + if self.InitFarp then + Template.heliport_callsign_id = self.InitFarpCallsignID + Template.heliport_frequency = self.InitFarpFreq + Template.heliport_modulation = self.InitFarpModu + Template.unitId=nil + end + -- Increase spawn index counter. self.SpawnIndex = self.SpawnIndex + 1 -- Name of the spawned static. Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) - -- Register the new static. - --_DATABASE:_RegisterStaticTemplate(Template, self.CoalitionID, self.CategoryID, CountryID) - _DATABASE:AddStatic(Template.name) + -- Add and register the new static. + local mystatic=_DATABASE:AddStatic(Template.name) -- Debug output. self:T(Template) -- Add static to the game. - local Static=coalition.addStaticObject(CountryID, Template) - + local Static=nil + + if self.InitFARP then + env.info("Spawning FARP") + Static=coalition.addGroup(CountryID, -1, Template) + else + Static=coalition.addStaticObject(CountryID, Template) + end - return _DATABASE:FindStatic(Static:getName()) + return mystatic end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 592adaf21..418962fc8 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -27,12 +27,16 @@ -- **Supported Carriers:** -- -- * [USS John C. Stennis](https://en.wikipedia.org/wiki/USS_John_C._Stennis) (CVN-74) +-- * [USS Theodore Roosevelt](https://en.wikipedia.org/wiki/USS_Theodore_Roosevelt_(CVN-71)) (CVN-71) [Super Carrier Module] +-- * [USS Abraham Lincoln](https://en.wikipedia.org/wiki/USS_Abraham_Lincoln_(CVN-72)) (CVN-72) [Super Carrier Module] +-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73)) (CVN-73) [Super Carrier Module] +-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module] -- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1)) (LHA-1) [**WIP**] -- -- **Supported Aircraft:** -- -- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI) --- * [F-14B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) +-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) @@ -47,7 +51,9 @@ -- the no other fixed wing aircraft (human or AI controlled) are supposed to land on the Tarawa. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- --- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. +-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. Same goes for the A version. +-- +-- The [DCS Supercarriers](https://forums.eagle.ru/forum/151-dcs-supercarrier/) are also supported. -- -- ## Discussion -- From 5515aa43d6f2e4a722dd7be83c86cd51e24ecc94 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Dec 2020 10:42:11 +0100 Subject: [PATCH 024/382] Update Modules.lua added Mantis.lua to Functional --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 73653489c..dde648962 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -67,6 +67,7 @@ __Moose.Include( 'Scripts/Moose/Functional/Suppression.lua' ) __Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Fox.lua' ) +__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) From 6c9dce70e7bb3d3e50826051068150fd60e3f168 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 14 Dec 2020 12:36:02 +0100 Subject: [PATCH 025/382] SPAWNSTATIC - Added new way to spawn FARPs - Added FARP callsign enum. --- Moose Development/Moose/Core/SpawnStatic.lua | 19 +++++++++++++++++-- Moose Development/Moose/Utilities/Utils.lua | 13 +++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index d01701010..8bbb35782 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -457,8 +457,23 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) local Static=nil if self.InitFARP then - env.info("Spawning FARP") - Static=coalition.addGroup(CountryID, -1, Template) + + local TemplateGroup={} + TemplateGroup.units={} + TemplateGroup.units[1]=Template + + TemplateGroup.visible=true + TemplateGroup.hidden=false + TemplateGroup.x=Template.x + TemplateGroup.y=Template.y + TemplateGroup.name=Template.name + + self:T("Spawning FARP") + self:T({Template=Template}) + self:T({TemplateGroup=TemplateGroup}) + + -- ED's dirty way to spawn FARPS. + Static=coalition.addGroup(CountryID, -1, TemplateGroup) else Static=coalition.addStaticObject(CountryID, Template) end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index bc7f32daa..ff42f0a47 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -115,6 +115,19 @@ CALLSIGN={ Mantis=18, Badger=19, }, + -- FARP + FARP={ + London=1, + Dallas=2, + Paris=3, + Moscow=4, + Berlin=5, + Rome=6, + Madrid=7, + Warsaw=8, + Dublin=9, + Perth=10, + }, } --#CALLSIGN --- Utilities static class. From e3c14678e24f30d53ea7f26b0cadce30e1d07e5d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Dec 2020 13:37:17 +0100 Subject: [PATCH 026/382] Error in FlightGroup.lua in line 2390 as detected by HRP Zero Not searching in Paris, but in pairs :) --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index d71f76d0e..1d64e7e22 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2387,7 +2387,7 @@ function FLIGHTGROUP:onafterEngageTargets(From, Event, To, TargetUnitSet) local DCSTasks={} - for _,_unit in paris(TargetUnitSet:GetSet()) do + for _,_unit in pairs(TargetUnitSet:GetSet()) do --detected by =HRP= Zero local unit=_unit --Wrapper.Unit#UNIT local task=self.group:TaskAttackUnit(unit, true) table.insert(DCSTasks) From 80b7e49eed8173efc771dccf394c106e2f44aa34 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Dec 2020 11:59:10 +0100 Subject: [PATCH 027/382] Update Mantis.lua Added picture link, cleaned up unnecessary globals --- Moose Development/Moose/Functional/Mantis.lua | 54 ++++++++++++------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 75bd2c96f..06de08fa0 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -12,12 +12,19 @@ -- Leverage attack range setup added by DCS in 11/20 -- -- === +-- +-- ## Missions: -- +-- ### [MANTIS - Modular, Automatic and Network capable Targeting and Interception System](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MTS%20-%20Mantis/MTS-010%20-%20Basic%20Mantis%20Demo) +-- +-- === +-- -- ### Authors : **applevangelist ** -- -- @module Functional.Mantis +-- @image Functional.Mantis.jpg -- Date: Dec 2020 --- + ------------------------------------------------------------------------- --- **MANTIS** class, extends @{Core.Base#BASE} -- @type MANTIS @@ -48,21 +55,27 @@ -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy. -- Leverage evasiveness from @{Functional.Sead#SEAD}. -- Leverage attack range setup added by DCS in 11/20. --- @usage +-- -- Set up your SAM sites in the mission editor. Name the groups with common prefix like "Red SAM". -- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. -- -- Start up your MANTIS -- --- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` --- [optional] Use `MANTIS:SetEWRGrouping(radius)`, `MANTIS:SetEWRRange(radius)`, `MANTIS:SetSAMRadius(radius)`, `MANTIS:SetDetectInterval(interval)` to fine-tune your setup. +-- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` +-- +-- [optional] Use +-- + `MANTIS:SetEWRGrouping(radius)` +-- + `MANTIS:SetEWRRange(radius)` +-- + `MANTIS:SetSAMRadius(radius)` +-- + `MANTIS:SetDetectInterval(interval)` +-- to fine-tune your setup. -- --- `myredmantis:Start()` +-- `myredmantis:Start()` -- -- @field #MANTIS MANTIS = { - ClassName = "MANTIS", + ClassName = "MANTIS", name = "mymantis", SAM_Templates_Prefix = "", SAM_Group = nil, @@ -109,10 +122,10 @@ do self.detectinterval = 30 -- @field #string version - self.version="0.2.1" + self.version="0.2.2" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) - -- Set some string id for output to DCS.log file. + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) -- Debug trace. @@ -124,14 +137,14 @@ do end if self.dynamic then - -- get SAM SET_GROUP + -- Set SAM SET_GROUP self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() - -- get EWR SET_GROUP + -- Set EWR SET_GROUP self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterStart() else - -- get SAM SET_GROUP + -- Set SAM SET_GROUP self.SAM_Group = SET_GROUP:New():FilterPrefixes(self.SAM_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() - -- get EWR SET_GROUP + -- Set EWR SET_GROUP self.EWR_Group = SET_GROUP:New():FilterPrefixes({self.SAM_Templates_Prefix,self.EWR_Templates_Prefix}):FilterCoalitions(self.Coalition):FilterOnce() end @@ -204,7 +217,7 @@ do self.detectinterval = interval end - --- Function to check if no object is in the given SAM zone + --- Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items -- @param samcoordinate Core.Point#COORDINATE Coordinate object. @@ -237,12 +250,13 @@ do self:F(self.lid.."Starting Detection") -- start detection - groupset = self.EWR_Group + local groupset = self.EWR_Group local grouping = self.grouping or 5000 local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --group detected objects to 5000m zones + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object + _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Groups detected objects to 5000m zones _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) _MANTISdetection:SetAcceptRange(acceptrange) _MANTISdetection:SetRefreshTimeInterval(interval) @@ -253,7 +267,7 @@ do if DetectedItem.IsDetected then local Coordinate = DetectedItem.Coordinate -- Core.Point#COORDINATE local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS() - m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end return _MANTISdetection @@ -328,8 +342,8 @@ do samgroup:OptionAlarmStateRed() --samgroup:OptionROEWeaponFree() --samgroup:SetAIOn() - text = string.format("SAM %s switched to alarm state RED!", name) - m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) end --end alive else if samgroup:IsAlive() then @@ -337,8 +351,8 @@ do samgroup:OptionAlarmStateGreen() --samgroup:OptionROEWeaponFree() --samgroup:SetAIOn() - text = string.format("SAM %s switched to alarm state GREEN!", name) - m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) end --end alive end --end check end --for for loop From 4fda0414a0e6b760bbed8b7006dab3a20b29c5ba Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 16 Dec 2020 10:10:46 +0100 Subject: [PATCH 028/382] Add new Routing Function to Controllable.lua --- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving. -- @param #CONTROLLABLE self -- @param #number speed Speed of the controllable, default 20 -- @param #number radius Radius of the relocation zone, default 500 -- @param #boolean onroad If true, route on road (less problems with AI way finding), default true -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false --- .../Moose/Wrapper/Controllable.lua | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 6282d409c..38b7f147b 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -129,6 +129,7 @@ -- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route. -- * @{#CONTROLLABLE.RouteGroundTo}(): Make the GROUND Controllable to drive towards a specific coordinate. -- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate. +-- * @{CONTROLLABLE.RelocateGroundRandomInRadius}(): Relocate the GROUND controllable to a random point in a given radius. -- -- # 5) Option methods -- @@ -3713,3 +3714,33 @@ function CONTROLLABLE:OptionEngageRange(EngageRange) end return nil end + +--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving. +-- @param #CONTROLLABLE self +-- @param #number speed Speed of the controllable, default 20 +-- @param #number radius Radius of the relocation zone, default 500 +-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true +-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false +function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) + self:F2( { self.ControllableName } ) + + local _coord = self:GetCoordinate() + local _radius = radius or 500 + local _speed = speed or 20 + local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) + local _onroad = onroad or true + local _grptsk = {} + local _candoroad = false + local _shortcut = shortcut or false + + -- create a DCS Task an push it on the group + -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) + if onroad then + _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut) + self:Route(_grptsk,5) + else + self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") + end + + return self +end From c01b94518f9996ff61c6dee4ea2d8aea8300eb8d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 17 Dec 2020 11:16:30 +0100 Subject: [PATCH 029/382] Update to Mantis 0.2.5 Changelog -- changed HQ to be a GROUP object instead of creating a CommandCenter. Allows user to use their own CC. -- added functionality to autorelocate HQ and EWR group(s) at random intervals between 30 and 60 mins -- added verbosity option for logging -- tidied up documentation --- Moose Development/Moose/Functional/Mantis.lua | 186 +++++++++++++----- 1 file changed, 140 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 06de08fa0..698b1c8fb 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1,12 +1,8 @@ ------------------------------------------------------------------------ --- MANTIS System ------------------------------------------------------------------------ --- ---- **MANTIS** - Moose derived *M*odular, *A*utomatic and *N*etwork capable *T*argeting and *I*nterception *S*ystem +--- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses -- -- === -- --- MANTIS - Moose derived Modular, Automatic and Network capable Targeting and Interception System +-- **MANTIS** - Moose derived Modular, Automatic and Network capable Targeting and Interception System -- Controls a network of SAM sites. Use detection to switch on the AA site closest to the enemy -- Leverage evasiveness from SEAD -- Leverage attack range setup added by DCS in 11/20 @@ -19,10 +15,11 @@ -- -- === -- --- ### Authors : **applevangelist ** +-- ### Author : **applevangelist ** -- -- @module Functional.Mantis -- @image Functional.Mantis.jpg + -- Date: Dec 2020 ------------------------------------------------------------------------- @@ -35,17 +32,21 @@ -- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group -- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET -- @field #string SEAD_Template_CC The ME name of the HQ object --- @field @{Tasking.CommandCenter#COMMANDCENTER} SEAD_CC The #COMMANDCENTER object +-- @field @{Wrapper.Group#GROUP} SEAD_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging -- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object -- @field #boolean debug Switch on extra messages +-- @field #boolean verbose Switch on extra logging -- @field #number checkradius Radius of the SAM sites -- @field #number grouping Radius to group detected objects -- @field #number acceptrange Radius of the EWR detection -- @field #number detectinterval Interval in seconds for the target detection +-- @field #number engagerange Firing engage range of the SAMs, see [https://wiki.hoggitworld.com/view/DCS_option_engagementRange] +-- @field #boolean autorelocate Relocate HQ and EWR groups in random intervals. Note: You need to select units for this which are *actually mobile* -- @extends @{Core.Base#BASE} + --- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat -- -- Simple Class for a more intelligent Air Defense System @@ -65,34 +66,43 @@ -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- -- [optional] Use --- + `MANTIS:SetEWRGrouping(radius)` --- + `MANTIS:SetEWRRange(radius)` --- + `MANTIS:SetSAMRadius(radius)` --- + `MANTIS:SetDetectInterval(interval)` +-- * `MANTIS:SetEWRGrouping(radius)` +-- * `MANTIS:SetEWRRange(radius)` +-- * `MANTIS:SetSAMRadius(radius)` +-- * `MANTIS:SetDetectInterval(interval)` +-- * `MANTIS:SetAutoRelocate(hq, ewr)` -- to fine-tune your setup. -- -- `myredmantis:Start()` --- +-- +-- -- @field #MANTIS MANTIS = { - ClassName = "MANTIS", - name = "mymantis", - SAM_Templates_Prefix = "", - SAM_Group = nil, - EWR_Templates_Prefix = "", - EWR_Group = nil, - SEAD_Template_CC = "", - SEAD_CC = nil, - SAM_Table = {}, - lid = "", - Detection = nil, - debug = false, - checkradius = 25000, - grouping = 5000, - acceptrange = 80000, - detectinterval = 30 + ClassName = "MANTIS", + name = "mymantis", + SAM_Templates_Prefix = "", + SAM_Group = nil, + EWR_Templates_Prefix = "", + EWR_Group = nil, + SEAD_Template_CC = "", + SEAD_CC = nil, + SAM_Table = {}, + lid = "", + Detection = nil, + debug = false, + checkradius = 25000, + grouping = 5000, + acceptrange = 80000, + detectinterval = 30, + engagerange = 75, + autorelocate = false, + verbose = false } +----------------------------------------------------------------------- +-- MANTIS System +----------------------------------------------------------------------- + do --- Function instantiate new class --@param #MANTIS self @@ -101,11 +111,11 @@ do --@param #string ewrprefix Prefixes for the EWR and AWACS groups from the ME, e.g. all groups starting with "Red EWR..." --@param #string hq Group name of your HQ (optional) --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" - --@param #boolean dynamic Use constant (true) filtering or just filer once (false, default) + --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) --@return #MANTIS self function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic) - -- DONE: Create user functions for these + -- TODO: Create some user functions for these -- TODO: Make HQ useful -- TODO: Set SAMs to auto if EWR dies @@ -120,9 +130,13 @@ do self.grouping = 5000 self.acceptrange = 80000 self.detectinterval = 30 + self.engagerange = 75 + self.autorelocate = false + self.autorelocateunits = { HQ = false, EWR = false} + self.verbose = false -- @field #string version - self.version="0.2.2" + self.version="0.2.5" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) -- Set the string id for output to DCS.log file. @@ -150,7 +164,8 @@ do -- set up CC if self.SEAD_Template_CC then - self.SEAD_CC = COMMANDCENTER:New(GROUP:FindByName(self.SEAD_Template_CC),self.SEAD_Template_CC) + self.SEAD_CC = GROUP:FindByName(self.SEAD_Template_CC) + --self.SEAD_CC = COMMANDCENTER:New(GROUP:FindByName(self.SEAD_Template_CC),self.SEAD_Template_CC) end -- Inherit everything from BASE class. local self = BASE:Inherit(self, BASE:New()) -- #MANTIS @@ -201,13 +216,35 @@ do self.checkradius = radius end + --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 + -- @param #MANTIS self + -- @param #number range Percent of the max fire range + function MANTIS:SetSAMRadius(range) + local range = range or 75 + if range < 0 or range > 100 then + range = 75 + end + self.engagerange = range + end + --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on function MANTIS:Debug(onoff) local onoff = onoff or false self.debug = onoff - end + end + + --- Function to get the HQ object for further use + -- @param #MANTIS self + -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist + function MANTIS:GetCommandCenter() + if self.SEAD_CC then + return self.SEAD_CC + else + return nil + end + end --- Function to set the detection interval -- @param #MANTIS self @@ -215,7 +252,57 @@ do function MANTIS:SetDetectInterval(interval) local interval = interval or 30 self.detectinterval = interval + end + + --- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS! + -- @param #MANTIS self + -- @param #boolean hq If true, will relocate HQ object + -- @param #boolean ewr If true, will relocate EWR objects + function MANTIS:SetAutoRelocate(hq, ewr) + self:F({hq, ewr}) + local hqrel = hq or false + local ewrel = ewr or false + if hqrel or ewrel then + self.autorelocate = true + self.autorelocateunits = { HQ = hqrel, EWR = ewrel } + self:T({self.autorelocate, self.autorelocateunits}) + end end + + --- [Internal] Function to execute the relocation + -- @param #MANTIS self + function MANTIS:_RelocateGroups() + self:T(self.lid.." Relocating Groups") + local text = self.lid.." Relocating Groups" + local m= MESSAGE:New(text,15,"MANTIS",true):ToAllIf(self.debug) + if self.verbose then env.info(text) end + if self.autorelocate then + -- relocate HQ + if self.autorelocateunits.HQ and self.SEAD_CC then --only relocate if HQ exists + local _hqgrp = self.SEAD_CC + self:T(self.lid.." Relocating HQ") + local text = self.lid.." Relocating HQ" + local m= MESSAGE:New(text,15,"MANTIS"):ToAll() + _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) + end + --relocate EWR + -- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach + if self.autorelocateunits.EWR then + -- get EWR Group + local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() + local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP + for _,_grp in pairs (EWR_Grps) do + if _grp:IsGround() then + self:T(self.lid.." Relocating EWR ".._grp:GetName()) + local text = self.lid.." Relocating EWR ".._grp:GetName() + local m= MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(text) end + _grp:RelocateGroundRandomInRadius(20,500,true,true) + end + end + end + end + end --- Function to check if any object is in the given SAM zone -- @param #MANTIS self @@ -235,6 +322,7 @@ do local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) + if self.verbose then env.info(self.lid..text) end -- end output to cross-check if targetdistance <= radius then return true @@ -256,7 +344,7 @@ do local interval = self.detectinterval or 60 --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Groups detected objects to 5000m zones + _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) _MANTISdetection:SetAcceptRange(acceptrange) _MANTISdetection:SetRefreshTimeInterval(interval) @@ -264,7 +352,8 @@ do function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) - if DetectedItem.IsDetected then + local debug = false + if DetectedItem.IsDetected and debug then local Coordinate = DetectedItem.Coordinate -- Core.Point#COORDINATE local text = "MANTIS: Detection at "..Coordinate:ToStringLLDMS() local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) @@ -283,6 +372,7 @@ do local SAM_Grps = SAM_SET.Set --table of objects local SAM_Tbl = {} -- table of SAM defense zones local SEAD_Grps = {} -- table of SAM names to make evasive + local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do --for i=1,#SAM_Grps do @@ -290,7 +380,7 @@ do group:OptionAlarmStateGreen() --group:OptionROEHoldFire() --group:SetAIOn() - group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,75) --engagement will be 75% of firing range + group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range if group:IsGround() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() @@ -321,15 +411,6 @@ do --get detected set local detset = detection:GetDetectedItemCoordinates() self:F("Check:", {detset}) - --[[report detections - if self.debug then - for _,_data in pairs (detset) do - local coord = _data - local text = "Target detect at " - text = text..coord:ToStringLLDMS() - m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) - end --end for - end --end if ]] -- switch SAMs on/off if (n)one of the detected groups is inside their reach local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates and i.3=zones of SAM sites for _,_data in pairs (samset) do @@ -344,6 +425,7 @@ do --samgroup:SetAIOn() local text = string.format("SAM %s switched to alarm state RED!", name) local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(self.lid..text) end end --end alive else if samgroup:IsAlive() then @@ -353,14 +435,23 @@ do --samgroup:SetAIOn() local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(self.lid..text) end end --end alive end --end check end --for for loop end --end function + -- relocation relay function + local function relocate() + self:_RelocateGroups() + end -- timer to run the system local interval = self.detectinterval self.MantisTimer = TIMER:New(check,self.Detection) self.MantisTimer:Start(5,interval,nil) + -- relocate HQ and EWR + local relointerval = math.random(1800,3600) -- random between 30 and 60 mins + self.MantisReloTimer = TIMER:New(relocate) + self.MantisReloTimer:Start(relointerval,relointerval,nil) return self end @@ -371,6 +462,9 @@ do if self.MantisTimer then self.MantisTimer:Stop() end + if self.MantisReloTimer then + self.MantisReloTimer:Stop() + end return self end From ee6954a4b95f56b060eab09117af2539ced06e4e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 17 Dec 2020 13:54:59 +0100 Subject: [PATCH 030/382] Update AI_Air_Engage.lua - Changed :Count to :CountAlive for AttackSetUnit. Issue #1393 --- Moose Development/Moose/AI/AI_Air_Engage.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 2679d472d..03a693ddf 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -418,6 +418,7 @@ end -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. +-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked. function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) self:I( { DefenderGroup, From, Event, To, AttackSetUnit } ) @@ -425,7 +426,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - local AttackCount = AttackSetUnit:Count() + local AttackCount = AttackSetUnit:CountAlive() if AttackCount > 0 then @@ -510,6 +511,7 @@ end -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. +-- @param Core.Set#SET_UNIT AttackSetUnit Set of units to be attacked. function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) @@ -517,8 +519,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - local AttackCount = AttackSetUnit:Count() - self:I({AttackCount = AttackCount}) + local AttackCount = AttackSetUnit:CountAlive()() + self:T({AttackCount = AttackCount}) if AttackCount > 0 then From ce5aaf0b485ba0fcf968e2aef5e87f732b9d6acc Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 18 Dec 2020 13:52:08 +0100 Subject: [PATCH 031/382] Update Airbase.lua - Changed Persion Gulf Airbase Names --- Moose Development/Moose/Wrapper/Airbase.lua | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 01e4eeb17..74097fd5c 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -268,31 +268,31 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Tunb_Kochak -- @field PersianGulf AIRBASE.PersianGulf = { - ["Abu_Dhabi_International_Airport"] = "Abu Dhabi International Airport", - ["Abu_Musa_Island_Airport"] = "Abu Musa Island Airport", - ["Al_Ain_International_Airport"] = "Al Ain International Airport", - ["Al_Bateen_Airport"] = "Al-Bateen Airport", - ["Al_Dhafra_AB"] = "Al Dhafra AB", + ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", + ["Abu_Musa_Island_Airport"] = "Abu Musa Island", + ["Al_Ain_International_Airport"] = "Al Ain Intl", + ["Al_Bateen_Airport"] = "Al-Bateen", + ["Al_Dhafra_AB"] = "Al Dhafra AFB", ["Al_Maktoum_Intl"] = "Al Maktoum Intl", - ["Al_Minhad_AB"] = "Al Minhad AB", + ["Al_Minhad_AB"] = "Al Minhad AFB", ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Lengeh"] = "Bandar Lengeh", - ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield", + ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask", ["Dubai_Intl"] = "Dubai Intl", ["Fujairah_Intl"] = "Fujairah Intl", ["Havadarya"] = "Havadarya", - ["Jiroft_Airport"] = "Jiroft Airport", - ["Kerman_Airport"] = "Kerman Airport", + ["Jiroft_Airport"] = "Jiroft", + ["Kerman_Airport"] = "Kerman", ["Khasab"] = "Khasab", - ["Kish_International_Airport"] = "Kish International Airport", - ["Lar_Airbase"] = "Lar Airbase", - ["Lavan_Island_Airport"] = "Lavan Island Airport", - ["Liwa_Airbase"] = "Liwa Airbase", + ["Kish_International_Airport"] = "Kish Intl", + ["Lar_Airbase"] = "Lar", + ["Lavan_Island_Airport"] = "Lavan Island", + ["Liwa_Airbase"] = "Liwa AFB", ["Qeshm_Island"] = "Qeshm Island", - ["Ras_Al_Khaimah"] = "Ras Al Khaimah", - ["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel Airport", + ["Ras_Al_Khaimah"] = "Ras Al Khaimah Intl", + ["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel", ["Sharjah_Intl"] = "Sharjah Intl", - ["Shiraz_International_Airport"] = "Shiraz International Airport", + ["Shiraz_International_Airport"] = "Shiraz Intl", ["Sir_Abu_Nuayr"] = "Sir Abu Nuayr", ["Sirri_Island"] = "Sirri Island", ["Tunb_Island_AFB"] = "Tunb Island AFB", From 08ba001b45e90d6c8394aa64f0f25aed54f38b33 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Dec 2020 01:18:01 +0100 Subject: [PATCH 032/382] Update AI_Air_Engage.lua Issue #1403 --- Moose Development/Moose/AI/AI_Air_Engage.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 03a693ddf..c9f6bc562 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -519,7 +519,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - local AttackCount = AttackSetUnit:CountAlive()() + local AttackCount = AttackSetUnit:CountAlive() self:T({AttackCount = AttackCount}) if AttackCount > 0 then From fed1fb2839bdd965874d94cb15cc3da52e5905c8 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 20 Dec 2020 23:48:13 +0100 Subject: [PATCH 033/382] Update Artillery.lua - fixed bug in :NewFromCargo --- Moose Development/Moose/Functional/Artillery.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 8a3d8ebce..199519c34 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -693,7 +693,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.1.8" +ARTY.version="1.1.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1146,12 +1146,11 @@ end -- @param alias (Optional) Alias name the group will be calling itself when sending messages. Default is the group name. -- @return #ARTY ARTY object or nil if group does not exist or is not a ground or naval group. function ARTY:NewFromCargoGroup(cargogroup, alias) - BASE:F2({cargogroup=cargogroup, alias=alias}) if cargogroup then - BASE:T(self.lid..string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName())) + BASE:T(string.format("ARTY script version %s. Added CARGO group %s.", ARTY.version, cargogroup:GetName())) else - BASE:E(self.lid.."ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)") + BASE:E("ERROR: Requested ARTY CARGO GROUP does not exist! (Has to be a MOOSE CARGO(!) group.)") return nil end From 3d7234de1953e4557f629ac8cb1ff8e436800257 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 21 Dec 2020 10:02:41 +0100 Subject: [PATCH 034/382] Update to 0.2.6 Corrected error in dupe function naming, added doc about default values --- Moose Development/Moose/Functional/Mantis.lua | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 698b1c8fb..d2e7109d5 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -61,20 +61,35 @@ -- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. -- --- Start up your MANTIS +-- # 1. Start up your MANTIS -- -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- -- [optional] Use +-- -- * `MANTIS:SetEWRGrouping(radius)` -- * `MANTIS:SetEWRRange(radius)` -- * `MANTIS:SetSAMRadius(radius)` -- * `MANTIS:SetDetectInterval(interval)` --- * `MANTIS:SetAutoRelocate(hq, ewr)` +-- * `MANTIS:SetAutoRelocate(hq, ewr)` +-- -- to fine-tune your setup. -- -- `myredmantis:Start()` -- +-- # 2. Default settings +-- +-- By default, the following settings are active: +-- +-- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` +-- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` +-- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` +-- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` +-- * engagerange = 75 (percent) - SAMs will only fire if flights are inside of a 75% radius of their max firerange - `MANTIS:SetSAMRange(range)` +-- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` +-- * autorelocate = false - HQ and (mobile) EWR system will not relocate in random intervals between 30mins and 1 hour - `MANTIS:SetAutoRelocate(hq, ewr)` +-- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` +-- -- -- @field #MANTIS MANTIS = { @@ -115,7 +130,7 @@ do --@return #MANTIS self function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic) - -- TODO: Create some user functions for these + -- DONE: Create some user functions for these -- TODO: Make HQ useful -- TODO: Set SAMs to auto if EWR dies @@ -136,7 +151,7 @@ do self.verbose = false -- @field #string version - self.version="0.2.5" + self.version="0.2.6" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) -- Set the string id for output to DCS.log file. @@ -219,7 +234,7 @@ do --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range - function MANTIS:SetSAMRadius(range) + function MANTIS:SetSAMRange(range) local range = range or 75 if range < 0 or range > 100 then range = 75 From 478934ddd12fc2ba1ad67868b25c6c7b07fcb540 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 21 Dec 2020 10:31:06 +0100 Subject: [PATCH 035/382] Update Sead.lua to 0.2.2 -- corrected error when a single string instead of a table is given to SEAD:New() -- added a function to extend the watched set of SAMs --- Moose Development/Moose/Functional/Sead.lua | 29 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 62f71e137..f7e591897 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -15,7 +15,9 @@ -- -- === -- --- ### Authors: **FlightControl** +-- ### Authors: **FlightControl**, **applevangelist** +-- +-- Last Update: Dec 2020 -- -- === -- @@ -52,7 +54,7 @@ SEAD = { --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. --- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken. +-- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. -- @return SEAD -- @usage -- -- CCCP SEAD Defenses @@ -68,14 +70,33 @@ function SEAD:New( SEADGroupPrefixes ) self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix end else - self.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes + self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes end self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.0") + self:I("*** SEAD - Started Version 0.2.2") return self end +--- Update the active SEAD Set +-- @param #SEAD self +-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string +-- @return #SEAD self +function SEAD:UpdateSet( SEADGroupPrefixes ) + + self:F( SEADGroupPrefixes ) + + if type( SEADGroupPrefixes ) == 'table' then + for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do + self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix + end + else + self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes + end + + return self +end + --- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355 -- @param #SEAD self -- @param #number range Set the engagement range in percent, e.g. 50 From 4869ae4baa49d24bea35bba209b0242fa3689778 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 21 Dec 2020 22:52:51 +0100 Subject: [PATCH 036/382] cargo --- Moose Development/Moose/Cargo/Cargo.lua | 13 +++++++++---- Moose Development/Moose/Cargo/CargoGroup.lua | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 6a06e52f6..6051c25e4 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -1066,18 +1066,23 @@ do -- CARGO_REPRESENTABLE --- CARGO_REPRESENTABLE Constructor. -- @param #CARGO_REPRESENTABLE self - -- @param #string Type - -- @param #string Name - -- @param #number LoadRadius (optional) - -- @param #number NearRadius (optional) + -- @param Wrapper.Positionable#POSITIONABLE CargoObject The cargo object. + -- @param #string Type Type name + -- @param #string Name Name. + -- @param #number LoadRadius (optional) Radius in meters. + -- @param #number NearRadius (optional) Radius in meters when the cargo is loaded into the carrier. -- @return #CARGO_REPRESENTABLE function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius ) + + -- Inherit CARGO. local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE self:F( { Type, Name, LoadRadius, NearRadius } ) local Desc = CargoObject:GetDesc() self:I( { Desc = Desc } ) + local Weight = math.random( 80, 120 ) + if Desc then if Desc.typeName == "2B11 mortar" then Weight = 210 diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index b64d205c2..8b1d0c54c 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -56,6 +56,8 @@ do -- CARGO_GROUP -- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene. -- @return #CARGO_GROUP Cargo group object. function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) + + -- Inherit CAROG_REPORTABLE local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP self:F( { Type, Name, LoadRadius } ) From 9a19dd254e80b44fad7867c1a47d492a19ee37c1 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Dec 2020 15:19:29 +0100 Subject: [PATCH 037/382] Mantis 0.3.0 - added advanced mode options Mantis 0.3.0 - added advanced mode options --- Moose Development/Moose/Functional/Mantis.lua | 208 ++++++++++++++++-- 1 file changed, 192 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index d2e7109d5..bd7560952 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -31,6 +31,7 @@ -- @field @{Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET -- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group -- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET +-- @field @{Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode -- @field #string SEAD_Template_CC The ME name of the HQ object -- @field @{Wrapper.Group#GROUP} SEAD_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites @@ -44,6 +45,9 @@ -- @field #number detectinterval Interval in seconds for the target detection -- @field #number engagerange Firing engage range of the SAMs, see [https://wiki.hoggitworld.com/view/DCS_option_engagementRange] -- @field #boolean autorelocate Relocate HQ and EWR groups in random intervals. Note: You need to select units for this which are *actually mobile* +-- @field #boolean advanced Use advanced mode, will decrease reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an HQ object +-- @field #number adv_ratio Percentage to use for advanced mode, defaults to 100% +-- @field #number adv_state Advanced mode state tracker -- @extends @{Core.Base#BASE} @@ -80,7 +84,9 @@ -- # 2. Default settings -- -- By default, the following settings are active: --- +-- +-- * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM" +-- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be AWACS -- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` -- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` @@ -99,6 +105,7 @@ MANTIS = { SAM_Group = nil, EWR_Templates_Prefix = "", EWR_Group = nil, + Adv_EWR_Group = nil, SEAD_Template_CC = "", SEAD_CC = nil, SAM_Table = {}, @@ -111,6 +118,9 @@ MANTIS = { detectinterval = 30, engagerange = 75, autorelocate = false, + advanced = false, + adv_ratio = 100, + adv_state = 0, verbose = false } @@ -131,8 +141,9 @@ do function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic) -- DONE: Create some user functions for these - -- TODO: Make HQ useful - -- TODO: Set SAMs to auto if EWR dies + -- DONE: Make HQ useful + -- DONE: Set SAMs to auto if EWR dies + -- TODO: Refresh SAM table in dynamic mode self.name = name or "mymantis" self.SAM_Templates_Prefix = samprefix or "Red SAM" @@ -148,10 +159,14 @@ do self.engagerange = 75 self.autorelocate = false self.autorelocateunits = { HQ = false, EWR = false} + self.advanced = false + self.adv_ratio = 100 + self.adv_state = 0 self.verbose = false + self.Adv_EWR_Group = nil -- @field #string version - self.version="0.2.6" + self.version="0.3.0" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) -- Set the string id for output to DCS.log file. @@ -260,6 +275,22 @@ do return nil end end + + --- Function to set the HQ object for further use + -- @param #MANTIS self + -- @param Wrapper.GROUP#GROUP The HQ #GROUP object to be set as HQ + function MANTIS:SetCommandCenter(group) + local group = group or nil + if group ~= nil then + if type(group) == "string" then + self.SEAD_CC = GROUP:FindByName(group) + self.SEAD_Template_CC = group + else + self.SEAD_CC = group + self.SEAD_Template_CC = group:GetName() + end + end + end --- Function to set the detection interval -- @param #MANTIS self @@ -269,6 +300,108 @@ do self.detectinterval = interval end + --- Function to set Advanded Mode + -- @param #MANTIS self + -- @param #boolean onoff If true, will activate Advanced Mode + -- @param #number ratio [optional] Percentage to use for advanced mode, defaults to 100% + -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object + -- E.g. `mymantis:SetAdvancedMode(true, 90)` + function MANTIS:SetAdvancedMode(onoff, ratio) + self:F({onoff, ratio}) + local onoff = onoff or false + local ratio = ratio or 100 + if (type(self.SEAD_Template_CC) == "string") and onoff and self.dynamic then + self.adv_ratio = ratio + self.advanced = true + self.adv_state = 0 + self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() + env.info(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) + else + local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." + local m= MESSAGE:New(text,10,"MANTIS",true):ToAll() + BASE:E(text) + end + end + + --- [Internal] Function to check if HQ is alive + -- @param #MANTIS self + -- @return #boolean True if HQ is alive, else false + function MANTIS:_CheckHQState() + local text = self.lid.." Checking HQ State" + self:T(text) + local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(text) end + -- start check + if self.advanced then + local hq = self.SEAD_Template_CC + local hqgrp = GROUP:FindByName(hq) + if hqgrp then + if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive + env.info(self.lid.." HQ is alive!") + return true + else + env.info(self.lid.." HQ is dead!") + return false + end + end + end + end + + --- [Internal] Function to check if EWR is (at least partially) alive + -- @param #MANTIS self + -- @return #boolean True if EWR is alive, else false + function MANTIS:_CheckEWRState() + local text = self.lid.." Checking EWR State" + self:F(text) + local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(text) end + -- start check + if self.advanced then + local EWR_Group = self.Adv_EWR_Group + --local EWR_Set = EWR_Group.Set + local nalive = EWR_Group:CountAlive() + env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) + if nalive > 0 then + return true + else + return false + end + end + end + + --- [Internal] Function to determine state of the advanced mode + -- @param #MANTIS self + -- @return #number Newly calculated interval + -- @return #number Previous state for tracking 0, 1, or 2 + function MANTIS:_CheckAdvState() + local text = self.lid.." Checking Advanced State" + self:F(text) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(text) end + -- start check + local currstate = self.adv_state -- save curr state for comparison later + local EWR_State = self:_CheckEWRState() + local HQ_State = self:_CheckHQState() + -- set state + if EWR_State and HQ_State then -- both alive + self.adv_state = 0 --everything is fine + elseif EWR_State or HQ_State then -- one alive + self.adv_state = 1 --slow down level 1 + else -- none alive + self.adv_state = 2 --slow down level 2 + end + -- calculate new detectioninterval + local interval = self.detectinterval -- e.g. 30 + local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8 + ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 + local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 + local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) + self:F(text) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then env.info(text) end + return newinterval, currstate + end + --- Function to set autorelocation for HQ and EWR objects. Note: Units must be actually mobile in DCS! -- @param #MANTIS self -- @param #boolean hq If true, will relocate HQ object @@ -289,7 +422,7 @@ do function MANTIS:_RelocateGroups() self:T(self.lid.." Relocating Groups") local text = self.lid.." Relocating Groups" - local m= MESSAGE:New(text,15,"MANTIS",true):ToAllIf(self.debug) + local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) if self.verbose then env.info(text) end if self.autorelocate then -- relocate HQ @@ -297,7 +430,7 @@ do local _hqgrp = self.SEAD_CC self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" - local m= MESSAGE:New(text,15,"MANTIS"):ToAll() + local m= MESSAGE:New(text,10,"MANTIS"):ToAll() _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) end --relocate EWR @@ -310,7 +443,7 @@ do if _grp:IsGround() then self:T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() - local m= MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then env.info(text) end _grp:RelocateGroundRandomInRadius(20,500,true,true) end @@ -381,6 +514,7 @@ do -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() + -- TODO: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 self:F(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group @@ -399,8 +533,9 @@ do if group:IsGround() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() - local grpzone = ZONE_UNIT:New(grpname,group:GetUnit(1),5000) -- defense zone around each SAM site 5000 meters - table.insert( SAM_Tbl, {grpname, grpcoord, grpzone}) + --local grpzone = ZONE_UNIT:New(grpname,group:GetUnit(1),5000) -- defense zone around each SAM site 5000 meters + --table.insert( SAM_Tbl, {grpname, grpcoord, grpzone}) + table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) end end @@ -427,7 +562,7 @@ do local detset = detection:GetDetectedItemCoordinates() self:F("Check:", {detset}) -- switch SAMs on/off if (n)one of the detected groups is inside their reach - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates and i.3=zones of SAM sites + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates for _,_data in pairs (samset) do local samcoordinate = _data[2] local name = _data[1] @@ -439,7 +574,7 @@ do --samgroup:OptionROEWeaponFree() --samgroup:SetAIOn() local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then env.info(self.lid..text) end end --end alive else @@ -449,7 +584,7 @@ do --samgroup:OptionROEWeaponFree() --samgroup:SetAIOn() local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,15,"MANTIS"):ToAllIf(self.debug) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then env.info(self.lid..text) end end --end alive end --end check @@ -459,14 +594,52 @@ do local function relocate() self:_RelocateGroups() end - -- timer to run the system + -- check advanced state + local function checkadvstate() + if self.advanced then + local interval, oldstate = self:_CheckAdvState() + local newstate = self.adv_state + if newstate ~= oldstate then + -- deal with new state + if newstate == 2 then + -- switch alarm state RED + if self.MantisTimer.isrunning then + self.MantisTimer:Stop() + self.MantisTimer.isrunning = false + end -- stop timer + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local name = _data[1] + local samgroup = GROUP:FindByName(name) + if samgroup:IsAlive() then + samgroup:OptionAlarmStateRed() + end -- end alive + end -- end for loop + elseif newstate <= 1 then + -- change MantisTimer to slow down or speed up + if self.MantisTimer.isrunning then + self.MantisTimer:Stop() + self.MantisTimer.isrunning = false + end + self.MantisTimer = TIMER:New(check,self.Detection) + self.MantisTimer:Start(5,interval,nil) + self.MantisTimer.isrunning = true + end + end -- end newstate vs oldstate + end -- end advanced + end + -- timers to run the system local interval = self.detectinterval self.MantisTimer = TIMER:New(check,self.Detection) self.MantisTimer:Start(5,interval,nil) - -- relocate HQ and EWR + self.MantisTimer.isrunning = true + -- timer to relocate HQ and EWR local relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.MantisReloTimer = TIMER:New(relocate) self.MantisReloTimer:Start(relointerval,relointerval,nil) + -- timer for advanced state check + self.MantisAdvTimer = TIMER:New(checkadvstate) + self.MantisAdvTimer:Start(30,interval*5,nil) return self end @@ -474,12 +647,15 @@ do -- @param #MANTIS self -- @return #MANTIS self function MANTIS:Stop() - if self.MantisTimer then + if self.MantisTimer.isrunning then self.MantisTimer:Stop() end - if self.MantisReloTimer then + if self.autorelocate then self.MantisReloTimer:Stop() end + if self.advanced then + self.MantisAdvTimer:Stop() + end return self end From e08df3f9ce03d646ce6f69c436f6b99e05bbbf94 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 23 Dec 2020 16:09:49 +0100 Subject: [PATCH 038/382] Update Timer.lua --- Moose Development/Moose/Core/Timer.lua | 32 ++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 9b8a9200b..258996d68 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -11,7 +11,7 @@ -- -- ### Author: **funkyfranky** -- @module Core.Timer --- @image CORE_Timer.png +-- @image Core_Scheduler.JPG --- TIMER class. @@ -19,6 +19,7 @@ -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. -- @field #number tid Timer ID returned by the DCS API function. +-- @field #number uid Unique ID of the timer. -- @field #function func Timer function. -- @field #table para Parameters passed to the timer function. -- @field #number Tstart Relative start time in seconds. @@ -26,9 +27,10 @@ -- @field #number dT Time interval between function calls in seconds. -- @field #number ncalls Counter of function calls. -- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. +-- @field #boolean isrunning If `true`, timer is running. Else it was not started yet or was stopped. -- @extends Core.Base#BASE ---- *Better three hours too soon than a minute too late.* William Shakespeare +--- *Better three hours too soon than a minute too late.* - William Shakespeare -- -- === -- @@ -36,7 +38,7 @@ -- -- # The TIMER Concept -- --- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. +-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. -- -- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction). -- @@ -62,20 +64,20 @@ -- -- Note that -- --- * if *Tstart* is not specified (*nil*), the first function call happens immediately. +-- * if *Tstart* is not specified (*nil*), the first function call happens immediately, i.e. after one millisecond. -- * if *dT* is not specified (*nil*), the function is called only once. -- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below). -- -- For example, -- -- mytimer:Start(3) -- Will call the function once after 3 seconds. --- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty. +-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternity. -- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec. -- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway. -- -- ## Stopping the Timer -- --- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function +-- The timer can be stopped manually by the @{#TIMER.Stop}(*Delay*) function -- -- mytimer:Stop() -- @@ -110,7 +112,7 @@ _TIMERID=0 --- TIMER class version. -- @field #string version -TIMER.version="0.1.0" +TIMER.version="0.1.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -142,6 +144,9 @@ function TIMER:New(Function, ...) -- Number of function calls. self.ncalls=0 + -- Not running yet. + self.isrunning=false + -- Increase counter _TIMERID=_TIMERID+1 @@ -185,6 +190,9 @@ function TIMER:Start(Tstart, dT, Duration) -- Set log id. self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid) + -- Is now running. + self.isrunning=true + -- Debug info. self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop))) @@ -209,6 +217,9 @@ function TIMER:Stop(Delay) self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) timer.removeFunction(self.tid) + -- Not running any more. + self.isrunning=false + -- Remove DB entry. --_TIMERDB[self.uid]=nil @@ -228,6 +239,13 @@ function TIMER:SetMaxFunctionCalls(Nmax) return self end +--- Check if the timer has been started and was not stopped. +-- @param #TIMER self +-- @return #boolean If `true`, the timer is running. +function TIMER:IsRunning() + return self.isrunning +end + --- Call timer function. -- @param #TIMER self -- @param #number time DCS model time in seconds. From c32cec4c5b11bfe1b93c1d68f26d4f948d7ea4b4 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 27 Dec 2020 16:24:13 +0100 Subject: [PATCH 039/382] Mantis.lua 0.3.5 Added option to use Awacs separate from EWR network with own detection range Extended documentation Added refresher for SAM table --- Moose Development/Moose/Functional/Mantis.lua | 251 +++++++++++++++--- 1 file changed, 207 insertions(+), 44 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bd7560952..c97068865 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -32,11 +32,12 @@ -- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group -- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET -- @field @{Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode --- @field #string SEAD_Template_CC The ME name of the HQ object --- @field @{Wrapper.Group#GROUP} SEAD_CC The #GROUP object of the HQ +-- @field #string HQ_Template_CC The ME name of the HQ object +-- @field @{Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging --- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object +-- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR +-- @field @{Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS -- @field #boolean debug Switch on extra messages -- @field #boolean verbose Switch on extra logging -- @field #number checkradius Radius of the SAM sites @@ -48,6 +49,8 @@ -- @field #boolean advanced Use advanced mode, will decrease reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an HQ object -- @field #number adv_ratio Percentage to use for advanced mode, defaults to 100% -- @field #number adv_state Advanced mode state tracker +-- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream +-- @field #number awacsrange Detection range of an optional Awacs unit -- @extends @{Core.Base#BASE} @@ -65,7 +68,33 @@ -- Set up your EWR system in the mission editor. Name the groups with common prefix like "Red EWR". Can be e.g. AWACS or a combination of AWACS and Search Radars like e.g. EWR 1L13 etc. -- [optional] Set up your HQ. Can be any group, e.g. a command vehicle. -- --- # 1. Start up your MANTIS +-- # 1. Basic tactical considerations when setting up your SAM sites +-- +-- ## 1.1 Radar systems and AWACS +-- +-- Typically, your setup should consist of EWR (early warning) radars to detect and track targets, accompanied by AWACS if your scenario forsees that. Ensure that your EWR radars have a good coverage of the area you want to track. +-- **Location** is of highest importantance here. Whilst AWACS in DCS has almost the "all seeing eye", EWR don't have that. Choose your location wisely, against a mountain backdrop or inside a valley even the best EWR system +-- doesn't work well. Prefer higher-up locations with a good view; use F7 in-game to check where you actually placed your EWR and have a look around. Apart from the obvious choice, do also consider other radar units +-- for this role, most have "SR" (search radar) or "STR" (search and track radar) in their names, use the encyclopedia to see what they actually do. +-- +-- ## 1.2 SAM sites +-- +-- Typically your SAM should cover all attack ranges. The closer the enemy gets, the more systems you will need to deploy to defend your location. Use a combination of long-range systems like the SA-10/11, midrange like SA-6 and short-range like +-- SA-2 for defense (Patriot, Hawk, Gepard, Blindfire for the blue side). For close-up defense and defense against HARMs or low-flying aircraft, helicopters it is also advisable to deploy SA-15 TOR systems, Shilka, Strela and Tunguska units, as well as manpads (Think Gepard, Avenger, Chaparral, +-- Linebacker, Roland systems for the blue side). If possible, overlap ranges for mutual coverage. +-- +-- ## 1.3 Typical problems +-- +-- Often times, people complain because the detection cannot "see" oncoming targets and/or Mantis switches on too late. Three typial problems here are +-- +-- * bad placement of radar units, +-- * overestimation how far units can "see" and +-- * not taking into account that a SAM site will take (e.g for a SA-6) 30-40 seconds between switching to RED, acquiring the target and firing. +-- +-- An attacker doing 350knots will cover ca 180meters/second or thus more than 6km until the SA-6 fires. Use triggers zones and the ruler in the missione editor to understand distances and zones. Take into account that the ranges given by the circles +-- in the mission editor are absolute maximum ranges; in-game this is rather 50-75% of that depending on the system. Fiddle with placement and options to see what works best for your scenario, and remember **everything in here is in meters**. +-- +-- # 2. Start up your MANTIS with a basic setting -- -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- @@ -80,13 +109,17 @@ -- to fine-tune your setup. -- -- `myredmantis:Start()` +-- +-- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: +-- +-- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- --- # 2. Default settings +-- # 3. Default settings -- -- By default, the following settings are active: -- -- * SAM_Templates_Prefix = "Red SAM" - SAM site group names in the mission editor begin with "Red SAM" --- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be AWACS +-- * EWR_Templates_Prefix = "Red EWR" - EWR group names in the mission editor begin with "Red EWR" - can also be combined with an AWACS unit -- * checkradius = 25000 (meters) - SAMs will engage enemy flights, if they are within a 25km around each SAM site - `MANTIS:SetSAMRadius(radius)` -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` -- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` @@ -96,6 +129,11 @@ -- * autorelocate = false - HQ and (mobile) EWR system will not relocate in random intervals between 30mins and 1 hour - `MANTIS:SetAutoRelocate(hq, ewr)` -- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` -- +-- # 4. Advanced Mode +-- +-- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. +-- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- Use this option if you want to make use of or allow advanced SEAD tactics. -- -- @field #MANTIS MANTIS = { @@ -106,11 +144,12 @@ MANTIS = { EWR_Templates_Prefix = "", EWR_Group = nil, Adv_EWR_Group = nil, - SEAD_Template_CC = "", - SEAD_CC = nil, + HQ_Template_CC = "", + HQ_CC = nil, SAM_Table = {}, lid = "", Detection = nil, + AWACS_Detection = nil, debug = false, checkradius = 25000, grouping = 5000, @@ -121,7 +160,10 @@ MANTIS = { advanced = false, adv_ratio = 100, adv_state = 0, - verbose = false + AWACS_Prefix = "", + advAwacs = false, + verbose = false, + awacsrange = 250000 } ----------------------------------------------------------------------- @@ -133,22 +175,24 @@ do --@param #MANTIS self --@param #string name Name of this MANTIS for reporting --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..." - --@param #string ewrprefix Prefixes for the EWR and AWACS groups from the ME, e.g. all groups starting with "Red EWR..." + --@param #string ewrprefix Prefixes for the EWR groups from the ME, e.g. all groups starting with "Red EWR..." --@param #string hq Group name of your HQ (optional) --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" - --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) + --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) + --@param #string awacs Group name of your Awacs (optional) --@return #MANTIS self - function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic) + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs) -- DONE: Create some user functions for these -- DONE: Make HQ useful -- DONE: Set SAMs to auto if EWR dies - -- TODO: Refresh SAM table in dynamic mode + -- DONE: Refresh SAM table in dynamic mode + -- DONE: Treat Awacs separately, since they might be >80km off site self.name = name or "mymantis" self.SAM_Templates_Prefix = samprefix or "Red SAM" self.EWR_Templates_Prefix = ewrprefix or "Red EWR" - self.SEAD_Template_CC = hq or nil + self.HQ_Template_CC = hq or nil self.Coalition = coaltion or "red" self.SAM_Table = {} self.dynamic = dynamic or false @@ -164,9 +208,16 @@ do self.adv_state = 0 self.verbose = false self.Adv_EWR_Group = nil + self.AWACS_Prefix = awacs or nil + self.awacsrange = 250000 --TODO: 250km, User Function to change + if type(awacs) == "string" then + self.advAwacs = true + else + self.advAwacs = false + end -- @field #string version - self.version="0.3.0" + self.version="0.3.5" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) -- Set the string id for output to DCS.log file. @@ -193,9 +244,8 @@ do end -- set up CC - if self.SEAD_Template_CC then - self.SEAD_CC = GROUP:FindByName(self.SEAD_Template_CC) - --self.SEAD_CC = COMMANDCENTER:New(GROUP:FindByName(self.SEAD_Template_CC),self.SEAD_Template_CC) + if self.HQ_Template_CC then + self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end -- Inherit everything from BASE class. local self = BASE:Inherit(self, BASE:New()) -- #MANTIS @@ -269,13 +319,25 @@ do -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist function MANTIS:GetCommandCenter() - if self.SEAD_CC then - return self.SEAD_CC + if self.HQ_CC then + return self.HQ_CC else return nil end end + --- Function to set separate AWACS detection instance + -- @param #MANTIS self + -- @param #string prefix Name of the AWACS group in the mission editor + function MANTIS:SetAwacs(prefix) + if prefix ~= nil then + if type(prefix) == "string" then + self.AWACS_Prefix = prefix + self.advAwacs = true + end + end + end + --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP The HQ #GROUP object to be set as HQ @@ -283,11 +345,11 @@ do local group = group or nil if group ~= nil then if type(group) == "string" then - self.SEAD_CC = GROUP:FindByName(group) - self.SEAD_Template_CC = group + self.HQ_CC = GROUP:FindByName(group) + self.HQ_Template_CC = group else - self.SEAD_CC = group - self.SEAD_Template_CC = group:GetName() + self.HQ_CC = group + self.HQ_Template_CC = group:GetName() end end end @@ -304,13 +366,13 @@ do -- @param #MANTIS self -- @param #boolean onoff If true, will activate Advanced Mode -- @param #number ratio [optional] Percentage to use for advanced mode, defaults to 100% - -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object + -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) self:F({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 - if (type(self.SEAD_Template_CC) == "string") and onoff and self.dynamic then + if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then self.adv_ratio = ratio self.advanced = true self.adv_state = 0 @@ -333,7 +395,7 @@ do if self.verbose then env.info(text) end -- start check if self.advanced then - local hq = self.SEAD_Template_CC + local hq = self.HQ_Template_CC local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive @@ -360,6 +422,14 @@ do local EWR_Group = self.Adv_EWR_Group --local EWR_Set = EWR_Group.Set local nalive = EWR_Group:CountAlive() + if self.advAwacs then + local awacs = GROUP:FindByName(self.AWACS_Prefix) + if awacs ~= nil then + if awacs:IsAlive() then + nalive = nalive+1 + end + end + end env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true @@ -426,8 +496,8 @@ do if self.verbose then env.info(text) end if self.autorelocate then -- relocate HQ - if self.autorelocateunits.HQ and self.SEAD_CC then --only relocate if HQ exists - local _hqgrp = self.SEAD_CC + if self.autorelocateunits.HQ and self.HQ_CC then --only relocate if HQ exists + local _hqgrp = self.HQ_CC self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" local m= MESSAGE:New(text,10,"MANTIS"):ToAll() @@ -510,6 +580,38 @@ do return _MANTISdetection end + --- Function to start the detection via AWACS if defined as separate + -- @param #MANTIS self + -- @return Functional.Detection #DETECTION_AREAS The running detection set + function MANTIS:StartAwacsDetection() + self:F(self.lid.."Starting Awacs Detection") + + -- start detection + local group = self.AWACS_Prefix + local groupset = SET_GROUP:New():FilterPrefixes(group):FilterCoalitions(self.Coalition):FilterStart() + local grouping = self.grouping or 5000 + --local acceptrange = self.acceptrange or 80000 + local interval = self.detectinterval or 60 + + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object + _MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones + _MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + _MANTISAwacs:SetAcceptRange(self.awacsrange) --250km + _MANTISAwacs:SetRefreshTimeInterval(interval) + _MANTISAwacs:Start() + + function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) + --BASE:I( { From, Event, To, DetectedItem }) + local debug = false + if DetectedItem.IsDetected and debug then + local Coordinate = DetectedItem.Coordinate -- Core.Point#COORDINATE + local text = "Awacs Detection at "..Coordinate:ToStringLLDMS() + local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + end + end + return _MANTISAwacs + end + --- Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self @@ -524,24 +626,53 @@ do local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do - --for i=1,#SAM_Grps do local group = _group - group:OptionAlarmStateGreen() - --group:OptionROEHoldFire() - --group:SetAIOn() + group:OptionAlarmStateGreen() -- AI off + group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range + if group:IsGround() then + local grpname = group:GetName() + local grpcoord = group:GetCoordinate() + table.insert( SAM_Tbl, {grpname, grpcoord}) + table.insert( SEAD_Grps, grpname ) + end + end + self.SAM_Table = SAM_Tbl + -- make SAMs evasive + local mysead = SEAD:New( SEAD_Grps ) + mysead:SetEngagementRange(engagerange) + self.mysead = mysead + return self + end + + --- (Internal) Function to update SAM table and SEAD state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_RefreshSAMTable() + self:F(self.lid.."Setting SAM Start States") + -- Requires SEAD 0.2.2 or better + -- get SAM Group + local SAM_SET = self.SAM_Group + local SAM_Grps = SAM_SET.Set --table of objects + local SAM_Tbl = {} -- table of SAM defense zones + local SEAD_Grps = {} -- table of SAM names to make evasive + local engagerange = self.engagerange -- firing range in % of max + --cycle through groups and set alarm state etc + for _i,_group in pairs (SAM_Grps) do + local group = _group group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range if group:IsGround() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() - --local grpzone = ZONE_UNIT:New(grpname,group:GetUnit(1),5000) -- defense zone around each SAM site 5000 meters - --table.insert( SAM_Tbl, {grpname, grpcoord, grpzone}) table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) end end self.SAM_Table = SAM_Tbl -- make SAMs evasive - SEAD:New( SEAD_Grps ) + if self.mysead ~= nil then + local mysead = self.mysead + mysead:UpdateSet( SEAD_Grps ) + end return self end @@ -556,11 +687,19 @@ do self:F(self.lid.."Starting MANTIS") self:SetSAMStartState() self.Detection = self:StartDetection() - + if self.advAwacs then + self.AWACS_Detection = self:StartAwacsDetection() + end + -- detection function local function check(detection) --get detected set local detset = detection:GetDetectedItemCoordinates() self:F("Check:", {detset}) + -- randomly update SAM Table + local rand = math.random(1,100) + if rand > 65 then -- 1/3 of cases + self:_RefreshSAMTable() + end -- switch SAMs on/off if (n)one of the detected groups is inside their reach local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates for _,_data in pairs (samset) do @@ -596,7 +735,6 @@ do end -- check advanced state local function checkadvstate() - if self.advanced then local interval, oldstate = self:_CheckAdvState() local newstate = self.adv_state if newstate ~= oldstate then @@ -606,6 +744,10 @@ do if self.MantisTimer.isrunning then self.MantisTimer:Stop() self.MantisTimer.isrunning = false + end -- stop Awacs timer + if self.MantisATimer.isrunning then + self.MantisATimer:Stop() + self.MantisATimer.isrunning = false end -- stop timer local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates for _,_data in pairs (samset) do @@ -621,25 +763,43 @@ do self.MantisTimer:Stop() self.MantisTimer.isrunning = false end + if self.MantisATimer.isrunning then + self.MantisATimer:Stop() + self.MantisATimer.isrunning = false + end self.MantisTimer = TIMER:New(check,self.Detection) self.MantisTimer:Start(5,interval,nil) self.MantisTimer.isrunning = true + if self.advAwacs then + self.MantisATimer = TIMER:New(check,self.AWACS_Detection) + self.MantisATimer:Start(15,interval,nil) + self.MantisATimer.isrunning = true + end end end -- end newstate vs oldstate - end -- end advanced end -- timers to run the system local interval = self.detectinterval self.MantisTimer = TIMER:New(check,self.Detection) self.MantisTimer:Start(5,interval,nil) self.MantisTimer.isrunning = true + -- Awacs timer + if self.advAwacs then + self.MantisATimer = TIMER:New(check,self.AWACS_Detection) + self.MantisATimer:Start(15,interval,nil) + self.MantisATimer.isrunning = true + end -- timer to relocate HQ and EWR - local relointerval = math.random(1800,3600) -- random between 30 and 60 mins - self.MantisReloTimer = TIMER:New(relocate) - self.MantisReloTimer:Start(relointerval,relointerval,nil) + if self.autorelocate then + local relointerval = math.random(1800,3600) -- random between 30 and 60 mins + self.MantisReloTimer = TIMER:New(relocate) + self.MantisReloTimer:Start(relointerval,relointerval,nil) + end -- timer for advanced state check - self.MantisAdvTimer = TIMER:New(checkadvstate) - self.MantisAdvTimer:Start(30,interval*5,nil) + if self.advanced then + self.MantisAdvTimer = TIMER:New(checkadvstate) + self.MantisAdvTimer:Start(30,interval*5,nil) + end return self end @@ -650,6 +810,9 @@ do if self.MantisTimer.isrunning then self.MantisTimer:Stop() end + if self.MantisATimer.isrunning then + self.MantisATimer:Stop() + end if self.autorelocate then self.MantisReloTimer:Stop() end From 01a5b523da116a9d1098c663e8042a1264eff2cf Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 27 Dec 2020 17:59:39 +0100 Subject: [PATCH 040/382] Arty v1.2.0 - fixed cargo issue --- Moose Development/Moose/Cargo/CargoGroup.lua | 7 ++- .../Moose/Functional/Artillery.lua | 54 +++++++++++-------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 8b1d0c54c..2d356f270 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -483,7 +483,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 + -- @param Core.Point#POINT_VEC2 ToPointVec2 function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) --self:F( { From, Event, To, ToPointVec2 } ) @@ -493,7 +493,10 @@ do -- CARGO_GROUP self.CargoSet:ForEach( function( Cargo ) --Cargo:UnLoad( ToPointVec2 ) - local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) + local RandomVec2=nil + if ToPointVec2 then + RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) + end Cargo:UnBoard( RandomVec2 ) end ) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 199519c34..8f7192641 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -693,7 +693,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.1.9" +ARTY.version="1.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2765,38 +2765,32 @@ function ARTY:onafterStatus(Controllable, From, Event, To) self:_EventFromTo("onafterStatus", Event, From, To) -- Get ammo. - local ntot, nshells, nrockets, nmissiles=self:GetAmmo() + local nammo, nshells, nrockets, nmissiles=self:GetAmmo() + + -- We have a cargo group ==> check if group was loaded into a carrier. + if self.iscargo and self.cargogroup then + if self.cargogroup:IsLoaded() and not self:is("InTransit") then + -- Group is now InTransit state. Current target is canceled. + self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias)) + self:Loaded() + elseif self.cargogroup:IsUnLoaded() then + -- Group has been unloaded and is combat ready again. + self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias)) + self:UnLoaded() + end + end -- FSM state. local fsmstate=self:GetState() - self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, ntot, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles)) + self:T(self.lid..string.format("Status %s, Ammo total=%d: shells=%d [smoke=%d, illu=%d, nukes=%d*%.3f kT], rockets=%d, missiles=%d", fsmstate, nammo, nshells, self.Nsmoke, self.Nillu, self.Nukes, self.nukewarhead/1000000, nrockets, nmissiles)) if self.Controllable and self.Controllable:IsAlive() then - -- We have a cargo group ==> check if group was loaded into a carrier. - if self.cargogroup then - if self.cargogroup:IsLoaded() and not self:is("InTransit") then - -- Group is now InTransit state. Current target is canceled. - self:T(self.lid..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias)) - self:Loaded() - elseif self.cargogroup:IsUnLoaded() then - -- Group has been unloaded and is combat ready again. - self:T(self.lid..string.format("Group %s has been unloaded from the carrier.", self.alias)) - self:UnLoaded() - end - end - -- Debug current status info. if self.Debug then self:_StatusReport() end - -- Group is being transported as cargo ==> skip everything and check again in 5 seconds. - if self:is("InTransit") then - self:__Status(-5) - return - end - -- Group on the move. if self:is("Moving") then self:T2(self.lid..string.format("%s: Moving", Controllable:GetName())) @@ -2883,7 +2877,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) end -- Get ammo. - local nammo, nshells, nrockets, nmissiles=self:GetAmmo() + --local nammo, nshells, nrockets, nmissiles=self:GetAmmo() -- Check if we have a target in the queue for which weapons are still available. local gotsome=false @@ -2913,9 +2907,23 @@ function ARTY:onafterStatus(Controllable, From, Event, To) -- Call status again in ~10 sec. self:__Status(self.StatusInterval) + elseif self.iscargo then + + -- We have a cargo group ==> check if group was loaded into a carrier. + if self.cargogroup and self.cargogroup:IsAlive() then + + -- Group is being transported as cargo ==> skip everything and check again in 5 seconds. + if self:is("InTransit") then + self:__Status(-5) + end + + end + else self:E(self.lid..string.format("Arty group %s is not alive!", self.groupname)) end + + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 87f4ebee64f0373f242ce787b065af45a9960164 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 01:10:40 +0100 Subject: [PATCH 041/382] Clients --- Moose Development/Moose/Core/Base.lua | 31 +++++++-- Moose Development/Moose/Core/Database.lua | 71 ++++++++++----------- Moose Development/Moose/Core/Event.lua | 22 +++++++ Moose Development/Moose/Wrapper/Airbase.lua | 40 ++++++------ Moose Development/Moose/Wrapper/Client.lua | 9 ++- 5 files changed, 106 insertions(+), 67 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 0262e2759..83c78c7ce 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -765,31 +765,48 @@ function BASE:CreateEventTakeoff( EventTime, Initiator ) world.onEvent( Event ) end + --- Creation of a S_EVENT_PLAYER_ENTER_AIRCRAFT event. + -- @param #BASE self + -- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered. + function BASE:CreateEventPlayerEnterAircraft( PlayerUnit ) + self:F( { PlayerUnit } ) + + local Event = { + id = EVENTS.PlayerEnterAircraft, + time = timer.getTime(), + initiator = PlayerUnit:GetDCSObject() + } + + world.onEvent(Event) + end + -- TODO: Complete DCS#Event structure. --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self -- @param DCS#Event event function BASE:onEvent(event) - --self:F( { BaseEventCodes[event.id], event } ) if self then - for EventID, EventObject in pairs( self.Events ) do + + for EventID, EventObject in pairs(self.Events) do if EventObject.EventEnabled then - --env.info( 'onEvent Table EventObject.Self = ' .. tostring(EventObject.Self) ) - --env.info( 'onEvent event.id = ' .. tostring(event.id) ) - --env.info( 'onEvent EventObject.Event = ' .. tostring(EventObject.Event) ) + if event.id == EventObject.Event then + if self == EventObject.Self then + if event.initiator and event.initiator:isExist() then event.IniUnitName = event.initiator:getName() end + if event.target and event.target:isExist() then event.TgtUnitName = event.target:getName() end - --self:T( { BaseEventCodes[event.id], event } ) - --EventObject.EventFunction( self, event ) + end + end + end end end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 6bd87bdd8..f633848ac 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -140,37 +140,6 @@ function DATABASE:New() self.UNITS_Position = 0 - --- @param #DATABASE self - local function CheckPlayers( self ) - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )} - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - --self:E( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - if UnitData and UnitData:isExist() then - - local UnitName = UnitData:getName() - local PlayerName = UnitData:getPlayerName() - local PlayerUnit = UNIT:Find( UnitData ) - --self:T( { "UnitData:", UnitData, UnitName, PlayerName, PlayerUnit } ) - - if PlayerName and PlayerName ~= "" then - if self.PLAYERS[PlayerName] == nil or self.PLAYERS[PlayerName] ~= UnitName then - --self:E( { "Add player for unit:", UnitName, PlayerName } ) - self:AddPlayer( UnitName, PlayerName ) - --_EVENTDISPATCHER:CreateEventPlayerEnterUnit( PlayerUnit ) - local Settings = SETTINGS:Set( PlayerName ) - Settings:SetPlayerMenu( PlayerUnit ) - end - end - end - end - end - end - - --self:E( "Scheduling" ) - --PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 ) - return self end @@ -224,8 +193,7 @@ end --- Deletes a Static from the DATABASE based on the Static Name. -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) - - --self.STATICS[DCSStaticName] = nil + self.STATICS[DCSStaticName] = nil end --- Finds a STATIC based on the StaticName. @@ -867,7 +835,7 @@ end function DATABASE:_RegisterClients() for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:T( { "Register Client:", ClientName } ) + self:I(string.format("Register Client %s", tostring(ClientName))) self:AddClient( ClientName ) end @@ -877,15 +845,15 @@ end --- @param #DATABASE self function DATABASE:_RegisterStatics() - local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } - self:I( { Statics = CoalitionsData } ) + local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - self:T( { "Register Static:", DCSStaticName } ) + self:I( { "Register Static:", DCSStaticName } ) self:AddStatic( DCSStaticName ) else self:E( { "Static does not exist: ", DCSStatic } ) @@ -911,9 +879,24 @@ function DATABASE:_RegisterAirbases() -- Add and register airbase. local airbase=self:AddAirbase( DCSAirbaseName ) + + -- Unique ID. + local airbaseUID=airbase:GetID(true) -- Debug output. - self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) + local text=string.format("Register Airbase: %s (ID=%d UID=%d), category=%s, parking=%d [", tostring(DCSAirbaseName), airbaseID, airbaseUID, AIRBASE.CategoryName[airbase.category], airbase.NparkingTotal) + for _,terminalType in pairs(AIRBASE.TerminalType) do + if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then + text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) + end + end + text=text.."]" + self:I(text) + + -- Check for DCS bug IDs. + if airbaseID~=airbase:GetID() then + --self:E("WARNING: :getID does NOT match :GetID!") + end end @@ -945,9 +928,13 @@ function DATABASE:_EventOnBirth( Event ) end end if Event.IniObjectCategory == 1 then + Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) + + -- Get player name. local PlayerName = Event.IniUnit:GetPlayerName() + if PlayerName then self:I( { "Player Joined:", PlayerName } ) self:AddClient( Event.IniDCSUnitName ) @@ -956,10 +943,16 @@ function DATABASE:_EventOnBirth( Event ) end local Settings = SETTINGS:Set( PlayerName ) Settings:SetPlayerMenu( Event.IniUnit ) - --MENU_INDEX:Refresh( Event.IniGroup ) end + + _DATABASE:CreateEventBirth(-EventTime,Initiator,IniUnitName,place,subplace) + end + end + + + end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 1f0b99b0a..a4a15ce44 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -192,6 +192,7 @@ world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003 world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004 world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005 world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006 +world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT = world.event.S_EVENT_MAX + 1007 --- The different types of events supported by MOOSE. @@ -233,6 +234,7 @@ EVENTS = { NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL, DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL, RemoveUnit = world.event.S_EVENT_REMOVE_UNIT, + PlayerEnterAircraft = world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, -- Added with DCS 2.5.6 DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier Kill = world.event.S_EVENT_KILL or -1, @@ -489,6 +491,11 @@ local _EVENTMETA = { Event = "OnEventRemoveUnit", Text = "S_EVENT_REMOVE_UNIT" }, + [EVENTS.PlayerEnterAircraft] = { + Order = 1, + Event = "OnEventPlayerEnterAircraft", + Text = "S_EVENT_PLAYER_ENTER_AIRCRAFT" + }, -- Added with DCS 2.5.6 [EVENTS.DetailedFailure] = { Order = 1, @@ -914,6 +921,21 @@ do -- Event Creation world.onEvent( Event ) end + + --- Creation of a S_EVENT_PLAYER_ENTER_AIRCRAFT event. + -- @param #EVENT self + -- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered. + function EVENT:CreateEventPlayerEnterAircraft( PlayerUnit ) + self:F( { PlayerUnit } ) + + local Event = { + id = EVENTS.PlayerEnterAircraft, + time = timer.getTime(), + initiator = PlayerUnit:GetDCSObject() + } + + world.onEvent( Event ) + end end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 74097fd5c..078d8069f 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -829,25 +829,27 @@ function AIRBASE:_InitParkingSpots() -- Put coordinates of parking spots into table. for _,spot in pairs(parkingdata) do - -- New parking spot. - local park={} --#AIRBASE.ParkingSpot - park.Vec3=spot.vTerminalPos - park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) - park.DistToRwy=spot.fDistToRW - park.Free=nil - park.TerminalID=spot.Term_Index - park.TerminalID0=spot.Term_Index_0 - park.TerminalType=spot.Term_Type - park.TOAC=spot.TO_AC - - for _,terminalType in pairs(AIRBASE.TerminalType) do - if self._CheckTerminalType(terminalType, park.TerminalType) then - self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 - end - end - - self.parkingByID[park.TerminalID]=park - table.insert(self.parking, park) + -- New parking spot. + local park={} --#AIRBASE.ParkingSpot + park.Vec3=spot.vTerminalPos + park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) + park.DistToRwy=spot.fDistToRW + park.Free=nil + park.TerminalID=spot.Term_Index + park.TerminalID0=spot.Term_Index_0 + park.TerminalType=spot.Term_Type + park.TOAC=spot.TO_AC + + self.NparkingTotal=self.NparkingTotal+1 + + for _,terminalType in pairs(AIRBASE.TerminalType) do + if self._CheckTerminalType(terminalType, park.TerminalType) then + self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 + end + end + + self.parkingByID[park.TerminalID]=park + table.insert(self.parking, park) end return self diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 4e6cce0de..64e42950d 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -117,6 +117,8 @@ end -- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) -- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) function CLIENT:FindByName( ClientName, ClientBriefing, Error ) + + -- Client local ClientFound = _DATABASE:FindClient( ClientName ) if ClientFound then @@ -132,6 +134,10 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error ) end end +--- Transport defines that the Client is a Transport. Transports show cargo. +-- @param #CLIENT self +-- @param #string ClientName Name of the client unit. +-- @return #CLIENT function CLIENT:Register( ClientName ) local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT @@ -204,8 +210,6 @@ function CLIENT:ShowMissionBriefing( MissionBriefing ) return self end - - --- Resets a CLIENT. -- @param #CLIENT self -- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. @@ -291,6 +295,7 @@ function CLIENT:GetDCSGroup() local ClientUnit = Unit.getByName( self.ClientName ) local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do self:T3( { "CoalitionData:", CoalitionData } ) for UnitId, UnitData in pairs( CoalitionData ) do From f56457bc286a8b373746197536bc2e90028cddf6 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 11:22:47 +0100 Subject: [PATCH 042/382] Update Intelligence.lua - added reject zones --- Moose Development/Moose/Ops/Intelligence.lua | 80 +++++++++++++++----- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index ed82c4fa8..edff50801 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -14,7 +14,6 @@ --- INTEL class. -- @type INTEL -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. @@ -47,7 +46,6 @@ -- @field #INTEL INTEL = { ClassName = "INTEL", - Debug = nil, verbose = 2, lid = nil, alias = nil, @@ -91,19 +89,19 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.0.3" +INTEL.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- DONE: Accept zones. --- TODO: Reject zones. -- TODO: Filter detection methods. +-- TODO: process detected set asynchroniously for better performance. +-- DONE: Accept zones. +-- DONE: Reject zones. -- NOGO: SetAttributeZone --> return groups of generalized attributes in a zone. -- DONE: Loose units only if they remain undetected for a given time interval. We want to avoid fast oscillation between detected/lost states. Maybe 1-5 min would be a good time interval?! -- DONE: Combine units to groups for all, new and lost. --- TODO: process detected set asynchroniously for better performance. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -181,6 +179,7 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- Defaults self:SetForgetTime() self:SetAcceptZones() + self:SetRejectZones() ------------------------ --- Pseudo Functions --- @@ -212,15 +211,6 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- @param #INTEL self -- @param #number delay Delay in seconds. - - -- Debug trace. - if false then - self.Debug=true - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end - return self end @@ -230,14 +220,14 @@ end --- Set accept zones. Only contacts detected in this/these zone(s) are considered. -- @param #INTEL self --- @param Core.Set#SET_ZONE AcceptZoneSet Set of accept zones +-- @param Core.Set#SET_ZONE AcceptZoneSet Set of accept zones. -- @return #INTEL self function INTEL:SetAcceptZones(AcceptZoneSet) self.acceptzoneset=AcceptZoneSet or SET_ZONE:New() return self end ---- Set accept zones. Only contacts detected in this zone are considered. +--- Add an accept zone. Only contacts detected in this zone are considered. -- @param #INTEL self -- @param Core.Zone#ZONE AcceptZone Add a zone to the accept zone set. -- @return #INTEL self @@ -246,6 +236,44 @@ function INTEL:AddAcceptZone(AcceptZone) return self end +--- Remove an accept zone from the accept zone set. +-- @param #INTEL self +-- @param Core.Zone#ZONE AcceptZone Remove a zone from the accept zone set. +-- @return #INTEL self +function INTEL:RemoveAcceptZone(AcceptZone) + self.acceptzoneset:Remove(AcceptZone:GetName(), true) + return self +end + +--- Set reject zones. Contacts detected in this/these zone(s) are rejected and not reported by the detection. +-- Note that reject zones overrule accept zones, i.e. if a unit is inside and accept zone and inside a reject zone, it is rejected. +-- @param #INTEL self +-- @param Core.Set#SET_ZONE RejectZoneSet Set of reject zone(s). +-- @return #INTEL self +function INTEL:SetRejectZones(RejectZoneSet) + self.rejectzoneset=RejectZoneSet or SET_ZONE:New() + return self +end + +--- Add a reject zone. Contacts detected in this zone are rejected and not reported by the detection. +-- Note that reject zones overrule accept zones, i.e. if a unit is inside and accept zone and inside a reject zone, it is rejected. +-- @param #INTEL self +-- @param Core.Zone#ZONE RejectZone Add a zone to the reject zone set. +-- @return #INTEL self +function INTEL:AddRejectZone(RejectZone) + self.rejectzoneset:AddZone(RejectZone) + return self +end + +--- Remove a reject zone from the reject zone set. +-- @param #INTEL self +-- @param Core.Zone#ZONE RejectZone Remove a zone from the reject zone set. +-- @return #INTEL self +function INTEL:RemoveRejectZone(RejectZone) + self.rejectzoneset:Remove(RejectZone:GetName(), true) + return self +end + --- Set forget contacts time interval. -- Previously known contacts that are not detected any more, are "lost" after this time. -- This avoids fast oscillations between a contact being detected and undetected. @@ -411,7 +439,6 @@ function INTEL:UpdateIntel() end end - -- TODO: Filter units from reject zones. -- TODO: Filter detection methods? local remove={} for unitname,_unit in pairs(DetectedUnits) do @@ -433,6 +460,23 @@ function INTEL:UpdateIntel() table.insert(remove, unitname) end end + + -- Check if unit is in any of the reject zones. + if self.rejectzoneset:Count()>0 then + local inzone=false + for _,_zone in pairs(self.rejectzoneset.Set) do + local zone=_zone --Core.Zone#ZONE + if unit:IsInZone(zone) then + inzone=true + break + end + end + + -- Unit is inside a reject zone ==> remove! + if inzone then + table.insert(remove, unitname) + end + end -- Filter unit categories. if #self.filterCategory>0 then From 6e35b80dafc62899c4831ec413ca22cde8599cdb Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 21:41:15 +0100 Subject: [PATCH 043/382] Update Warehouse.lua - requests should no longer be processed if warehouse has no airbase --- .../Moose/Functional/Warehouse.lua | 68 ++++++++++++++----- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index ac3d9f758..ee56fc92b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7179,24 +7179,35 @@ function WAREHOUSE:_CheckRequestNow(request) _assetcategory=_assets[1].category -- Check available parking for air asset units. - if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then + if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then - if self:IsRunwayOperational() then - - local Parking=self:_FindParkingForAssets(self.airbase,_assets) + if self.airbase then + + if self:IsRunwayOperational() then - --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) + 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 requested assets at the moment.", self.alias) + self:_InfoMessage(text, 5) + return false + end + + else + -- Runway destroyed. + local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias) self:_InfoMessage(text, 5) - return false + return false end else - -- Runway destroyed. - local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias) + + -- No airbase! + local text=string.format("Warehouse %s: Request denied! No airbase", self.alias) self:_InfoMessage(text, 5) return false + end end @@ -7220,14 +7231,37 @@ function WAREHOUSE:_CheckRequestNow(request) 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) - self:_InfoMessage(text, 5) - - return false + if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then + + if self.airbase then + + if self:IsRunwayOperational() then + + local Parking=self:_FindParkingForAssets(self.airbase,_transports) + + -- No parking ==> return false + 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) + self:_InfoMessage(text, 5) + return false + end + + else + + -- Runway destroyed. + local text=string.format("Warehouse %s: Request denied! Runway is still destroyed", self.alias) + self:_InfoMessage(text, 5) + return false + + end + + else + -- No airbase + local text=string.format("Warehouse %s: Request denied! No airbase currently!", self.alias) + self:_InfoMessage(text, 5) + return false end + end else From 1bb70b8697c30d8533c09366e381c6c30a635999 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 22:28:36 +0100 Subject: [PATCH 044/382] Update Warehouse.lua - added airbase coalition check when spawning aircraft #1413 --- Moose Development/Moose/Functional/Warehouse.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index ee56fc92b..552914ead 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7181,7 +7181,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check available parking for air asset units. if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then - if self.airbase then + if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then if self:IsRunwayOperational() then @@ -7233,7 +7233,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check available parking for transport units. if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then - if self.airbase then + if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then if self:IsRunwayOperational() then @@ -7537,7 +7537,7 @@ function WAREHOUSE:_CheckQueue() -- Check if request is possible now. local okay=false - if valid then + if valid then okay=self:_CheckRequestNow(qitem) else -- Remember invalid request and delete later in order not to confuse the loop. From 38cf5be738074741a5c962b3395c2e2d20d3daa0 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 22:51:10 +0100 Subject: [PATCH 045/382] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 59a1db182..f4ed1e8fb 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -897,11 +897,20 @@ function AIRBASE:GetParkingSpotsTable(termtype) if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then local spot=self:_GetParkingSpotByID(_spot.Term_Index) + + if spot then - spot.Free=_isfree(_spot) -- updated - spot.TOAC=_spot.TO_AC -- updated - - table.insert(spots, spot) + spot.Free=_isfree(_spot) -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(spots, spot) + + else + + self:E(string.format("ERROR: Parking spot %s is nil!", tostring(_spot.Term_Index))) + + end + end end From 084f00afadf4ea88dad7f292d33a8cdfd07cc427 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 29 Dec 2020 23:38:27 +0100 Subject: [PATCH 046/382] Update SpawnStatic.lua - fixed static spawns --- Moose Development/Moose/Core/SpawnStatic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 8bbb35782..95b41655d 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -456,7 +456,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- Add static to the game. local Static=nil - if self.InitFARP then + if self.InitFarp then local TemplateGroup={} TemplateGroup.units={} From f725039da5865a769871524be890f8b4773efa15 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 31 Dec 2020 00:51:54 +0100 Subject: [PATCH 047/382] Client and Event updates --- Moose Development/Moose/Core/Base.lua | 9 +- Moose Development/Moose/Core/Database.lua | 99 +++++++++++++++------- Moose Development/Moose/Core/Event.lua | 9 +- Moose Development/Moose/Wrapper/Client.lua | 8 +- 4 files changed, 87 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 83c78c7ce..4398c6d1a 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -676,6 +676,13 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. + --- Occurs when a player enters a slot and takes control of an aircraft. + -- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event. + -- initiator : The unit that is being taken control of. + -- @function [parent=#BASE] OnEventPlayerEnterAircraft + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + end @@ -765,7 +772,7 @@ function BASE:CreateEventTakeoff( EventTime, Initiator ) world.onEvent( Event ) end - --- Creation of a S_EVENT_PLAYER_ENTER_AIRCRAFT event. + --- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event. -- @param #BASE self -- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered. function BASE:CreateEventPlayerEnterAircraft( PlayerUnit ) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index f633848ac..9a9f0c738 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -24,7 +24,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -33,6 +33,9 @@ --- @type DATABASE +-- @field #string ClassName Name of the class. +-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. +-- @field #table CLIENTS Clients. -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. @@ -126,8 +129,6 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - - -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) @@ -135,8 +136,8 @@ function DATABASE:New() self:_RegisterGroupsAndUnits() self:_RegisterClients() self:_RegisterStatics() - --self:_RegisterPlayers() self:_RegisterAirbases() + --self:_RegisterPlayers() self.UNITS_Position = 0 @@ -156,14 +157,22 @@ end --- Adds a Unit based on the Unit Name in the DATABASE. -- @param #DATABASE self +-- @param #string DCSUnitName Unit name. +-- @return Wrapper.Unit#UNIT The added unit. function DATABASE:AddUnit( DCSUnitName ) - if not self.UNITS[DCSUnitName] then + if not self.UNITS[DCSUnitName] then + + -- Debug info. self:T( { "Add UNIT:", DCSUnitName } ) - local UnitRegister = UNIT:Register( DCSUnitName ) - self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) + + --local UnitRegister = UNIT:Register( DCSUnitName ) + + -- Register unit + self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) - table.insert( self.UNITS_Index, DCSUnitName ) + -- This is not used anywhere in MOOSE as far as I can see so I remove it until there comes an error somewhere. + --table.insert(self.UNITS_Index, DCSUnitName ) end return self.UNITS[DCSUnitName] @@ -179,6 +188,8 @@ end --- Adds a Static based on the Static Name in the DATABASE. -- @param #DATABASE self +-- @param #string DCSStaticName Name of the static. +-- @return Wrapper.Static#STATIC The static object. function DATABASE:AddStatic( DCSStaticName ) if not self.STATICS[DCSStaticName] then @@ -594,6 +605,9 @@ function DATABASE:Spawn( SpawnTemplate ) end --- Set a status to a Group within the Database, this to check crossing events for example. +-- @param #DATABASE self +-- @param #string GroupName Group name. +-- @param #string Status Status. function DATABASE:SetStatusGroup( GroupName, Status ) self:F2( Status ) @@ -601,8 +615,11 @@ function DATABASE:SetStatusGroup( GroupName, Status ) end --- Get a status to a Group within the Database, this to check crossing events for example. +-- @param #DATABASE self +-- @param #string GroupName Group name. +-- @return #string Status or an empty string "". function DATABASE:GetStatusGroup( GroupName ) - self:F2( Status ) + self:F2( GroupName ) if self.Templates.Groups[GroupName] then return self.Templates.Groups[GroupName].Status @@ -616,7 +633,8 @@ end -- @param #table GroupTemplate -- @param DCS#coalition.side CoalitionSide The coalition.side of the object. -- @param DCS#Object.Category CategoryID The Object.category of the object. --- @param DCS#country.id CountryID the country.id of the object +-- @param DCS#country.id CountryID the country ID of the object. +-- @param #string GroupName (Optional) The name of the group. Default is `GroupTemplate.name`. -- @return #DATABASE self function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) @@ -681,6 +699,10 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category ) end +--- Get group template. +-- @param #DATABASE self +-- @param #string GroupName Group name. +-- @return #table Group template table. function DATABASE:GetGroupTemplate( GroupName ) local GroupTemplate = self.Templates.Groups[GroupName].Template GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID @@ -691,7 +713,10 @@ end --- Private method that registers new Static Templates within the DATABASE Object. -- @param #DATABASE self --- @param #table StaticTemplate +-- @param #table StaticTemplate Template table. +-- @param #number CoalitionID Coalition ID. +-- @param #number CategoryID Category ID. +-- @param #number CountryID Country ID. -- @return #DATABASE self function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID ) @@ -721,10 +746,13 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:AddStatic( StaticTemplateName ) + return self end - ---- @param #DATABASE self +--- Get static group template. +-- @param #DATABASE self +-- @param #string StaticName Name of the static +-- @return #table Static template table. function DATABASE:GetStaticGroupTemplate( StaticName ) local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID @@ -913,20 +941,28 @@ function DATABASE:_EventOnBirth( Event ) self:F( { Event } ) if Event.IniDCSUnit then + if Event.IniObjectCategory == 3 then + self:AddStatic( Event.IniDCSUnitName ) + else + if Event.IniObjectCategory == 1 then + self:AddUnit( Event.IniDCSUnitName ) self:AddGroup( Event.IniDCSGroupName ) + -- Add airbase if it was spawned later in the mission. local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) if DCSAirbase then self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) self:AddAirbase(Event.IniDCSUnitName) end + end end + if Event.IniObjectCategory == 1 then Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) @@ -936,17 +972,28 @@ function DATABASE:_EventOnBirth( Event ) local PlayerName = Event.IniUnit:GetPlayerName() if PlayerName then - self:I( { "Player Joined:", PlayerName } ) + + -- Debug info. + self:I(string.format("Player %s joint unit %s of group %s", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) + + -- Add client. self:AddClient( Event.IniDCSUnitName ) + + -- Add player. if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end + + -- Player settings. local Settings = SETTINGS:Set( PlayerName ) - Settings:SetPlayerMenu( Event.IniUnit ) + Settings:SetPlayerMenu(Event.IniUnit) + end - _DATABASE:CreateEventBirth(-EventTime,Initiator,IniUnitName,place,subplace) - + if PlayerName or self.CLIENTS[Event.IniDCSUnitName] then + self:CreateEventPlayerEnterAircraft(Event.IniUnit) + end + end end @@ -1348,19 +1395,13 @@ function DATABASE:_RegisterTemplates() for group_num, Template in pairs(obj_type_data.group) do if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID - ) + + self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) + else - self:_RegisterStaticTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID - ) + + self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) + end --if GroupTemplate and GroupTemplate.units then end --for group_num, GroupTemplate in pairs(obj_type_data.group) do end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index a4a15ce44..6a7960748 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -646,7 +646,7 @@ end -- @param #function EventFunction The function to be called when the event occurs for the unit. -- @param EventClass The instance of the class for which the event is. -- @param #function OnEventFunction --- @return #EVENT +-- @return #EVENT self function EVENT:OnEventForTemplate( EventTemplate, EventFunction, EventClass, EventID ) self:F2( EventTemplate.name ) @@ -693,8 +693,9 @@ end -- @param #string GroupName The name of the GROUP. -- @param #function EventFunction The function to be called when the event occurs for the GROUP. -- @param Core.Base#BASE EventClass The self instance of the class for which the event is. --- @param EventID --- @return #EVENT +-- @param #number EventID Event ID. +-- @param ... Optional arguments passed to the event function. +-- @return #EVENT self function EVENT:OnEventForGroup( GroupName, EventFunction, EventClass, EventID, ... ) local Event = self:Init( EventID, EventClass ) @@ -711,7 +712,7 @@ do -- OnBirth -- @param Wrapper.Group#GROUP EventGroup -- @param #function EventFunction The function to be called when the event occurs for the unit. -- @param EventClass The self instance of the class for which the event is. - -- @return #EVENT + -- @return #EVENT self function EVENT:OnBirthForTemplate( EventTemplate, EventFunction, EventClass ) self:F2( EventTemplate.name ) diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 64e42950d..2100d698b 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -66,8 +66,7 @@ CLIENT = { ClientBriefingShown = false, _Menus = {}, _Tasks = {}, - Messages = { - } + Messages = {}, } @@ -137,7 +136,7 @@ end --- Transport defines that the Client is a Transport. Transports show cargo. -- @param #CLIENT self -- @param #string ClientName Name of the client unit. --- @return #CLIENT +-- @return #CLIENT self function CLIENT:Register( ClientName ) local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT @@ -158,7 +157,7 @@ end --- Transport defines that the Client is a Transport. Transports show cargo. -- @param #CLIENT self --- @return #CLIENT +-- @return #CLIENT self function CLIENT:Transport() self:F() @@ -245,6 +244,7 @@ end --- Checks for a client alive event and calls a function on a continuous basis. -- @param #CLIENT self -- @param #function CallBackFunction Create a function that will be called when a player joins the slot. +-- @param ... (Optional) Arguments for callback function as comma separated list. -- @return #CLIENT function CLIENT:Alive( CallBackFunction, ... ) self:F() From 787151597c7fe36f08c23426fc9a42ca5045994a Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 31 Dec 2020 17:17:15 +0100 Subject: [PATCH 048/382] Client --- Moose Development/Moose/Core/Database.lua | 29 ++++++--- Moose Development/Moose/Wrapper/Client.lua | 68 +++++++++++++++++----- 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 9a9f0c738..47389e7a6 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -468,6 +468,8 @@ end --- Adds a CLIENT based on the ClientName in the DATABASE. -- @param #DATABASE self +-- @param #string ClientName Name of the Client unit. +-- @return Wrapper.Client#CLIENT The client object. function DATABASE:AddClient( ClientName ) if not self.CLIENTS[ClientName] then @@ -863,7 +865,7 @@ end function DATABASE:_RegisterClients() for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:I(string.format("Register Client %s", tostring(ClientName))) + self:I(string.format("Register Client: %s", tostring(ClientName))) self:AddClient( ClientName ) end @@ -967,6 +969,13 @@ function DATABASE:_EventOnBirth( Event ) Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) + + -- TODO: create event ClientAlive + local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT + + if client then + + end -- Get player name. local PlayerName = Event.IniUnit:GetPlayerName() @@ -974,10 +983,15 @@ function DATABASE:_EventOnBirth( Event ) if PlayerName then -- Debug info. - self:I(string.format("Player %s joint unit %s of group %s", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) + self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) - -- Add client. - self:AddClient( Event.IniDCSUnitName ) + -- Add client in case it does not exist already. + if not client then + client=self:AddClient(Event.IniDCSUnitName) + end + + -- Add player. + client:AddPlayer(PlayerName) -- Add player. if not self.PLAYERS[PlayerName] then @@ -988,12 +1002,11 @@ function DATABASE:_EventOnBirth( Event ) local Settings = SETTINGS:Set( PlayerName ) Settings:SetPlayerMenu(Event.IniUnit) + -- Create an event. + self:CreateEventPlayerEnterAircraft(Event.IniUnit) + end - if PlayerName or self.CLIENTS[Event.IniDCSUnitName] then - self:CreateEventPlayerEnterAircraft(Event.IniUnit) - end - end end diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 2100d698b..8caea77d5 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -67,6 +67,7 @@ CLIENT = { _Menus = {}, _Tasks = {}, Messages = {}, + Players = {}, } @@ -145,12 +146,7 @@ function CLIENT:Register( ClientName ) self.ClientName = ClientName self.MessageSwitch = true self.ClientAlive2 = false - - --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5, 0.5 ) - self.AliveCheckScheduler:NoTrace() - self:F( self ) return self end @@ -159,29 +155,68 @@ end -- @param #CLIENT self -- @return #CLIENT self function CLIENT:Transport() - self:F() - self.ClientTransport = true return self end ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. +--- Adds a briefing to a CLIENT when a player joins a mission. -- @param #CLIENT self -- @param #string ClientBriefing is the text defining the Mission briefing. -- @return #CLIENT self function CLIENT:AddBriefing( ClientBriefing ) - self:F( ClientBriefing ) + self.ClientBriefing = ClientBriefing self.ClientBriefingShown = false return self end +--- Add player name. +-- @param #CLIENT self +-- @param #string PlayerName Name of the player. +-- @return #CLIENT self +function CLIENT:AddPlayer(PlayerName) + + table.insert(self.Players, PlayerName) + + return self +end + +--- Get player name(s). +-- @param #CLIENT self +-- @return #table List of player names. +function CLIENT:GetPlayers() + return self.Players +end + +--- Get name of player. +-- @param #CLIENT self +-- @return # +function CLIENT:GetPlayer() + return self.Players[1] +end + +--- Remove player. +-- @param #CLIENT self +-- @param #string PlayerName Name of the player. +-- @return #CLIENT self +function CLIENT:RemovePlayer(PlayerName) + + for i,playername in pairs(self.Players) do + if PlayerName==playername then + table.remove(self.Players, i) + break + end + end + + return self +end + + --- Show the briefing of a CLIENT. -- @param #CLIENT self -- @return #CLIENT self function CLIENT:ShowBriefing() - self:F( { self.ClientName, self.ClientBriefingShown } ) if not self.ClientBriefingShown then self.ClientBriefingShown = true @@ -252,6 +287,9 @@ function CLIENT:Alive( CallBackFunction, ... ) self.ClientCallBack = CallBackFunction self.ClientParameters = arg + self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. self.ClientName }, 0.1, 5, 0.5 ) + self.AliveCheckScheduler:NoTrace() + return self end @@ -259,6 +297,8 @@ end function CLIENT:_AliveCheckScheduler( SchedulerName ) self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) + env.info("FF client alive scheduler") + if self:IsAlive() then if self.ClientAlive2 == false then self:ShowBriefing() @@ -388,11 +428,13 @@ function CLIENT:GetClientGroupUnit() local ClientDCSUnit = Unit.getByName( self.ClientName ) self:T( self.ClientDCSUnit ) - if ClientDCSUnit and ClientDCSUnit:isExist() then - local ClientUnit = _DATABASE:FindUnit( self.ClientName ) - self:T2( ClientUnit ) + + if ClientDCSUnit then -- and ClientDCSUnit:isExist() then + local ClientUnit=_DATABASE:FindUnit( self.ClientName ) return ClientUnit end + + return nil end --- Returns the DCSUnit of the CLIENT. From 1b80d68f505795d4c347cd577abbc3facfef3046 Mon Sep 17 00:00:00 2001 From: acrojason Date: Thu, 31 Dec 2020 10:15:32 -0800 Subject: [PATCH 049/382] Add support for naval logistics --- .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 218 +++++++++++++++--- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 217 +++++++++++++---- Moose Development/Moose/Cargo/CargoUnit.lua | 7 +- .../Moose/Functional/Warehouse.lua | 31 +-- Moose Development/Moose/Modules.lua | 2 + .../Moose/Wrapper/Positionable.lua | 13 ++ Moose Setup/Moose.files | 28 ++- 7 files changed, 398 insertions(+), 118 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua index ed4984381..2de56b9ec 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -1,38 +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" } - 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, 3000 ) - self:SetDeployRadius( 500, 3000 ) - - 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 +--- 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 index 7f3f6f5aa..e28e52aa2 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -1,5 +1,60 @@ --- **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", @@ -7,16 +62,21 @@ AI_CARGO_SHIP = { } --- 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( CombatRadius ) + 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 ) @@ -45,14 +105,14 @@ function AI_CARGO_SHIP:SetCarrier( CargoCarrier ) AICargoTroops:Destroyed() end end -end + end -self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) -self.Coalition = self.CargoCarrier:GetCoalition() + self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) + self.Coalition = self.CargoCarrier:GetCoalition() -self:SetControllable( CargoCarrier ) + self:SetControllable( CargoCarrier ) -return self + return self end @@ -69,11 +129,11 @@ function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius ) 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 + local Attributes = NearUnit:GetDesc() + self:F({Desc=Attributes}) + if NearUnit:HasAttributes( "Trucks" ) then + return NearUnit:GetGroup() + end end end @@ -100,7 +160,7 @@ end -- @param Cargo.CargoGroup#CARGO_GROUP Cargo -- @return #AI_CARGO_SHIP function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) - BASE:T("DEBUGGING*** AI_CARGO_SHIP:FollowToCarrier") + local InfantryGroup = CargoGroup:GetGroup() self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) @@ -147,6 +207,8 @@ 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 @@ -197,8 +259,11 @@ function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To ) 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 @@ -206,9 +271,10 @@ function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, 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 @@ -216,6 +282,15 @@ function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, 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 @@ -224,16 +299,89 @@ function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, end end -function AI_CARGO_SHIP:onafterPickedUp( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) +--- 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 - Speed = Speed or Ship:GetSpeedMax()*0.8 + 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 = {} - for i=1, #lane do + -- 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) @@ -243,36 +391,7 @@ function AI_CARGO_SHIP:onafterPickedUp( Ship, From, Event, To, Coordinate, Speed Ship:Route(Waypoints, 1) else - BASE:T("ERROR: No shipping lane defined for Naval Transport!") - 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 = {} - - for i=1, #lane do - local coord = lane[i] - local Waypoint = coord:WaypointGround(_speed) - table.insert(Waypoints, Waypoint) - end - - local Waypoint = Waypoints[#Waypoints] - - Ship:Route(Waypoints, 1) - - else - BASE:T("ERROR: No shipping lane defined for Naval Transport!") + 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 d61c33984..2aabf81a7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1777,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! @@ -1830,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 @@ -4496,7 +4495,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Find asset belonging to this group. local asset=self:FindAssetInDB(_group) - BASE:T("DEBUGGING*** load radius: "..asset.loadradius) -- New cargo group object. local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius) @@ -4556,7 +4554,8 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Pickup and deploy zones. local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) PickupZoneSet:AddZone(self.harborzone) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.portzone) + 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() @@ -4583,21 +4582,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet pickupinner=20 deployouter=1000 deployinner=0 - --BASE:T("DEBUGGING*** Let's try to move these units") - --[[for _,_group in pairs(CargoGroupSet:GetSetObjects()) do - local group=GROUP:FindByName( _group:GetName() ) --Wrapper.Group#GROUP - - - --local _speed = group:GetSpeedMax()*0.7 - BASE:T("DEBUGGING*** Group ".._.." coordinate is "..CargoTransport:GetCoordinate()) - --local FromCoord = group:GetCoordinate() - local ToCoord = CargoTransport:GetCoordinate() - - local FromWP = FromCoord:WaypointGround() - local ToWP = ToCoord:WaypointGround( 15, "Vee" ) - - group:Route( { FromWP, ToWP }, 10 ) - end]]-- else pickupouter=200 pickupinner=0 @@ -4639,15 +4623,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Dispatcher Event Functions -- -------------------------------- - --- Function called before carrier loads something - function CargoTransport:OnBeforeMonitor(From, Event, To, Carrier, Cargo, PickupZone) - -- Need to get the cargo over to the portzone - -- But what if the cargo can't move on it's own? - BASE:T("DEBUGGING*** CargoTransport:OnBeforeMonitor") - - end - - --- Function called after carrier picked up something. function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 22f23bb75..45442da11 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -95,10 +95,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 be85454a3..e3ce4778a 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1390,6 +1390,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 4852f9346..8a3b67961 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -1,3 +1,4 @@ +Utilities/Enums.lua Utilities/Routines.lua Utilities/Utils.lua @@ -11,6 +12,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 @@ -18,6 +20,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/Goal.lua @@ -48,6 +52,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 @@ -67,31 +72,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 @@ -108,10 +117,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 From c0fc649f8b73d6ea1e93ae491cd05af1098d98f6 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 1 Jan 2021 01:46:14 +0100 Subject: [PATCH 050/382] Client --- Moose Development/Moose/Cargo/CargoGroup.lua | 3 ++ Moose Development/Moose/Core/Database.lua | 50 +++++++++++++++----- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 2d356f270..4a55946e3 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -78,6 +78,9 @@ do -- CARGO_GROUP local GroupName = CargoGroup:GetName() self.CargoName = Name self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) ) + + -- Deactivate late activation. + self.CargoTemplate.lateActivation=false self.GroupTemplate = UTILS.DeepCopy( self.CargoTemplate ) self.GroupTemplate.name = self.CargoName .. "#CARGO" diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 47389e7a6..91259c8b7 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -692,6 +692,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end + -- Debug info. self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, Category = self.Templates.Groups[GroupTemplateName].CategoryID, @@ -739,7 +740,8 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID self.Templates.Statics[StaticTemplateName].CountryID = CountryID - self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, + -- Debug info. + self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, Country = self.Templates.Statics[StaticTemplateName].CountryID @@ -829,33 +831,38 @@ end function DATABASE:_RegisterGroupsAndUnits() local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + for DCSGroupId, DCSGroup in pairs( CoalitionData ) do if DCSGroup:isExist() then + + -- Group name. local DCSGroupName = DCSGroup:getName() - self:I( { "Register Group:", DCSGroupName } ) + -- Add group. + self:I(string.format("Register Group: %s", tostring(DCSGroupName))) self:AddGroup( DCSGroupName ) + -- Loop over units in group. for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do + -- Get unit name. local DCSUnitName = DCSUnit:getName() - self:I( { "Register Unit:", DCSUnitName } ) + + -- Add unit. + self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) self:AddUnit( DCSUnitName ) + end else - self:E( { "Group does not exist: ", DCSGroup } ) + self:E({"Group does not exist: ", DCSGroup}) end end end - self:T("Groups:") - for GroupName, Group in pairs( self.GROUPS ) do - self:T( { "Group:", GroupName } ) - end - return self end @@ -1069,13 +1076,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) if Event.IniUnit then + if Event.IniObjectCategory == 1 then + + -- Try to get the player name. This can be buggy for multicrew aircraft! local PlayerName = Event.IniUnit:GetPlayerName() - if PlayerName and self.PLAYERS[PlayerName] then - self:I( { "Player Left:", PlayerName } ) + + if PlayerName then --and self.PLAYERS[PlayerName] then + + -- Debug info. + self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName))) + + -- Remove player menu. local Settings = SETTINGS:Set( PlayerName ) - Settings:RemovePlayerMenu( Event.IniUnit ) - self:DeletePlayer( Event.IniUnit, PlayerName ) + Settings:RemovePlayerMenu(Event.IniUnit) + + -- Delete player. + self:DeletePlayer(Event.IniUnit, PlayerName) + + -- Client stuff. + local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT + if client then + client:RemovePlayer(PlayerName) + end + end end end From ec039f2999ad6c416107e4f79c79cc33afce6237 Mon Sep 17 00:00:00 2001 From: acrojason Date: Thu, 31 Dec 2020 17:04:21 -0800 Subject: [PATCH 051/382] Multiple ship types, set default cargobay limit, fixed doc bug --- .../Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 1 - .../Moose/Functional/Warehouse.lua | 40 ++++++++++++------- .../Moose/Wrapper/Positionable.lua | 2 +- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua index 2de56b9ec..d20cca9eb 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -133,7 +133,6 @@ -- -- === -- - -- @field #AI_CARGO_DISPATCHER_SHIP AI_CARGO_DISPATCHER_SHIP = { ClassName = "AI_CARGO_DISPATCHER_SHIP" diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 2aabf81a7..0a73f149e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -193,7 +193,13 @@ -- warehouseBatumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- -- This becomes important when assets are requested from other warehouses as described below. In this case, the five Hueys are now marked as transport helicopters and --- not attack helicopters. +-- not attack helicopters. This is also particularly useful when adding assets to a warehouse with the intention of using them to transport other units that are part of +-- a subsequent request (see below). Setting the attribute will help to ensure that warehouse module can find the correct unit when attempting to service a request in its +-- queue. For example, if we want to add an Amphibious Landing Ship, even though most are indeed armed, it's recommended to do the following: +-- +-- warehouseBatumi:AddAsset("Landing Ship", 1, WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP) +-- +-- Then when adding the request, you can simply specify WAREHOUSE.TransportType.SHIP (which corresponds to NAVAL_UNARMEDSHIP) as the TransportType. -- -- ### Setting the Cargo Bay Weight Limit -- You can 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 @@ -1727,12 +1733,15 @@ WAREHOUSE.Attribute = { -- @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", - HELICOPTER = "Air_TransportHelo", - APC = "Ground_APC", - TRAIN = "Ground_Train", - SHIP = "Naval_UnarmedShip", - SELFPROPELLED = "Selfpropelled", + AIRPLANE = "Air_TransportPlane", + HELICOPTER = "Air_TransportHelo", + APC = "Ground_APC", + TRAIN = "Ground_Train", + SHIP = "Naval_UnarmedShip", + AIRCRAFTCARRIER = "Naval_AircraftCarrier", + WARSHIP = "Naval_WarShip", + ARMEDSHIP = "Naval_ArmedShip", + 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. @@ -4352,7 +4361,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!") return - elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER then -- Spawn Ship in port zone spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone) @@ -4483,7 +4492,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 + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER + or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then _boardradius=6000 end @@ -4549,7 +4559,8 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Set home zone. CargoTransport:SetHomeZone(self.spawnzone) - elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER + or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then -- Pickup and deploy zones. local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) @@ -4577,7 +4588,8 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet local pickupinner = 0 local deployouter = 200 local deployinner = 0 - if Request.transporttype==WAREHOUSE.TransportType.SHIP then + if Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER + or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then pickupouter=1000 pickupinner=20 deployouter=1000 @@ -6968,7 +6980,8 @@ function WAREHOUSE:_CheckRequestValid(request) valid=false end - elseif request.transporttype==WAREHOUSE.TransportType.SHIP then + elseif request.transporttype==WAREHOUSE.TransportType.SHIP or request.transporttype==WAREHOUSE.TransportType.AIRCRAFTCARRIER + or request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or request.transporttype==WAREHOUSE.TransportType.WARSHIP then -- Transport by ship. local shippinglane=self:HasConnectionNaval(request.warehouse) @@ -8099,10 +8112,9 @@ function WAREHOUSE:_GetAttribute(group) -- Ships local aircraftcarrier=group:HasAttribute("Aircraft Carriers") local warship=group:HasAttribute("Heavy armed ships") - local armedship=group:HasAttribute("Armed Ship") + local armedship=group:HasAttribute("Armed ships") or group:HasAttribute("Armed Ship") local unarmedship=group:HasAttribute("Unarmed ships") - -- Define attribute. Order is important. if transportplane then attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index e3ce4778a..da4365de0 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1401,7 +1401,7 @@ do -- Cargo ["Dry-cargo ship-1"] = 70000, ["Dry-cargo ship-2"] = 70000, } - self.__.CargoBayWeightLimit = Weights[Desc.typeName] + self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) else local Desc = self:GetDesc() From 721d027fb36400333d93ee8a52ca744fa13561bd Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 1 Jan 2021 16:37:55 +0100 Subject: [PATCH 052/382] Update AI_Cargo_Helicopter.lua MI-26 is slightly over 5m blocking boarding of Cargo --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 4b838fde5..8851cc6c9 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -243,7 +243,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) if self.RoutePickup == true then - if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 10 then --self:Load( Helicopter:GetPointVec2() ) self:Load( self.PickupZone ) self.RoutePickup = false From 774c8971c8badf02c70280d1b8c3e6899639134a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:24:00 +0100 Subject: [PATCH 053/382] Update AI_Cargo_Helicopter.lua Fixed FSM dead ends --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 8851cc6c9..fe8998093 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -62,7 +62,6 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) - self:SetStartState( "Unloaded" ) self:AddTransition( "Unloaded", "Pickup", "*" ) @@ -72,11 +71,13 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Boarding", "Board", "Boarding" ) self:AddTransition( "Boarding", "Loaded", "Boarding" ) self:AddTransition( "Boarding", "PickedUp", "Loaded" ) + self:AddTransition( "Boarding", "Deploy", "Loaded" ) self:AddTransition( "Loaded", "Unload", "Unboarding" ) self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) - + self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) + self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Orbit" , "*" ) @@ -251,7 +252,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) end if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 10 then self:Unload( self.DeployZone ) self.RouteDeploy = false end @@ -622,4 +623,3 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat end end - From 7b7605e50131cadc6cbfe0a4ed39a9dcb925f018 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 2 Jan 2021 01:21:45 +0100 Subject: [PATCH 054/382] Client --- Moose Development/Moose/Wrapper/Client.lua | 102 ++++++++++++--------- Moose Development/Moose/Wrapper/Unit.lua | 8 +- 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 8caea77d5..39f6af974 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -3,8 +3,7 @@ -- === -- -- ### Author: **FlightControl** --- --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -52,13 +51,6 @@ -- -- @field #CLIENT CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, ClassName = "CLIENT", ClientName = nil, ClientAlive = false, @@ -73,20 +65,13 @@ CLIENT = { --- Finds a CLIENT from the _DATABASE using the relevant DCS Unit. -- @param #CLIENT self --- @param #string ClientName Name of the DCS **Unit** as defined within the Mission Editor. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #CLIENT --- @usage --- -- Create new Clients. --- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' ) --- Mission:AddGoal( DeploySA6TroopsGoal ) --- --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() ) --- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) -function CLIENT:Find( DCSUnit, Error ) +-- @param DCS#Unit DCSUnit The DCS unit of the client. +-- @param #boolean Error Throw an error message. +-- @return #CLIENT The CLIENT found in the _DATABASE. +function CLIENT:Find(DCSUnit, Error) + local ClientName = DCSUnit:getName() + local ClientFound = _DATABASE:FindClient( ClientName ) if ClientFound then @@ -123,7 +108,9 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error ) if ClientFound then ClientFound:F( { ClientName, ClientBriefing } ) - ClientFound:AddBriefing( ClientBriefing ) + + ClientFound:AddBriefing(ClientBriefing) + ClientFound.MessageSwitch = true return ClientFound @@ -138,13 +125,18 @@ end -- @param #CLIENT self -- @param #string ClientName Name of the client unit. -- @return #CLIENT self -function CLIENT:Register( ClientName ) +function CLIENT:Register(ClientName) - local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT - - self:F( ClientName ) + -- Inherit unit. + local self = BASE:Inherit( self, UNIT:Register(ClientName )) -- #CLIENT + + -- Set client name. self.ClientName = ClientName + + -- Message switch. self.MessageSwitch = true + + -- Alive2. self.ClientAlive2 = false return self @@ -184,16 +176,19 @@ end --- Get player name(s). -- @param #CLIENT self --- @return #table List of player names. +-- @return #table List of player names or an empty table `{}`. function CLIENT:GetPlayers() return self.Players end --- Get name of player. -- @param #CLIENT self --- @return # +-- @return #string Player name or `nil`. function CLIENT:GetPlayer() - return self.Players[1] + if #self.Players>0 then + return self.Players[1] + end + return nil end --- Remove player. @@ -297,21 +292,29 @@ end function CLIENT:_AliveCheckScheduler( SchedulerName ) self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) - env.info("FF client alive scheduler") - - if self:IsAlive() then + if self:IsAlive() then + if self.ClientAlive2 == false then + + -- Show briefing. self:ShowBriefing() + + -- Callback function. if self.ClientCallBack then self:T("Calling Callback function") self.ClientCallBack( self, unpack( self.ClientParameters ) ) end + + -- Alive. self.ClientAlive2 = true end + else + if self.ClientAlive2 == true then self.ClientAlive2 = false end + end return true @@ -344,10 +347,14 @@ function CLIENT:GetDCSGroup() --self:F(self.ClientName) if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() and UnitData:getGroup():isExist() then + + if ClientGroup:isExist() and UnitData:getGroup():isExist() then + if ClientGroup:getID() == UnitData:getGroup():getID() then self:T3( "Normal logic" ) self:T3( self.ClientName .. " : group found!" ) @@ -355,15 +362,22 @@ function CLIENT:GetDCSGroup() self.ClientGroupName = ClientGroup:getName() return ClientGroup end + else + -- Now we need to resolve the bugs in DCS 1.5 ... -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) self:T3( "Bug 1.5 logic" ) + local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate + self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName + self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) return ClientGroup + end -- else -- error( "Client " .. self.ClientName .. " not found!" ) @@ -388,22 +402,22 @@ function CLIENT:GetDCSGroup() end end + -- Nothing could be found :( self.ClientGroupID = nil - self.ClientGroupUnit = nil + self.ClientGroupName = nil return nil end --- TODO: Check DCS#Group.ID --- Get the group ID of the client. -- @param #CLIENT self --- @return DCS#Group.ID +-- @return #number DCS#Group ID. function CLIENT:GetClientGroupID() - local ClientGroup = self:GetDCSGroup() + -- This updates the ID. + self:GetDCSGroup() - --self:F( self.ClientGroupID ) -- Determined in GetDCSGroup() return self.ClientGroupID end @@ -413,15 +427,15 @@ end -- @return #string function CLIENT:GetClientGroupName() - local ClientGroup = self:GetDCSGroup() - - self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() + -- This updates the group name. + self:GetDCSGroup() + return self.ClientGroupName end --- Returns the UNIT of the CLIENT. -- @param #CLIENT self --- @return Wrapper.Unit#UNIT +-- @return Wrapper.Unit#UNIT The client UNIT or `nil`. function CLIENT:GetClientGroupUnit() self:F2() @@ -429,7 +443,7 @@ function CLIENT:GetClientGroupUnit() self:T( self.ClientDCSUnit ) - if ClientDCSUnit then -- and ClientDCSUnit:isExist() then + if ClientDCSUnit and ClientDCSUnit:isExist() then local ClientUnit=_DATABASE:FindUnit( self.ClientName ) return ClientUnit end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index cbbc924d4..739c42649 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -103,12 +103,18 @@ UNIT = { --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. --- @return #UNIT +-- @return #UNIT self function UNIT:Register( UnitName ) + + -- Inherit CONTROLLABLE. local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) + + -- Set unit name. self.UnitName = UnitName + -- Set event prio. self:SetEventPriority( 3 ) + return self end From 5b2e67df194a047fc6e9fc9a932e53dd33de02e1 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 2 Jan 2021 18:53:58 +0100 Subject: [PATCH 055/382] Update AI_Cargo.lua Some small additions from learnings from AI CARGO HELI --- Moose Development/Moose/AI/AI_Cargo.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index 586c0ab51..a5a08b2e7 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -393,7 +393,7 @@ end 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 + if Carrier and Carrier:IsAlive() and From == "Boarding" then self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } ) if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then self:__Board( -10, Cargo, CarrierUnit, PickupZone ) @@ -509,7 +509,7 @@ end function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone, Defend ) self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone, Defend = Defend } ) - if Carrier and Carrier:IsAlive() then + if Carrier and Carrier:IsAlive() and From == "Unboarding" then if not Cargo:IsUnLoaded() then self:__Unboard( 10, Cargo, CarrierUnit, DeployZone, Defend ) return From c51a65f0587b7f2ceb9b099d37bc404c99a88984 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 2 Jan 2021 18:56:27 +0100 Subject: [PATCH 056/382] Update AI_Cargo_Helicopter.lua Removed unnecessary FSM transitions, added height to Waypoints to avoid (blue) Helis to creep over the ground --- .../Moose/AI/AI_Cargo_Helicopter.lua | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index fe8998093..0307266c7 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -62,14 +62,15 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) + self:SetStartState( "Unloaded" ) self:AddTransition( "Unloaded", "Pickup", "*" ) self:AddTransition( "Loaded", "Deploy", "*" ) - + --[[ self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) self:AddTransition( "Boarding", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Loaded", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Loaded" ) self:AddTransition( "Boarding", "PickedUp", "Loaded" ) self:AddTransition( "Boarding", "Deploy", "Loaded" ) self:AddTransition( "Loaded", "Unload", "Unboarding" ) @@ -77,7 +78,11 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) - + --]] + self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) + self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) + self:AddTransition( "Unloaded", "Unloaded", "Unloaded" ) self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Orbit" , "*" ) @@ -309,7 +314,11 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina -- true -- ) -- Route[#Route+1] = WaypointFrom - local CoordinateTo = Coordinate + local CoordinateTo = Coordinate + + local landheight = CoordinateTo:GetLandHeight() -- get target height + CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + local WaypointTo = CoordinateTo:WaypointAir( "RADIO", POINT_VEC3.RoutePointType.TurningPoint, @@ -363,7 +372,10 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina -- true -- ) -- Route[#Route+1] = WaypointFrom - local CoordinateTo = Coordinate + local CoordinateTo = Coordinate + local landheight = CoordinateTo:GetLandHeight() -- get target height + CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + local WaypointTo = CoordinateTo:WaypointAir( "RADIO", POINT_VEC3.RoutePointType.TurningPoint, @@ -423,7 +435,8 @@ end -- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. -- @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, Height, PickupZone ) - + self:F({Coordinate, Speed, Height, PickupZone }) + if Helicopter and Helicopter:IsAlive() ~= nil then Helicopter:Activate() @@ -437,7 +450,6 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin --- Calculate the target route point. local CoordinateFrom = Helicopter:GetCoordinate() - local CoordinateTo = Coordinate --- Create a route point of type air. local WaypointFrom = CoordinateFrom:WaypointAir( @@ -449,6 +461,10 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin ) --- Create a route point of type air. + local CoordinateTo = Coordinate + local landheight = CoordinateTo:GetLandHeight() -- get target height + CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + local WaypointTo = CoordinateTo:WaypointAir( "RADIO", POINT_VEC3.RoutePointType.TurningPoint, @@ -526,7 +542,11 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin Route[#Route+1] = WaypointFrom --- Create a route point of type air. - local CoordinateTo = Coordinate + + local CoordinateTo = Coordinate + local landheight = CoordinateTo:GetLandHeight() -- get target height + CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + local WaypointTo = CoordinateTo:WaypointAir( "RADIO", POINT_VEC3.RoutePointType.TurningPoint, @@ -596,7 +616,10 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Route[#Route+1] = WaypointFrom --- Create a route point of type air. - local CoordinateTo = Coordinate + local CoordinateTo = Coordinate + local landheight = CoordinateTo:GetLandHeight() -- get target height + CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + local WaypointTo = CoordinateTo:WaypointAir( "RADIO", POINT_VEC3.RoutePointType.TurningPoint, @@ -623,3 +646,4 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat end end + From 417af6a93c748b1f9a17a281137a77e1c3662322 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 2 Jan 2021 21:45:19 +0100 Subject: [PATCH 057/382] Client --- Moose Development/Moose/AI/AI_Cargo_APC.lua | 19 ++++++--- Moose Development/Moose/Core/Database.lua | 46 +++++++++++++++++---- Moose Development/Moose/Wrapper/Client.lua | 8 ++++ Moose Development/Moose/Wrapper/Unit.lua | 31 +++++++++++++- 4 files changed, 89 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index c20475711..ac63297d0 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -428,18 +428,27 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He self.RouteDeploy = true - local _speed=Speed or APC:GetSpeedMax()*0.5 - - local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) + -- Set speed in km/h. + local speedmax=APC:GetSpeedMax() + local _speed=Speed or speedmax*0.5 + _speed=math.min(_speed, speedmax) + -- Route on road. + local Waypoints = APC:TaskGroundOnRoad(Coordinate, _speed, "Line abreast", true) + + -- Task function local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) - self:F({Waypoints = Waypoints}) + -- Last waypoint local Waypoint = Waypoints[#Waypoints] - APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. + + -- Set task function + APC:SetTaskWaypoint(Waypoint, TaskFunction) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. + -- Route group APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. + -- Call parent function. self:GetParent( self, AI_CARGO_APC ).onafterDeploy( self, APC, From, Event, To, Coordinate, Speed, Height, DeployZone ) end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 91259c8b7..63df70d01 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -977,11 +977,11 @@ function DATABASE:_EventOnBirth( Event ) Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) - -- TODO: create event ClientAlive + -- Client local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT if client then - + -- TODO: create event ClientAlive end -- Get player name. @@ -1018,8 +1018,6 @@ function DATABASE:_EventOnBirth( Event ) end - - end @@ -1030,17 +1028,31 @@ function DATABASE:_EventOnDeadOrCrash( Event ) self:F2( { Event } ) if Event.IniDCSUnit then + + local name=Event.IniDCSUnitName + if Event.IniObjectCategory == 3 then if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) end else if Event.IniObjectCategory == 1 then + + -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit( Event.IniDCSUnitName ) + self:DeleteUnit(Event.IniDCSUnitName) end + + -- Remove client players. + local client=self.CLIENTS[name] --Wrapper.Client#CLIENT + + if client then + client:RemovePlayers() + end + end end + end self:AccountDestroys( Event ) @@ -1055,15 +1067,31 @@ function DATABASE:_EventOnPlayerEnterUnit( Event ) if Event.IniDCSUnit then if Event.IniObjectCategory == 1 then + + -- Add unit. self:AddUnit( Event.IniDCSUnitName ) + + -- Ini unit. Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) + + -- Add group. self:AddGroup( Event.IniDCSGroupName ) + + -- Get player unit. local PlayerName = Event.IniDCSUnit:getPlayerName() - if not self.PLAYERS[PlayerName] then - self:AddPlayer( Event.IniDCSUnitName, PlayerName ) + + if PlayerName then + + if not self.PLAYERS[PlayerName] then + self:AddPlayer( Event.IniDCSUnitName, PlayerName ) + end + + local Settings = SETTINGS:Set( PlayerName ) + Settings:SetPlayerMenu( Event.IniUnit ) + + else + self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit") end - local Settings = SETTINGS:Set( PlayerName ) - Settings:SetPlayerMenu( Event.IniUnit ) end end end diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 39f6af974..445c655ee 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -207,6 +207,14 @@ function CLIENT:RemovePlayer(PlayerName) return self end +--- Remove all players. +-- @param #CLIENT self +-- @return #CLIENT self +function CLIENT:RemovePlayers() + self.Players={} + return self +end + --- Show the briefing of a CLIENT. -- @param #CLIENT self diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 739c42649..1321cba5d 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -13,7 +13,7 @@ -- -- ### Author: **FlightControl** -- --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -22,6 +22,8 @@ --- @type UNIT +-- @field #string ClassName Name of the class. +-- @field #string UnitName Name of the unit. -- @extends Wrapper.Controllable#CONTROLLABLE --- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object. @@ -87,6 +89,7 @@ -- @field #UNIT UNIT UNIT = { ClassName="UNIT", + UnitName=nil, } @@ -379,6 +382,32 @@ function UNIT:GetPlayerName() end +--- Checks is the unit is a *Player* or *Client* slot. +-- @param #UNIT self +-- @return #boolean If true, unit is a player or client aircraft +function UNIT:IsClient() + + if _DATABASE.CLIENTS[self.UnitName] then + return true + end + + return false +end + +--- Get the CLIENT of the unit +-- @param #UNIT self +-- @return Wrapper.Client#CLIENT +function UNIT:GetClient() + + local client=_DATABASE.CLIENTS[self.UnitName] + + if client then + return client + end + + return nil +end + --- Returns the unit's number in the group. -- The number is the same number the unit has in ME. -- It may not be changed during the mission. From 3358f98bc40ff77ab72b9a143a381eace867cf60 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 2 Jan 2021 23:53:36 +0100 Subject: [PATCH 058/382] Some stuff --- Moose Development/Moose/Cargo/Cargo.lua | 14 ++- Moose Development/Moose/Cargo/CargoUnit.lua | 13 +-- Moose Development/Moose/Core/Database.lua | 96 +++++++++++++++++---- Moose Development/Moose/Core/Event.lua | 2 +- Moose Development/Moose/Core/Set.lua | 34 +++++--- 5 files changed, 119 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 6051c25e4..3e43b3ef7 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -1078,11 +1078,14 @@ do -- CARGO_REPRESENTABLE local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE self:F( { Type, Name, LoadRadius, NearRadius } ) - local Desc = CargoObject:GetDesc() - self:I( { Desc = Desc } ) + -- Descriptors. + local Desc=CargoObject:GetDesc() + self:T({Desc=Desc}) + -- Weight. local Weight = math.random( 80, 120 ) + -- Adjust weight.. if Desc then if Desc.typeName == "2B11 mortar" then Weight = 210 @@ -1091,13 +1094,8 @@ do -- CARGO_REPRESENTABLE end end + -- Set weight. self:SetWeight( Weight ) - --- local Box = CargoUnit:GetBoundingBox() --- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z ) --- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } ) - --self:SetVolume( VolumeUnit ) - return self end diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index 88ba8c725..4d0093b47 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -46,14 +46,17 @@ do -- CARGO_UNIT -- @param #number NearRadius (optional) -- @return #CARGO_UNIT function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT - self:I( { Type, Name, LoadRadius, NearRadius } ) - self:T( CargoUnit ) + -- Inherit CARGO_REPRESENTABLE. + local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT + + -- Debug info. + self:T({Type=Type, Name=Name, LoadRadius=LoadRadius, NearRadius=NearRadius}) + + -- Set cargo object. self.CargoObject = CargoUnit - self:T( self.ClassName ) - + -- Set event prio. self:SetEventPriority( 5 ) return self diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 63df70d01..eafe78140 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -411,7 +411,6 @@ do -- cargo local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! for CargoGroupName, CargoGroup in pairs( Groups ) do - self:I( { Cargo = CargoGroupName } ) if self:IsCargo( CargoGroupName ) then local CargoInfo = CargoGroupName:match("#CARGO(.*)") local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") @@ -755,46 +754,96 @@ end --- Get static group template. -- @param #DATABASE self --- @param #string StaticName Name of the static +-- @param #string StaticName Name of the static. -- @return #table Static template table. function DATABASE:GetStaticGroupTemplate( StaticName ) - local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate - return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID + if self.Templates.Statics[StaticName] then + local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate + return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID + else + self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) + return nil + end end ---- @param #DATABASE self +--- Get static unit template. +-- @param #DATABASE self +-- @param #string StaticName Name of the static. +-- @return #table Static template table. function DATABASE:GetStaticUnitTemplate( StaticName ) - local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate - return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID + if self.Templates.Statics[StaticName] then + local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate + return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID + else + self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName)) + return nil + end end - +--- Get group name from unit name. +-- @param #DATABASE self +-- @param #string UnitName Name of the unit. +-- @return #string Group name. function DATABASE:GetGroupNameFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupName + if self.Templates.Units[UnitName] then + return self.Templates.Units[UnitName].GroupName + else + self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) + return nil + end end +--- Get group template from unit name. +-- @param #DATABASE self +-- @param #string UnitName Name of the unit. +-- @return #table Group template. function DATABASE:GetGroupTemplateFromUnitName( UnitName ) - return self.Templates.Units[UnitName].GroupTemplate + if self.Templates.Units[UnitName] then + return self.Templates.Units[UnitName].GroupTemplate + else + self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) + return nil + end end +--- Get coalition ID from client name. +-- @param #DATABASE self +-- @param #string ClientName Name of the Client. +-- @return #number Coalition ID. function DATABASE:GetCoalitionFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CoalitionID end +--- Get category ID from client name. +-- @param #DATABASE self +-- @param #string ClientName Name of the Client. +-- @return #number Category ID. function DATABASE:GetCategoryFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CategoryID end +--- Get country ID from client name. +-- @param #DATABASE self +-- @param #string ClientName Name of the Client. +-- @return #number Country ID. function DATABASE:GetCountryFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CountryID end --- Airbase +--- Get coalition ID from airbase name. +-- @param #DATABASE self +-- @param #string AirbaseName Name of the airbase. +-- @return #number Coalition ID. function DATABASE:GetCoalitionFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetCoalition() end +--- Get category from airbase name. +-- @param #DATABASE self +-- @param #string AirbaseName Name of the airbase. +-- @return #number Category. function DATABASE:GetCategoryFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetCategory() end @@ -890,7 +939,7 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - self:I( { "Register Static:", DCSStaticName } ) + self:I(string.format("Register Static: %s", tostring(DCSStaticName))) self:AddStatic( DCSStaticName ) else self:E( { "Static does not exist: ", DCSStatic } ) @@ -921,7 +970,7 @@ function DATABASE:_RegisterAirbases() local airbaseUID=airbase:GetID(true) -- Debug output. - local text=string.format("Register Airbase: %s (ID=%d UID=%d), category=%s, parking=%d [", tostring(DCSAirbaseName), airbaseID, airbaseUID, AIRBASE.CategoryName[airbase.category], airbase.NparkingTotal) + local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal) for _,terminalType in pairs(AIRBASE.TerminalType) do if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) @@ -1025,19 +1074,29 @@ end -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) - self:F2( { Event } ) if Event.IniDCSUnit then local name=Event.IniDCSUnitName if Event.IniObjectCategory == 3 then + + --- + -- STATICS + --- + if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) end + else + if Event.IniObjectCategory == 1 then + --- + -- UNITS + --- + -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then self:DeleteUnit(Event.IniDCSUnitName) @@ -1048,13 +1107,20 @@ function DATABASE:_EventOnDeadOrCrash( Event ) if client then client:RemovePlayers() - end - + end + end end + -- Add airbase if it was spawned later in the mission. + local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE + if airbase and (airbase:IsHelipad() or airbase:IsShip()) then + self:DeleteAirbase(Event.IniDCSUnitName) + end + end + -- Account destroys. self:AccountDestroys( Event ) end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 6a7960748..4588c6ba7 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -820,7 +820,7 @@ do -- Event Creation -- @param #EVENT self -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. function EVENT:CreateEventNewCargo( Cargo ) - self:I( { Cargo } ) + self:F( { Cargo } ) local Event = { id = EVENTS.NewCargo, diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index df5c431df..b34ceb757 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4705,7 +4705,8 @@ do -- SET_AIRBASE if _DATABASE then -- We use the BaseCaptured event, which is generated by DCS when a base got captured. - self:HandleEvent( EVENTS.BaseCaptured ) + self:HandleEvent(EVENTS.BaseCaptured) + self:HandleEvent(EVENTS.Dead) -- We initialize the first set. for ObjectName, Object in pairs( self.Database ) do @@ -4720,10 +4721,9 @@ do -- SET_AIRBASE return self end - --- Starts the filtering. + --- Base capturing event. -- @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. @@ -4739,24 +4739,36 @@ do -- SET_AIRBASE end + --- Dead event. + -- @param #SET_AIRBASE self + -- @param Core.Event#EVENT EventData + function SET_AIRBASE:OnEventDead(EventData) + + local airbaseName, airbase=self:FindInDatabase(EventData) + + if airbase and airbase:IsShip() or airbase:IsHelipad() then + self:RemoveAirbasesByName(airbaseName) + 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! -- @param #SET_AIRBASE self - -- @param Core.Event#EVENTDATA Event - -- @return #string The name of the AIRBASE - -- @return #table The AIRBASE + -- @param Core.Event#EVENTDATA Event Event data. + -- @return #string The name of the AIRBASE. + -- @return Wrapper.Airbase#AIRBASE The AIRBASE object. function SET_AIRBASE:AddInDatabase( Event ) - self:F3( { Event } ) - return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end --- Handles the Database to check on any event that Object exists in the Database. -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! -- @param #SET_AIRBASE self - -- @param Core.Event#EVENTDATA Event - -- @return #string The name of the AIRBASE - -- @return #table The AIRBASE + -- @param Core.Event#EVENTDATA Event Event data. + -- @return #string The name of the AIRBASE. + -- @return Wrapper.Airbase#AIRBASE The AIRBASE object. function SET_AIRBASE:FindInDatabase( Event ) self:F3( { Event } ) From 697d12aefc4c4fb19ce913658f03eecf0a56e19b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 3 Jan 2021 16:31:07 +0100 Subject: [PATCH 059/382] Update AI_A2A_Dispatcher.lua #1422 --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 05ef48758..eae6ded44 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -2919,9 +2919,13 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron. function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) self.Defenders = self.Defenders or {} - local DefenderName = Defender:GetName() - self:F( { DefenderName = DefenderName } ) - return self.Defenders[ DefenderName ] + if Defender ~= nil then + local DefenderName = Defender:GetName() + self:F( { DefenderName = DefenderName } ) + return self.Defenders[ DefenderName ] + else + return nil + end end From 8e46e41b340c44f186775a759a7a5578eefd3d76 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 5 Jan 2021 09:54:10 +0100 Subject: [PATCH 060/382] Update AI_Cargo_Helicopter.lua Fix for missed FSM call --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 0307266c7..95361511c 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -82,7 +82,8 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Boarding", "Loaded", "Loaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) - self:AddTransition( "Unloaded", "Unloaded", "Unloaded" ) + self:AddTransition( "Unloaded", "Unloaded", "Unloaded" ) + self:AddTransition( "*", "PickedUp", "*" ) self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Orbit" , "*" ) From 218c7736e0c4a538326d07e3f6bb667a701d6a12 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 5 Jan 2021 11:25:03 +0100 Subject: [PATCH 061/382] Update AI_Cargo_Helicopter.lua --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 95361511c..1f58b7b08 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -79,7 +79,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) --]] - self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "*", "Loaded", "Loaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) self:AddTransition( "Unloaded", "Unloaded", "Unloaded" ) From cbc5b5bb89f49a5635e1e45fb95f05aca690fa3c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 5 Jan 2021 11:27:37 +0100 Subject: [PATCH 062/382] Update AI_Cargo.lua Fix for another dead end --- Moose Development/Moose/AI/AI_Cargo.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index a5a08b2e7..1de7e2d0f 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -52,7 +52,8 @@ function AI_CARGO:New( Carrier, CargoSet ) self:AddTransition( "Loaded", "Deploy", "*" ) self:AddTransition( "*", "Load", "Boarding" ) - self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" ) + self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( "Loaded", "Board", "Loaded" ) self:AddTransition( "Boarding", "Loaded", "Boarding" ) self:AddTransition( "Boarding", "PickedUp", "Loaded" ) From b61cfc739074ee2cf3b9152474d3a671e0a52308 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 9 Jan 2021 23:47:45 +0100 Subject: [PATCH 063/382] Update Set.lua --- Moose Development/Moose/Core/Set.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index b34ceb757..41261abcc 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4928,7 +4928,7 @@ do -- SET_CARGO --- (R2.1) Add CARGO to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Cargo.Cargo#CARGO Cargo A single cargo. - -- @return self + -- @return Core.Set#SET_CARGO self function SET_CARGO:AddCargo( Cargo ) --R2.4 self:Add( Cargo:GetName(), Cargo ) @@ -4940,7 +4940,7 @@ do -- SET_CARGO --- (R2.1) Add CARGOs to SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param #string AddCargoNames A single name or an array of CARGO names. - -- @return self + -- @return Core.Set#SET_CARGO self function SET_CARGO:AddCargosByName( AddCargoNames ) --R2.1 local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } @@ -4955,7 +4955,7 @@ do -- SET_CARGO --- (R2.1) Remove CARGOs from SET_CARGO. -- @param Core.Set#SET_CARGO self -- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. - -- @return self + -- @return Core.Set#SET_CARGO self function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) --R2.1 local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } From ae6613fae19d6a17691ea38f204ae0c9fcc9c921 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 Jan 2021 00:42:00 +0100 Subject: [PATCH 064/382] CARGO --- Moose Development/Moose/AI/AI_Cargo.lua | 9 +- Moose Development/Moose/AI/AI_Cargo_APC.lua | 83 ++++++++++++++++--- .../Moose/AI/AI_Cargo_Airplane.lua | 17 ++-- .../Moose/AI/AI_Cargo_Dispatcher.lua | 4 +- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 59 +++++++++++-- .../Moose/AI/AI_Cargo_Helicopter.lua | 75 +++-------------- Moose Development/Moose/Cargo/Cargo.lua | 2 +- Moose Development/Moose/Cargo/CargoGroup.lua | 2 +- Moose Development/Moose/Cargo/CargoUnit.lua | 2 +- 9 files changed, 153 insertions(+), 100 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index 1de7e2d0f..5bcd94437 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -1,4 +1,4 @@ ---- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. +--- **AI** - Models the intelligent transportation of infantry and other cargo. -- -- === -- @@ -35,10 +35,9 @@ AI_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 +-- @param Wrapper.Group#GROUP Carrier Cargo carrier group. +-- @param Core.Set#SET_CARGO CargoSet Set of cargo(s) to transport. +-- @return #AI_CARGO self function AI_CARGO:New( Carrier, CargoSet ) local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index ac63297d0..bee08e1b1 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.4) - Models the intelligent transportation of infantry and other cargo. +--- **AI** - Models the intelligent transportation of cargo using ground vehicles. -- -- === -- @@ -157,6 +157,45 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier ) return self end +--- Set whether or not the carrier will use roads to *pickup* and *deploy* the cargo. +-- @param #AI_CARGO_APC self +-- @param #boolean Offroad If true, carrier will not use roads. If `nil` or `false` the carrier will use roads when available. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_APC self +function AI_CARGO_APC:SetOffRoad(Offroad, Formation) + + self:SetPickupOffRoad(Offroad, Formation) + self:SetDeployOffRoad(Offroad, Formation) + + return self +end + +--- Set whether the carrier will *not* use roads to *pickup* the cargo. +-- @param #AI_CARGO_APC self +-- @param #boolean Offroad If true, carrier will not use roads. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_APC self +function AI_CARGO_APC:SetPickupOffRoad(Offroad, Formation) + + self.pickupOffroad=Offroad + self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad + + return self +end + +--- Set whether the carrier will *not* use roads to *deploy* the cargo. +-- @param #AI_CARGO_APC self +-- @param #boolean Offroad If true, carrier will not use roads. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_APC self +function AI_CARGO_APC:SetDeployOffRoad(Offroad, Formation) + + self.deployOffroad=Offroad + self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad + + return self +end + --- Find a free Carrier within a radius. -- @param #AI_CARGO_APC self @@ -350,10 +389,13 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To ) end - ---- @param #AI_CARGO_APC --- @param Wrapper.Group#GROUP APC -function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) +--- Pickup task function. Triggers Load event. +-- @param Wrapper.Group#GROUP APC The cargo carrier group. +-- @param #AI_CARGO_APC sel `AI_CARGO_APC` class. +-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). +-- @param #number Speed Speed (not used). +-- @param Core.Zone#ZONE PickupZone Pickup zone. +function AI_CARGO_APC._Pickup(APC, self, Coordinate, Speed, PickupZone) APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) @@ -362,8 +404,12 @@ function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) end end - -function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone ) +--- Deploy task function. Triggers Unload event. +-- @param Wrapper.Group#GROUP APC The cargo carrier group. +-- @param #AI_CARGO_APC self `AI_CARGO_APC` class. +-- @param Core.Point#COORDINATE Coordinate. The coordinate (not used). +-- @param Core.Zone#ZONE DeployZone Deploy zone. +function AI_CARGO_APC._Deploy(APC, self, Coordinate, DeployZone) APC:F( { "AI_CARGO_APC._Deploy:", APC } ) @@ -392,12 +438,20 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, He self.RoutePickup = true local _speed=Speed or APC:GetSpeedMax()*0.5 + + -- Route on road. + local Waypoints = {} + + if self.pickupOffroad then + Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.pickupFormation) + Waypoints[2]=Coordinate:WaypointGround(_speed, self.pickupFormation, DCSTasks) + else + Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) + end - local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone ) - - self:F({Waypoints = Waypoints}) + local Waypoint = Waypoints[#Waypoints] APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. @@ -434,7 +488,14 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, He _speed=math.min(_speed, speedmax) -- Route on road. - local Waypoints = APC:TaskGroundOnRoad(Coordinate, _speed, "Line abreast", true) + local Waypoints = {} + + if self.deployOffroad then + Waypoints[1]=APC:GetCoordinate():WaypointGround(Speed, self.deployFormation) + Waypoints[2]=Coordinate:WaypointGround(_speed, self.deployFormation, DCSTasks) + else + Waypoints=APC:TaskGroundOnRoad(Coordinate, _speed, ENUMS.Formation.Vehicle.OffRoad, true) + end -- Task function local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 2b3277bde..ede3d20bd 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.4) - Models the intelligent transportation of infantry (cargo). +--- **AI** - Models the intelligent transportation of cargo using airplanes. -- -- === -- @@ -408,9 +408,6 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone 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. @@ -438,14 +435,10 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Height, Uncontrolled -- To point. local AirbasePointVec2 = Airbase:GetPointVec2() - local ToWaypoint = AirbasePointVec2:WaypointAir( - POINT_VEC3.RoutePointAltType.BARO, - "Land", - "Landing", - Speed or Airplane:GetSpeedMax()*0.8 - ) - ToWaypoint["airdromeId"] = Airbase:GetID() - ToWaypoint["speed_locked"] = true + local ToWaypoint = AirbasePointVec2:WaypointAir(POINT_VEC3.RoutePointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) + + --ToWaypoint["airdromeId"] = Airbase:GetID() + --ToWaypoint["speed_locked"] = true -- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned. diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 424a0f814..ec725d5f8 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -1,4 +1,4 @@ ---- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. +--- **AI** - Models the intelligent transportation of infantry and other cargo. -- -- ## Features: -- @@ -1104,7 +1104,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor() -- The Pickup sequence ... -- Check if this Carrier need to go and Pickup something... -- So, if the cargo bay is not full yet with cargo to be loaded ... - self:I( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) + self:T( { Carrier = CarrierGroupName, IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then -- ok, so there is a free Carrier -- now find the first cargo that is Unloaded diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index b4bef21bc..cd530ab24 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** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs. +--- **AI** - Models the intelligent transportation of infantry and other cargo using APCs. -- -- ## Features: -- @@ -181,25 +181,36 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon return self end + +--- AI cargo +-- @param #AI_CARGO_DISPATCHER_APC self +-- @param Wrapper.Group#GROUP APC The APC carrier. +-- @param Core.Set#SET_CARGO CargoSet Cargo set. +-- @return AI.AI_Cargo_APC#AI_CARGO_DISPATCHER_APC AI cargo APC object. function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet ) - return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius ) + local aicargoapc=AI_CARGO_APC:New(APC, CargoSet, self.CombatRadius) + + aicargoapc:SetDeployOffRoad(self.deployOffroad, self.deployFormation) + aicargoapc:SetPickupOffRoad(self.pickupOffroad, self.pickupFormation) + + return aicargoapc end --- Enable/Disable unboarding of cargo (infantry) when enemies are nearby (to help defend the carrier). -- This is only valid for APCs and trucks etc, thus ground vehicles. -- @param #AI_CARGO_DISPATCHER_APC self -- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. --- When the combat radius is 0, no defense will happen of the carrier. +-- When the combat radius is 0 (default), no defense will happen of the carrier. -- When the combat radius is not provided, no defense will happen! -- @return #AI_CARGO_DISPATCHER_APC -- @usage -- -- -- Disembark the infantry when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( true ) +-- AICargoDispatcher:SetCombatRadius( 500 ) -- -- -- Keep the cargo in the carrier when the carrier is under attack. --- AICargoDispatcher:SetCombatRadius( false ) +-- AICargoDispatcher:SetCombatRadius( 0 ) function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius ) self.CombatRadius = CombatRadius or 0 @@ -207,3 +218,41 @@ function AI_CARGO_DISPATCHER_APC:SetCombatRadius( CombatRadius ) return self end +--- Set whether the carrier will *not* use roads to *pickup* and *deploy* the cargo. +-- @param #AI_CARGO_DISPATCHER_APC self +-- @param #boolean Offroad If true, carrier will not use roads. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_DISPATCHER_APC self +function AI_CARGO_DISPATCHER_APC:SetOffRoad(Offroad, Formation) + + self:SetPickupOffRoad(Offroad, Formation) + self:SetDeployOffRoad(Offroad, Formation) + + return self +end + +--- Set whether the carrier will *not* use roads to *pickup* the cargo. +-- @param #AI_CARGO_DISPATCHER_APC self +-- @param #boolean Offroad If true, carrier will not use roads. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_DISPATCHER_APC self +function AI_CARGO_DISPATCHER_APC:SetPickupOffRoad(Offroad, Formation) + + self.pickupOffroad=Offroad + self.pickupFormation=Formation or ENUMS.Formation.Vehicle.OffRoad + + return self +end + +--- Set whether the carrier will *not* use roads to *deploy* the cargo. +-- @param #AI_CARGO_DISPATCHER_APC self +-- @param #boolean Offroad If true, carrier will not use roads. +-- @param #number Formation Offroad formation used. Default is `ENUMS.Formation.Vehicle.Offroad`. +-- @return #AI_CARGO_DISPATCHER_APC self +function AI_CARGO_DISPATCHER_APC:SetDeployOffRoad(Offroad, Formation) + + self.deployOffroad=Offroad + self.deployFormation=Formation or ENUMS.Formation.Vehicle.OffRoad + + return self +end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 1f58b7b08..a9bae01c3 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.4) - Models the intelligent transportation of infantry (cargo). +--- **AI** - Models the intelligent transportation of cargo using helicopters. -- -- === -- @@ -364,26 +364,11 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina local Route = {} --- local CoordinateFrom = Helicopter:GetCoordinate() --- local WaypointFrom = CoordinateFrom:WaypointAir( --- "RADIO", --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, --- Speed, --- true --- ) --- Route[#Route+1] = WaypointFrom local CoordinateTo = Coordinate local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - 50, - true - ) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, 50, true) Route[#Route+1] = WaypointTo local Tasks = {} @@ -393,7 +378,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina Route[#Route+1] = WaypointTo -- Now route the helicopter - Helicopter:Route( Route, 0 ) + Helicopter:Route(Route, 0) end end @@ -422,7 +407,6 @@ function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, Deplo self:GetParent( self, AI_CARGO_HELICOPTER ).onafterDeployed( self, Helicopter, From, Event, To, DeployZone ) - end --- On after Pickup event. @@ -453,26 +437,14 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin local CoordinateFrom = Helicopter:GetCoordinate() --- Create a route point of type air. - local WaypointFrom = CoordinateFrom:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - _speed, - true - ) + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) --- Create a route point of type air. local CoordinateTo = Coordinate local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - _speed, - true - ) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,_speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointTo @@ -532,13 +504,7 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - _speed, - true - ) + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom @@ -548,13 +514,7 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - _speed, - true - ) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo @@ -564,7 +524,9 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin local Tasks = {} + -- The _Deploy function does not exist. 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() ) @@ -607,13 +569,8 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() - local WaypointFrom = CoordinateFrom:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - Speed , - true - ) + + local WaypointFrom = CoordinateFrom:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointFrom --- Create a route point of type air. @@ -621,13 +578,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat local landheight = CoordinateTo:GetLandHeight() -- get target height CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground - local WaypointTo = CoordinateTo:WaypointAir( - "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - Speed , - true - ) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointTo @@ -642,7 +593,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Route[#Route+1] = WaypointTo -- Now route the helicopter - Helicopter:Route( Route, 0 ) + Helicopter:Route(Route, 0) end diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 3e43b3ef7..df796aed1 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -1,4 +1,4 @@ ---- **Core** -- Management of CARGO logistics, that can be transported from and to transportation carriers. +--- **Cargo** - Management of CARGO logistics, that can be transported from and to transportation carriers. -- -- === -- diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 4a55946e3..2d59f75d3 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -1,4 +1,4 @@ ---- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object. +--- **Cargo** - Management of grouped cargo logistics, which are based on a @{Wrapper.Group} object. -- -- === -- diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index 4d0093b47..92baf40a4 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -1,4 +1,4 @@ ---- **Cargo** -- Management of single cargo logistics, which are based on a @{Wrapper.Unit} object. +--- **Cargo** - Management of single cargo logistics, which are based on a @{Wrapper.Unit} object. -- -- === -- From 94f206af68df41e5f3b0d1aa40421dbf3372e587 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 Jan 2021 13:49:35 +0100 Subject: [PATCH 065/382] AIRBASE - Added black and white parking spot lists. --- Moose Development/Moose/Core/Spawn.lua | 40 +++++---- .../Moose/Functional/Warehouse.lua | 6 +- Moose Development/Moose/Wrapper/Airbase.lua | 88 ++++++++++++++++++- 3 files changed, 113 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index ebcfb1f99..89d6d681c 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1718,27 +1718,29 @@ 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 -- Helo is spawned. Try exclusive helo spots first. self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) - spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata) nfree=#spots if nfree0 then + for _,terminalID in pairs(self.parkingBlacklist or {}) do + if terminalID==TerminalID then + -- This is a invalid spot. + return false + end + end + end + + + -- Check if a whitelist was defined. + if self.parkingWhitelist and #self.parkingWhitelist>0 then + for _,terminalID in pairs(self.parkingWhitelist or {}) do + if terminalID==TerminalID then + -- This is a valid spot. + return true + end + end + -- No match ==> invalid spot + return false + end + + -- Neither black nor white lists were defined or spot is not in black list. + return true +end + --- Helper function to check for the correct terminal type including "artificial" ones. -- @param #number Term_Type Termial type from getParking routine. -- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator. From a6e16fb7ad3bf6c32306bf29b8a4d118bb336231 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 12 Jan 2021 17:40:21 +0100 Subject: [PATCH 066/382] Update Airbase.lua Fixed bug in white list --- Moose Development/Moose/Wrapper/Airbase.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index d3d3bab56..317c3b448 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -663,10 +663,10 @@ function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist) -- Ensure we got a table. if type(TerminalIdWhitelist)~="table" then - TerminalIdWhitelist={self.parkingBlacklist} + TerminalIdWhitelist={TerminalIdWhitelist} end - self.parkingWhitelist={TerminalIdWhitelist} + self.parkingWhitelist=TerminalIdWhitelist return self end @@ -687,7 +687,7 @@ function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) -- Ensure we got a table. if type(TerminalIdBlacklist)~="table" then - TerminalIdBlacklist={self.parkingBlacklist} + TerminalIdBlacklist={TerminalIdBlacklist} end self.parkingBlacklist=TerminalIdBlacklist From eb0b43cea861f4a25eb146af8b33f568b2eb70c0 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 13 Jan 2021 08:32:45 +0100 Subject: [PATCH 067/382] MANTIS 0.3.6 -- clean up docs -- added a function to set a new SAM range whilst running --- Moose Development/Moose/Functional/Mantis.lua | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index c97068865..a4ca58c19 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1,4 +1,4 @@ ---- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses + --- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses -- -- === -- @@ -20,23 +20,23 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Dec 2020 +-- Date: Jan 2021 ------------------------------------------------------------------------- ---- **MANTIS** class, extends @{Core.Base#BASE} --- @type MANTIS +--- **MANTIS** class, extends @{#Core.Base#BASE} +-- @type MANTIS #MANTIS -- @field #string Classname -- @field #string name Name of this Mantis -- @field #string SAM_Templates_Prefix Prefix to build the #GROUP_SET for SAM sites --- @field @{Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET +-- @field @{#Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET -- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group --- @field @{Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET --- @field @{Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode +-- @field @{#Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET +-- @field @{#Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object --- @field @{Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ +-- @field @{#Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging --- @field @{Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR +-- @field @{#Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR -- @field @{Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS -- @field #boolean debug Switch on extra messages -- @field #boolean verbose Switch on extra logging @@ -51,7 +51,7 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit --- @extends @{Core.Base#BASE} +-- @extends @{#Core.Base#BASE} --- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat @@ -97,6 +97,7 @@ -- # 2. Start up your MANTIS with a basic setting -- -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` +-- `myredmantis:Start()` -- -- [optional] Use -- @@ -106,13 +107,12 @@ -- * `MANTIS:SetDetectInterval(interval)` -- * `MANTIS:SetAutoRelocate(hq, ewr)` -- --- to fine-tune your setup. --- --- `myredmantis:Start()` +-- before starting #MANTIS to fine-tune your setup. -- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: -- -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` +-- `mybluemantis:Start()` -- -- # 3. Default settings -- @@ -171,7 +171,7 @@ MANTIS = { ----------------------------------------------------------------------- do - --- Function instantiate new class + --- Function to instantiate a new object of class MANTIS --@param #MANTIS self --@param #string name Name of this MANTIS for reporting --@param #string samprefix Prefixes for the SAM groups from the ME, e.g. all groups starting with "Red Sam..." @@ -217,7 +217,7 @@ do end -- @field #string version - self.version="0.3.5" + self.version="0.3.6" env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) -- Set the string id for output to DCS.log file. @@ -307,6 +307,19 @@ do self.engagerange = range end + --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night + -- @param #MANTIS self + -- @param #number range Percent of the max fire range + function MANTIS:SetNewSAMRangeWhileRunning(range) + local range = range or 75 + if range < 0 or range > 100 then + range = 75 + end + self.engagerange = range + self:_RefreshSAMTable() + self.mysead.EngagementRange = range + end + --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on From 29727ec9a6550d79b2654e8423ef7c16c968b4bc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 13 Jan 2021 08:44:10 +0100 Subject: [PATCH 068/382] Update AI_A2A_Dispatcher.lua Closed gap in Docu --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index eae6ded44..941ac29e5 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -658,7 +658,9 @@ do -- AI_A2A_DISPATCHER -- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees. -- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location. -- - -- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the + -- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the CAP command used earlier on, e.g. + -- + -- A2ADispatcher:SetSquadronCap( "Mineralnye", CAPZoneEast, 4000, 10000, 500, 600, 800, 900 ) -- -- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the -- whole pattern lies within the patrol zone. From 9440d2b2c377e0881039fa0e48fdcb4584322e29 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 13 Jan 2021 08:47:20 +0100 Subject: [PATCH 069/382] Update Controllable.lua missing # in docu --- Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 38b7f147b..d46e62dd1 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -129,7 +129,7 @@ -- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route. -- * @{#CONTROLLABLE.RouteGroundTo}(): Make the GROUND Controllable to drive towards a specific coordinate. -- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate. --- * @{CONTROLLABLE.RelocateGroundRandomInRadius}(): Relocate the GROUND controllable to a random point in a given radius. +-- * @{#CONTROLLABLE.RelocateGroundRandomInRadius}(): Relocate the GROUND controllable to a random point in a given radius. -- -- # 5) Option methods -- From f3a2cf728491312c813a6b02110ea6c13b37cab4 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 Jan 2021 00:59:52 +0100 Subject: [PATCH 070/382] Patrol - Added `FLIGHTGROUP:SetEngageDetectedOn()` - AUFTRAG patrol zone. --- Moose Development/Moose/Ops/ArmyGroup.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 46 ++++- Moose Development/Moose/Ops/FlightGroup.lua | 177 ++++++++++++++++++-- Moose Development/Moose/Ops/OpsGroup.lua | 80 ++++++--- 4 files changed, 268 insertions(+), 37 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 28857197e..f27cd357a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -830,7 +830,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) end ---- On after "EngageTarget" event. +--- Update engage target. -- @param #ARMYGROUP self function ARMYGROUP:_UpdateEngageTarget() diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index b7e19f41c..2711c190a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -315,6 +315,7 @@ _AUFTRAGSNR=0 -- @field #string TANKER Tanker mission. -- @field #string TROOPTRANSPORT Troop transport mission. -- @field #string ARTY Fire at point. +-- @field #string PATROLZONE Patrol a zone. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -338,6 +339,7 @@ AUFTRAG.Type={ TANKER="Tanker", TROOPTRANSPORT="Troop Transport", ARTY="Fire At Point", + PATROLZONE="Patrol Zone", } --- Mission status. @@ -1191,6 +1193,28 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) return mission end +--- Create a PATROLZONE mission. Group(s) will go to the zone and patrol it randomly. +-- @param #AUFTRAG self +-- @param Core.Zone#ZONE Zone The patrol zone. +-- @return #AUFTRAG self +function AUFTRAG:NewPATROLZONE(Zone) + + local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) + + mission:_TargetFromObject(Zone) + + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionROT=ENUMS.ROT.PassiveDefense + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=1.0 + + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + --- Create a mission to attack a group. Mission type is automatically chosen from the group category. -- @param #AUFTRAG self -- @param Ops.Target#TARGET Target The target. @@ -2926,7 +2950,7 @@ end -- @param #AUFTRAG self -- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things. function AUFTRAG:GetObjective() - return self:GetTargetData().Target + return self:GetTargetData():GetObject() end --- Get type of target. @@ -3388,6 +3412,26 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, self:GetTargetVec2(), self.artyRadius, self.artyShots, self.engageWeaponType) table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.PATROLZONE then + + ------------------------- + -- PATROL ZONE Mission -- + ------------------------- + + local DCStask={} + + DCStask.id="PatrolZone" + + -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. + local param={} + param.zonename=self:GetTargetName() + param.zone=self:GetObjective() + param.altitude=70 + + DCStask.params=param + + table.insert(DCStasks, DCStask) else self:E(self.lid..string.format("ERROR: Unknown mission task!")) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1d64e7e22..5a8d8cc68 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -512,6 +512,41 @@ function FLIGHTGROUP:SetFuelCriticalRTB(switch) return self end +--- Enable to automatically engage detected targets. +-- @param #FLIGHTGROUP self +-- @param #number RangeMax Max range in NM. Only detected targets within this radius will be engaged. Default is 25 NM. +-- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. +-- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. +-- @return #OPSGROUP self +function FLIGHTGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + + if TargetTypes then + if type(TargetTypes)~="table" then + TargetTypes={TargetTypes} + end + else + TargetTypes={"All"} + end + + self.engagedetectedOn=true + self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) + self.engagedetectedTypes=TargetTypes + self.engagedetectedEngageZones=EngageZoneSet + self.engagedetectedNoEngageZones=NoEngageZoneSet + + return self +end + +--- Disable to automatically engage detected targets. +-- @param #FLIGHTGROUP self +-- @return #OPSGROUP self +function FLIGHTGROUP:SetEngageDetectedOff() + self.engagedetectedOn=false +end + + + --- Enable that the group is despawned after landing. This can be useful to avoid DCS taxi issues with other AI or players or jamming taxiways. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self @@ -976,6 +1011,87 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:ClearToLand() end end + + if self:IsAirborne() and self.detectionOn and self.engagedetectedOn then + + env.info("FF check detected:") + + local targetgroup=nil --Wrapper.Group#GROUP + local targetdist=math.huge + + -- Loop over detected groups. + for _,_group in pairs(self.detectedgroups:GetSet()) do + local group=_group --Wrapper.Group#GROUP + + env.info("group "..group:GetName()) + + if group and group:IsAlive() then + + local targetcoord=group:GetCoordinate() + + local distance=targetcoord:Get2DDistance(self:GetCoordinate()) + + if distance<=self.engagedetectedRmax and distance Mission Done! local Mission=self:GetMissionByTaskID(Task.id) @@ -2324,6 +2336,11 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) --Mission paused. Do nothing! end else + + if Task.description=="Engage_Target" then + self:Disengage() + end + self:T(self.lid.."Task Done but NO mission found ==> _CheckGroupDone in 1 sec") self:_CheckGroupDone(1) end @@ -2984,33 +3001,48 @@ end -- @param #string To To state. -- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed. function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) - - -- Apply tasks of this waypoint. - local ntasks=self:_SetWaypointTasks(Waypoint) - - -- Get waypoint index. - local wpindex=self:GetWaypointIndex(Waypoint.uid) - -- Final waypoint reached? - if wpindex==nil or wpindex==#self.waypoints then - - -- Set switch to true. - if not self.adinfinitum or #self.waypoints<=1 then - self.passedfinalwp=true - end + -- Get the current task. + local task=self:GetTaskCurrent() + + if task and task.id=="PatrolZone" then + + local zone=task.dcstask.params.zone --Core.Zone#ZONE + + local Coordinate=zone:GetRandomCoordinate() + FLIGHTGROUP.AddWaypoint(self,Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + + else + + -- Apply tasks of this waypoint. + local ntasks=self:_SetWaypointTasks(Waypoint) + + -- Get waypoint index. + local wpindex=self:GetWaypointIndex(Waypoint.uid) + + -- Final waypoint reached? + if wpindex==nil or wpindex==#self.waypoints then + + -- Set switch to true. + if not self.adinfinitum or #self.waypoints<=1 then + self.passedfinalwp=true + end + + end + + -- Check if all tasks/mission are done? + -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. + if ntasks==0 then + self:_CheckGroupDone(0.1) + end + + -- Debug info. + local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", + tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar)) + self:T(self.lid..text) + end - - -- Check if all tasks/mission are done? - -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if ntasks==0 then - self:_CheckGroupDone(0.1) - end - - -- Debug info. - local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", - tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar)) - self:T(self.lid..text) end From 73b1394ca7555c82e402acacde48a0c0ab0fd54d Mon Sep 17 00:00:00 2001 From: OttoWerkr <31276014+OttoWerkr@users.noreply.github.com> Date: Fri, 15 Jan 2021 14:27:43 -0500 Subject: [PATCH 071/382] Update Scenery.lua Added FindByName() to SCENERY Class. --- Moose Development/Moose/Wrapper/Scenery.lua | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index 9eccde422..445154037 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -57,3 +57,40 @@ end function SCENERY:GetThreatLevel() return 0, "Scenery" end + +--- Find a SCENERY object by it's name/id. +--@param #SCENERY self +--@param #string name The name/id of the scenery object as taken from the ME. Ex. '595785449' +--@return #SCENERY Scenery Object or nil if not found. +function SCENERY:FindByName(name) + local findAirbase = function () + local airbases = AIRBASE.GetAllAirbases() + for index,airbase in pairs(airbases) do + local surftype = airbase:GetCoordinate():GetSurfaceType() + if surftype ~= land.SurfaceType.SHALLOW_WATER and surftype ~= land.SurfaceType.WATER then + return airbase:GetCoordinate() + end + end + return nil + end + + local sceneryScan = function (scancoord) + if scancoord ~= nil then + local _,_,sceneryfound,_,_,scenerylist = scancoord:ScanObjects(200, false, false, true) + if sceneryfound == true then + scenerylist[1].id_ = name + SCENERY.SceneryObject = SCENERY:Register(scenerylist[1].id_, scenerylist[1]) + return SCENERY.SceneryObject + end + end + return nil + end + + if SCENERY.SceneryObject then + SCENERY.SceneryObject.SceneryObject.id_ = name + SCENERY.SceneryObject.SceneryName = name + return SCENERY:Register(SCENERY.SceneryObject.SceneryObject.id_, SCENERY.SceneryObject.SceneryObject) + else + return sceneryScan(findAirbase()) + end +end From 8f562993807885c17442cf740e97829e058a5c88 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 Jan 2021 23:29:16 +0100 Subject: [PATCH 072/382] OPS **AUFTRAG** - Added PATROLZONE type. Works for all OPSGROUPs (air, ground and sea). **FLIGHTGROUP** - Improved EngageTarget - Added option to automatically engage detected targets. **TARGET** - Improved Zone type --- Moose Development/Moose/Ops/ArmyGroup.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 19 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 96 ++++++++++++++------- Moose Development/Moose/Ops/NavyGroup.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 60 ++++++++++--- Moose Development/Moose/Ops/Target.lua | 37 ++++++-- 6 files changed, 158 insertions(+), 56 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f27cd357a..a5754efd9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -101,6 +101,7 @@ function ARMYGROUP:New(Group) self.lid=string.format("ARMYGROUP %s | ", self.groupname) -- Defaults + self.isArmygroup=true self:SetDefaultROE() self:SetDefaultAlarmstate() self:SetDetection() diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2711c190a..7a952cba8 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -101,6 +101,7 @@ -- -- @field #string missionTask Mission task. See `ENUMS.MissionTask`. -- @field #number missionAltitude Mission altitude in meters. +-- @field #number missionSpeed Mission speed in km/h. -- @field #number missionFraction Mission coordiante fraction. Default is 0.5. -- @field #number missionRange Mission range in meters. Used in AIRWING class. -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. @@ -441,7 +442,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.5.0" +AUFTRAG.version="0.6.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1196,18 +1197,22 @@ end --- Create a PATROLZONE mission. Group(s) will go to the zone and patrol it randomly. -- @param #AUFTRAG self -- @param Core.Zone#ZONE Zone The patrol zone. +-- @param #number Speed Speed in knots. +-- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL. -- @return #AUFTRAG self -function AUFTRAG:NewPATROLZONE(Zone) +function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude) local mission=AUFTRAG:New(AUFTRAG.Type.PATROLZONE) - mission:_TargetFromObject(Zone) - + mission:_TargetFromObject(Zone) + mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.PassiveDefense mission.optionAlarm=ENUMS.AlarmState.Auto - mission.missionFraction=1.0 + mission.missionFraction=1.0 + mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil + mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or nil mission.DCStask=mission:GetDCSMissionTask() @@ -3425,9 +3430,9 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. local param={} - param.zonename=self:GetTargetName() param.zone=self:GetObjective() - param.altitude=70 + param.altitude=self.missionAltitude + param.speed=self.missionSpeed DCStask.params=param diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5a8d8cc68..fb5d28510 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -251,6 +251,7 @@ function FLIGHTGROUP:New(group) self:SetDefaultROE() self:SetDefaultROT() self:SetDetection() + self.isFlightgroup=true -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) @@ -277,10 +278,9 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. Not implemented yet! self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2G missiles. Not implemented yet! - self:AddTransition("Airborne", "EngageTargets", "Engaging") -- Engage targets. + self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. - self:AddTransition("*", "ElementParking", "*") -- An element is parking. self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines. self:AddTransition("*", "ElementTaxiing", "*") -- An element is taxiing to the runway. @@ -291,7 +291,6 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "ElementOutOfAmmo", "*") -- An element is completely out of ammo. - self:AddTransition("*", "Parking", "Parking") -- The whole flight group is parking. self:AddTransition("*", "Taxiing", "Taxiing") -- The whole flight group is taxiing. self:AddTransition("*", "Takeoff", "Airborne") -- The whole flight group is airborne. @@ -514,13 +513,14 @@ end --- Enable to automatically engage detected targets. -- @param #FLIGHTGROUP self --- @param #number RangeMax Max range in NM. Only detected targets within this radius will be engaged. Default is 25 NM. +-- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. -- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". -- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. -- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. --- @return #OPSGROUP self +-- @return #FLIGHTGROUP self function FLIGHTGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + -- Ensure table. if TargetTypes then if type(TargetTypes)~="table" then TargetTypes={TargetTypes} @@ -528,13 +528,27 @@ function FLIGHTGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, N else TargetTypes={"All"} end + + -- Ensure SET_ZONE if ZONE is provided. + if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) + EngageZoneSet=zoneset + end + if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) + NoEngageZoneSet=zoneset + end + -- Set parameters. self.engagedetectedOn=true self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) self.engagedetectedTypes=TargetTypes self.engagedetectedEngageZones=EngageZoneSet self.engagedetectedNoEngageZones=NoEngageZoneSet + -- Ensure detection is ON or it does not make any sense. + self:SetDetection(true) + return self end @@ -543,10 +557,10 @@ end -- @return #OPSGROUP self function FLIGHTGROUP:SetEngageDetectedOff() self.engagedetectedOn=false + return self end - --- Enable that the group is despawned after landing. This can be useful to avoid DCS taxi issues with other AI or players or jamming taxiways. -- @param #FLIGHTGROUP self -- @return #FLIGHTGROUP self @@ -1014,8 +1028,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self:IsAirborne() and self.detectionOn and self.engagedetectedOn then - env.info("FF check detected:") - + -- Target. local targetgroup=nil --Wrapper.Group#GROUP local targetdist=math.huge @@ -1023,8 +1036,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) for _,_group in pairs(self.detectedgroups:GetSet()) do local group=_group --Wrapper.Group#GROUP - env.info("group "..group:GetName()) - if group and group:IsAlive() then local targetcoord=group:GetCoordinate() @@ -1077,7 +1088,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if insideEngage and not insideNoEngage then targetdist=distance targetgroup=group - env.info("targetgroup "..group:GetName()) end end @@ -1087,8 +1097,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end if targetgroup then - env.info("engage target! "..targetgroup:GetName()) - self:EngageTargets(targetgroup) + self:EngageTarget(targetgroup) end end @@ -1911,8 +1920,14 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) end if self.taskcurrent>0 then - self:E(self.lid.."Update route denied because taskcurrent>0") - allowed=false + + local task=self:GetTaskCurrent() + if task.dcstask.id=="PatrolZone" then + -- For patrol zone, we need to allow the update. + else + self:E(self.lid.."Update route denied because taskcurrent>0") + allowed=false + end end -- Not good, because mission will never start. Better only check if there is a current task! @@ -2035,6 +2050,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:UnpauseMission() return end + + -- Group is currently engaging. + if self:IsEngaging() then + return + end -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() @@ -2493,24 +2513,24 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) end ---- On after "EngageTargets" event. Order to engage a set of units. +--- On after "EngageTarget" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Set#SET_UNIT TargetUnitSet -function FLIGHTGROUP:onafterEngageTargets(From, Event, To, Target) - - env.info("FF engage targets") +-- @param #table Target Target object. Can be a UNIT, STATIC, GROUP, SET_UNIT or SET_GROUP object. +function FLIGHTGROUP:onafterEngageTarget(From, Event, To, Target) + -- DCS task. local DCStask=nil - if Target:IsInstanceOf("UNIT") then + -- Check target object. + if Target:IsInstanceOf("UNIT") or Target:IsInstanceOf("STATIC") then + + DCStask=self:GetGroup():TaskAttackUnit(Target, true) elseif Target:IsInstanceOf("GROUP") then - - env.info("FF engage targets GROUP!") - + DCStask=self:GetGroup():TaskAttackGroup(Target, nil, nil, nil, nil, nil, nil, true) elseif Target:IsInstanceOf("SET_UNIT") then @@ -2527,13 +2547,25 @@ function FLIGHTGROUP:onafterEngageTargets(From, Event, To, Target) DCStask=self:GetGroup():TaskCombo(DCSTasks) elseif Target:IsInstanceOf("SET_GROUP") then + + local DCSTasks={} + + for _,_unit in pairs(Target:GetSet()) do --detected by =HRP= Zero + local unit=_unit --Wrapper.Unit#UNIT + local task=self:GetGroup():TaskAttackGroup(Target, nil, nil, nil, nil, nil, nil, true) + table.insert(DCSTasks) + end + + -- Task combo. + DCStask=self:GetGroup():TaskCombo(DCSTasks) else - + self:E("ERROR: unknown Target in EngageTarget! Needs to be a UNIT, STATIC, GROUP, SET_UNIT or SET_GROUP") + return end - -- Create new task.The description "Task_Engage" is checked. - local Task=self:NewTaskScheduled(DCStask, 1, "Task_Engage", 0) + -- Create new task.The description "Engage_Target" is checked so do not change that lightly. + local Task=self:NewTaskScheduled(DCStask, 1, "Engage_Target", 0) -- Backup ROE setting. Task.backupROE=self:GetROE() @@ -2541,6 +2573,12 @@ function FLIGHTGROUP:onafterEngageTargets(From, Event, To, Target) -- Switch ROE to open fire self:SwitchROE(ENUMS.ROE.OpenFire) + -- Pause current mission. + local mission=self:GetMissionCurrent() + if mission then + self:PauseMission() + end + -- Execute task. self:TaskExecute(Task) @@ -2553,9 +2591,7 @@ end -- @param #string To To state. -- @param Core.Set#SET_UNIT TargetUnitSet function FLIGHTGROUP:onafterDisengage(From, Event, To) - - env.info("FF disengage targets") - + self:T(self.lid.."Disengage target") end --- On before "LandAt" event. Check we have a helo group. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 6c6f54d44..7fa71c335 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -123,6 +123,7 @@ function NAVYGROUP:New(GroupName) self:SetDefaultAlarmstate() self:SetPatrolAdInfinitum(true) self:SetPathfinding(false) + self.isNavygroup=true -- Add FSM transitions. -- From State --> Event --> To State diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 0638bfbf5..a79f5e1c4 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -22,6 +22,9 @@ -- @field #table template Template of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. +-- @field #boolean isFlightgroup Is a FLIGHTGROUP. +-- @field #boolean isArmygroup Is an ARMYGROUP. +-- @field #boolean isNavygroup Is a NAVYGROUP. -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -327,7 +330,7 @@ OPSGROUP.TaskType={ --- NavyGroup version. -- @field #string version -OPSGROUP.version="0.7.0" +OPSGROUP.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2133,7 +2136,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) if self.taskcurrent>0 then self:TaskCancel() end - + -- Set current task. self.taskcurrent=Task.id @@ -2169,10 +2172,25 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) elseif Task.dcstask.id=="PatrolZone" then - local Coordinate=Task.dcstask.params.zone:GetCoordinate() - - FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) - + --- + -- Task patrol zone. + --- + + -- Parameters. + local zone=Task.dcstask.params.zone --Core.Zone#ZONE + local Coordinate=zone:GetRandomCoordinate() + local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) + local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil + + -- New waypoint. + if self.isFlightgroup then + FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + elseif self.isNavygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isArmygroup then + NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + end + else -- If task is scheduled (not waypoint) set task. @@ -2252,6 +2270,8 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) if Task.dcstask.id=="Formation" then Task.formation:Stop() done=true + elseif Task.dcstask.id=="PatrolZone" then + done=true elseif stopflag==1 or (not self:IsAlive()) or self:IsDead() or self:IsStopped() then -- Manual call TaskDone if setting flag to one was not successful. done=true @@ -2272,7 +2292,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) end else - + local text=string.format("WARNING: No (current) task to cancel!") self:E(self.lid..text) @@ -2307,7 +2327,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) -- Debug message. local text=string.format("Task done: %s ID=%d", Task.description, Task.id) - self:T(self.lid..text) + self:I(self.lid..text) -- No current task. if Task.id==self.taskcurrent then @@ -2872,6 +2892,8 @@ function OPSGROUP:RouteToMission(mission, delay) end end + + elseif mission.type==AUFTRAG.Type.PATROLZONE then end @@ -3005,13 +3027,24 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Get the current task. local task=self:GetTaskCurrent() - if task and task.id=="PatrolZone" then + if task and task.dcstask.id=="PatrolZone" then - local zone=task.dcstask.params.zone --Core.Zone#ZONE - - local Coordinate=zone:GetRandomCoordinate() + -- Remove old waypoint. + self:RemoveWaypointByID(Waypoint.uid) + + local zone=task.dcstask.params.zone --Core.Zone#ZONE + local Coordinate=zone:GetRandomCoordinate() + local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) + local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil - FLIGHTGROUP.AddWaypoint(self,Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + if self.isFlightgroup then + FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + elseif self.isNavygroup then + ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation) + elseif self.isArmygroup then + NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) + end + else @@ -4208,7 +4241,6 @@ function OPSGROUP:_CheckAmmoStatus() self.outofAmmo=false end if ammo.Total==0 and not self.outofAmmo then - env.info("FF out of ammo") self.outofAmmo=true self:OutOfAmmo() end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 41a53905a..7aea445b2 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -72,6 +72,7 @@ TARGET = { -- @field #string SCENERY Target is a SCENERY object. -- @field #string COORDINATE Target is a COORDINATE. -- @field #string AIRBASE Target is an AIRBASE. +-- @field #string ZONE Target is a ZONE object. TARGET.ObjectType={ GROUP="Group", UNIT="Unit", @@ -79,6 +80,7 @@ TARGET.ObjectType={ SCENERY="Scenery", COORDINATE="Coordinate", AIRBASE="Airbase", + ZONE="Zone", } @@ -89,12 +91,14 @@ TARGET.ObjectType={ -- @field #string NAVAL -- @field #string AIRBASE -- @field #string COORDINATE +-- @field #string ZONE TARGET.Category={ AIRCRAFT="Aircraft", GROUND="Ground", NAVAL="Naval", AIRBASE="Airbase", COORDINATE="Coordinate", + ZONE="Zone", } --- Object status. @@ -124,7 +128,7 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.3.0" +TARGET.version="0.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -658,12 +662,12 @@ function TARGET:_AddObject(Object) elseif Object:IsInstanceOf("ZONE_BASE") then local zone=Object --Core.Zone#ZONE_BASE - Object=zone:GetCoordinate() + Object=zone --:GetCoordinate() - target.Type=TARGET.ObjectType.COORDINATE + target.Type=TARGET.ObjectType.ZONE target.Name=zone:GetName() - target.Coordinate=Object + target.Coordinate=zone:GetCoordinate() target.Life0=1 target.Life=1 @@ -773,7 +777,13 @@ function TARGET:GetTargetLife(Target) elseif Target.Type==TARGET.ObjectType.COORDINATE then return 1 + + elseif Target.Type==TARGET.ObjectType.ZONE then + + return 1 + else + self:E("ERROR: unknown target object type in GetTargetLife!") end end @@ -865,6 +875,13 @@ function TARGET:GetTargetVec3(Target) local vec3={x=object.x, y=object.y, z=object.z} return vec3 + elseif Target.Type==TARGET.ObjectType.ZONE then + + local object=Target.Object --Core.Zone#ZONE + + local vec3=object:GetVec3() + return vec3 + end self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3") @@ -1032,7 +1049,13 @@ function TARGET:GetTargetCategory(Target) elseif Target.Type==TARGET.ObjectType.COORDINATE then return TARGET.Category.COORDINATE - + + elseif Target.Type==TARGET.ObjectType.ZONE then + + return TARGET.Category.ZONE + + else + self:E("ERROR: unknown target category!") end return category @@ -1141,6 +1164,10 @@ function TARGET:CountObjectives(Target) -- No target we can check! + elseif Target.Type==TARGET.ObjectType.ZONE then + + -- No target we can check! + else self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") end From 209b7a64edd52535c28d854df5070df17ba39f79 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 15 Jan 2021 23:42:30 +0100 Subject: [PATCH 073/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 45 +++++++++++++----------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a79f5e1c4..51782feae 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2327,7 +2327,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) -- Debug message. local text=string.format("Task done: %s ID=%d", Task.description, Task.id) - self:I(self.lid..text) + self:T(self.lid..text) -- No current task. if Task.id==self.taskcurrent then @@ -2647,7 +2647,7 @@ function OPSGROUP:onafterPauseMission(From, Event, To) local Task=Mission:GetGroupWaypointTask(self) -- Debug message. - self:I(self.lid..string.format("Pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + self:T(self.lid..string.format("Pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) -- Cancelling the mission is actually cancelling the current task. self:TaskCancel(Task) @@ -2666,7 +2666,8 @@ end -- @param #string To To state. function OPSGROUP:onafterUnpauseMission(From, Event, To) - self:I(self.lid..string.format("Unpausing mission")) + -- Debug info. + self:T(self.lid..string.format("Unpausing mission")) if self.missionpaused then @@ -4796,25 +4797,29 @@ function OPSGROUP:SwitchAlarmstate(alarmstate) if self:IsAlive() or self:IsInUtero() then - self.option.Alarm=alarmstate or self.optionDefault.Alarm - - if self:IsInUtero() then - self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) - else + if self.isArmygroup or self.isNavygroup then - if self.option.Alarm==0 then - self.group:OptionAlarmStateAuto() - elseif self.option.Alarm==1 then - self.group:OptionAlarmStateGreen() - elseif self.option.Alarm==2 then - self.group:OptionAlarmStateRed() - else - self:E("ERROR: Unknown Alarm State! Setting to AUTO") - self.group:OptionAlarmStateAuto() - self.option.Alarm=0 - end + self.option.Alarm=alarmstate or self.optionDefault.Alarm - self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) + if self:IsInUtero() then + self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) + else + + if self.option.Alarm==0 then + self.group:OptionAlarmStateAuto() + elseif self.option.Alarm==1 then + self.group:OptionAlarmStateGreen() + elseif self.option.Alarm==2 then + self.group:OptionAlarmStateRed() + else + self:E("ERROR: Unknown Alarm State! Setting to AUTO") + self.group:OptionAlarmStateAuto() + self.option.Alarm=0 + end + + self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) + + end end else From d10adb5de8ab679da0c5342e35b252aeec0d0647 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 16 Jan 2021 14:50:36 +0100 Subject: [PATCH 074/382] Update AI_A2G_Dispatcher.lua Corrected Docu line 434 --- Moose Development/Moose/AI/AI_A2G_Dispatcher.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 91d990afc..3db79d137 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -431,7 +431,7 @@ do -- AI_A2G_DISPATCHER -- -- ### 2.2. The **Defense Reactivity**. -- - -- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is + -- There are three levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is -- also determined by the distance of the enemy ground target to the defense coordinate. -- If you want to have a **low** defense reactivity, that is, the probability that an A2G defense will engage to the enemy ground target, then -- use the @{#AI_A2G_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods From c62c21c38639a4eef41ddc7c4671ac31936e695b Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 16 Jan 2021 22:56:28 +0100 Subject: [PATCH 075/382] FLIGHTGROUP v0.6.1 - Improved engage targets --- Moose Development/Moose/Ops/FlightGroup.lua | 47 +++++++++++++++------ Moose Development/Moose/Ops/OpsGroup.lua | 7 ++- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index fb5d28510..ce641b949 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -194,7 +194,7 @@ FLIGHTGROUP.Attribute = { --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.6.0" +FLIGHTGROUP.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -935,7 +935,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Distance travelled --- - if self.verbose>=3 and self:IsAlive() then + if self.verbose>=4 and self:IsAlive() then -- Travelled distance since last check. local ds=self.travelds @@ -1025,8 +1025,11 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:ClearToLand() end end - - if self:IsAirborne() and self.detectionOn and self.engagedetectedOn then + + --- + -- Engage Detected Targets + --- + if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1038,16 +1041,20 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if group and group:IsAlive() then - local targetcoord=group:GetCoordinate() - - local distance=targetcoord:Get2DDistance(self:GetCoordinate()) + -- Get 3D vector of target. + local targetVec3=group:GetVec3() + + -- Distance to target. + local distance=UTILS.VecDist3D(self.position, targetVec3) if distance<=self.engagedetectedRmax and distance0 then - local task=self:GetTaskCurrent() - if task.dcstask.id=="PatrolZone" then - -- For patrol zone, we need to allow the update. + --local task=self:GetTaskCurrent() + local task=self:GetTaskByID(self.taskcurrent) + + if task then + if task.dcstask.id=="PatrolZone" then + -- For patrol zone, we need to allow the update. + else + local taskname=task and task.description or "No description" + self:E(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) + allowed=false + end else - self:E(self.lid.."Update route denied because taskcurrent>0") + -- Now this can happen, if we directly use TaskExecute as the task is not in the task queue and cannot be removed. + self:T(self.lid..string.format("WARNING: before update route taskcurrent=%d>0 but no task?!", self.taskcurrent)) + -- Anyhow, a task is running so we do not allow to update the route! allowed=false end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 51782feae..c202ab0ff 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2096,7 +2096,8 @@ end -- @param #OPSGROUP self -- @return #OPSGROUP.Task Current task or nil. function OPSGROUP:GetTaskCurrent() - return self:GetTaskByID(self.taskcurrent, OPSGROUP.TaskStatus.EXECUTING) + local task=self:GetTaskByID(self.taskcurrent, OPSGROUP.TaskStatus.EXECUTING) + return task end --- Get task by its id. @@ -2893,11 +2894,9 @@ function OPSGROUP:RouteToMission(mission, delay) end end - - elseif mission.type==AUFTRAG.Type.PATROLZONE then - end + -- Formation. local formation=nil if self.isGround and mission.optionFormation then formation=mission.optionFormation From f4cb6df8d4bb7cc9c1b3a8e92a1671b8d550273b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 Jan 2021 16:53:22 +0100 Subject: [PATCH 076/382] Ops respawn --- Moose Development/Moose/Ops/ArmyGroup.lua | 39 ++++++- Moose Development/Moose/Ops/FlightGroup.lua | 20 ---- Moose Development/Moose/Ops/OpsGroup.lua | 119 +++++++++++++++++++- 3 files changed, 152 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index a5754efd9..de31c0a03 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -394,11 +394,43 @@ function ARMYGROUP:onafterStatus(From, Event, To) else -- Info text. - local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) - self:T2(self.lid..text) + if self.verbose>=1 then + local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) + self:I(self.lid..text) + end end + --- + -- Elements + --- + + if self.verbose>=2 then + local text="Elements:" + for i,_element in pairs(self.elements) do + local element=_element --#ARMYGROUP.Element + + local name=element.name + local status=element.status + local unit=element.unit + --local life=unit:GetLifeRelative() or 0 + local life,life0=self:GetLifePoints(element) + + local life0=element.life0 + + -- Get ammo. + local ammo=self:GetAmmoElement(element) + + -- Output text for element. + text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d", + i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + end + if #self.elements==0 then + text=text.." none!" + end + self:I(self.lid..text) + end + --- -- Tasks & Missions @@ -965,8 +997,11 @@ function ARMYGROUP:OnEventBirth(EventData) if self.respawning then + self:I(self.lid.."Respawning unit "..tostring(unitname)) + local function reset() self.respawning=nil + self:_CheckGroupDone() end -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ce641b949..92f7e4edc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2025,26 +2025,6 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) end ---- On after "Respawn" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #table Template The template used to respawn the group. -function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) - - self:T(self.lid.."Respawning group!") - - local template=UTILS.DeepCopy(Template or self.template) - - if self.group and self.group:InAir() then - template.lateActivation=false - self.respawning=true - self.group=self.group:Respawn(template) - end - -end - --- Check if flight is done, i.e. -- -- * passed the final waypoint, diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c202ab0ff..bdc8d445d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -399,6 +399,7 @@ function OPSGROUP:New(Group) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. + self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -408,7 +409,6 @@ function OPSGROUP:New(Group) self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. - self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "PassingWaypoint", "*") -- Passing waypoint. self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle. @@ -497,12 +497,34 @@ end --- Returns the absolute (average) life points of the group. -- @param #OPSGROUP self +-- @param #OPSGROUP.Element Element (Optional) Only get life points of this element. -- @return #number Life points. If group contains more than one element, the average is given. -- @return #number Initial life points. -function OPSGROUP:GetLifePoints() - if self.group then - return self.group:GetLife(), self.group:GetLife0() +function OPSGROUP:GetLifePoints(Element) + + local life=0 + local life0=0 + + if Element then + + local unit=Element.unit + + if unit then + life=unit:GetLife() + life0=unit:GetLife0() + end + + else + + for _,element in pairs(self.elements) do + local l,l0=self:GetLifePoints(element) + life=life+l + life0=life+l0 + end + end + + return life, life0 end @@ -3800,6 +3822,95 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end +--- On after "Respawn" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #table Template The template used to respawn the group. +function OPSGROUP:onafterRespawn(From, Event, To, Template) + + self:I(self.lid.."Respawning group!") + + local template=UTILS.DeepCopy(Template or self.template) + + template.lateActivation=false + + self:_Respawn(template,Reset) + +end + +--- Respawn the group. +-- @param #OPSGROUP self +-- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. +-- @param #boolean Reset Reset positions if TRUE. +-- @return #OPSGROUP self +function OPSGROUP:_Respawn(Template, Reset) + + env.info("FF _Respawn") + + -- Given template or get old. + Template=Template or UTILS.DeepCopy(self.template) + + -- Get correct heading. + local function _Heading(course) + local h + if course<=180 then + h=math.rad(course) + else + h=-math.rad(360-course) + end + return h + end + + if self:IsAlive() then + + --- + -- Group is ALIVE + + -- Get units. + local units=self.group:GetUnits() + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit:GetName()==Unit.name then + local vec3=unit:GetVec3() + local heading=unit:GetHeading() + Unit.x=vec3.x + Unit.y=vec3.z + Unit.alt=vec3.y + Unit.heading=math.rad(heading) + Unit.psi=-Unit.heading + end + end + + end + + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. + self:Despawn(0, true) + + else + + end + + -- Currently respawning. + self.respawning=true + + self:I({Template=Template}) + + -- Spawn new group. + _DATABASE:Spawn(Template) + + -- Reset events. + --self:ResetEvents() + + return self +end + --- On before "Dead" event. -- @param #OPSGROUP self -- @param #string From From state. From 5146106885fda9f144424892e19432e9690f7314 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 19 Jan 2021 16:56:26 +0100 Subject: [PATCH 077/382] Update Controllable.lua --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index d46e62dd1..526a184a4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1897,7 +1897,7 @@ do -- Patrol methods local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( 0 ) + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) @@ -1950,7 +1950,7 @@ do -- Patrol methods -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( 20 ) + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) From 89aa08829eab164dc0ae7afd4806f4e736275a4a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:05:14 +0100 Subject: [PATCH 078/382] Update ATIS.lua Fix time to be negative if early AM when using time to ZULU or time to GMT difference --- Moose Development/Moose/Ops/ATIS.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 29b2918b2..688b743b7 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1318,6 +1318,10 @@ function ATIS:onafterBroadcast(From, Event, To) time=time-UTILS.GMTToLocalTimeDifference()*60*60 end + if time < 0 then + time = 24*60*60 - time --avoid negative time around midnight + end + local clock=UTILS.SecondsToClock(time) local zulu=UTILS.Split(clock, ":") local ZULU=string.format("%s%s", zulu[1], zulu[2]) From a6ff84c09a1d2715f7549c6354c16547dd5b2e0a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 21 Jan 2021 15:34:50 +0100 Subject: [PATCH 079/382] ATIS.lua update time calculation over midnight (logic error) Actually line 1322 + time here, sind it's negative ... :) --- Moose Development/Moose/Ops/ATIS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 688b743b7..044963a28 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1319,7 +1319,7 @@ function ATIS:onafterBroadcast(From, Event, To) end if time < 0 then - time = 24*60*60 - time --avoid negative time around midnight + time = 24*60*60 + time --avoid negative time around midnight end local clock=UTILS.SecondsToClock(time) From e9f92d225033d5cd6ee45a63660307fdc166bcd9 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 22 Jan 2021 18:34:05 +0100 Subject: [PATCH 080/382] Update Set.lua Break loop after x tries --- Moose Development/Moose/Core/Set.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 41261abcc..87bcf9e4a 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -5411,10 +5411,12 @@ do -- SET_ZONE --- Get a random zone from the set. -- @param #SET_ZONE self + -- @param #number margin Number of tries to find a zone -- @return Core.Zone#ZONE_BASE The random Zone. -- @return #nil if no zone in the collection. - function SET_ZONE:GetRandomZone() - + function SET_ZONE:GetRandomZone(margin) + + local margin = margin or 100 if self:Count() ~= 0 then local Index = self.Index @@ -5423,9 +5425,11 @@ do -- SET_ZONE -- Loop until a zone has been found. -- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected. -- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues! - while not ZoneFound do + local counter = 0 + while (not ZoneFound) or (counter < margin) do local ZoneRandom = math.random( 1, #Index ) ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe() + counter = counter + 1 end return ZoneFound From 5a43936e359e1d4029802b0618057bb18e7416c9 Mon Sep 17 00:00:00 2001 From: Wingthor Date: Fri, 22 Jan 2021 23:10:22 +0100 Subject: [PATCH 081/382] Added a table shuffler using Fisher Yeates algorithm in Utilities/Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 182537ce6..2ca10e6f9 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1500,4 +1500,24 @@ function UTILS.GetOSTime() end return nil -end \ No newline at end of file +end + +--- Shuffle a table accoring to Fisher Yeates algorithm +--@param #table table to be shuffled +--@return #table +function UTILS.ShuffleTable(t) + if t == nil or type(t) ~= "table" then + BASE:I("Error in ShuffleTable: Missing or wrong tyåe of Argument") + return + end + math.random() + math.random() + math.random() + local TempTable = {} + for i = 1, #t do + local r = math.random(1,#t) + TempTable[i] = t[r] + table.remove(t,r) + end + return TempTable +end From c247a98402607c6f11b9580f8522cec370fe12ca Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 23 Jan 2021 16:46:44 +0100 Subject: [PATCH 082/382] OPS cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 255 ++--- Moose Development/Moose/Ops/FlightGroup.lua | 151 +-- Moose Development/Moose/Ops/NavyGroup.lua | 94 +- Moose Development/Moose/Ops/OpsGroup.lua | 1117 ++++++++++++++----- 4 files changed, 967 insertions(+), 650 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index de31c0a03..5a9570e75 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -64,6 +64,7 @@ ARMYGROUP = { -- @field #number length Length of element in meters. -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +-- @extends Ops.OpsGroup#OPSGROUP.Element --- Target -- @type ARMYGROUP.Target @@ -78,10 +79,10 @@ ARMYGROUP.version="0.4.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Retreat. -- TODO: Suppression of fire. -- TODO: Check if group is mobile. -- TODO: F10 menu. +-- DONE: Retreat. -- DONE: Rearm. Specify a point where to go and wait until ammo is full. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -116,13 +117,13 @@ function ARMYGROUP:New(Group) self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. - self:AddTransition("*", "Retreat", "Retreating") -- - self:AddTransition("Retreating", "Retreated", "Retreated") -- + self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. + self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. - self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target - self:AddTransition("Engaging", "Disengage", "Cruising") -- Engage a target + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state + self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state + self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state + self:AddTransition("Engaging", "Disengage", "Cruising") -- Disengage and back to cruising. self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled. self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed. @@ -152,8 +153,7 @@ function ARMYGROUP:New(Group) -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) - self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) - + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) --self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. @@ -343,7 +343,9 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - if self:IsAlive() then + local alive=self:IsAlive() + + if alive then --- -- Detection @@ -371,6 +373,10 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_UpdateEngageTarget() end + end + + if alive~=nil then + if self.verbose>=1 then -- Get number of tasks and missions. @@ -379,14 +385,20 @@ function ARMYGROUP:onafterStatus(From, Event, To) local roe=self:GetROE() local alarm=self:GetAlarmstate() - local speed=UTILS.MpsToKnots(self.velocity) + local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) local formation=self.option.Formation or "unknown" local ammo=self:GetAmmoTot() + + local cargo=0 + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + cargo=cargo+element.weightCargo + end -- Info text. - local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d", - fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading, ammo.Total) + local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d, Cargo=%.1f", + fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading or 0, ammo.Total, cargo) self:I(self.lid..text) end @@ -408,7 +420,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do - local element=_element --#ARMYGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local name=element.name local status=element.status @@ -443,6 +455,26 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:__Status(-30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events ==> See OPSGROUP +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling when a unit is hit. +-- @param #ARMYGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function ARMYGROUP:OnEventHit(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- TODO: suppression + + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -452,7 +484,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #ARMYGROUP.Element Element The group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) @@ -811,6 +843,40 @@ function ARMYGROUP:onafterRetreated(From, Event, To) end +--- On after "Board" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. +function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) + + local Coordinate=Carrier.unit:GetCoordinate() + + local Speed=UTILS.KmphToKnots(self.speedMax*0.2) + + local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) + + +end + +--- On after "Pickup" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. +function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) + + local Coordinate=Carrier.unit:GetCoordinate() + + local Speed=UTILS.KmphToKnots(self.speedMax*0.2) + + local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) + + +end + --- On after "EngageTarget" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -980,113 +1046,6 @@ function ARMYGROUP:onafterStop(From, Event, To) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events DCS -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Event function handling the birth of a unit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventBirth(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - if self.respawning then - - self:I(self.lid.."Respawning unit "..tostring(unitname)) - - local function reset() - self.respawning=nil - self:_CheckGroupDone() - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) - self:ElementSpawned(element) - - end - - end - -end - ---- Event function handling the crash of a unit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventDead(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) - - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) - self:ElementDestroyed(element) - end - - end - -end - ---- Event function handling when a unit is removed from the game. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end - ---- Event function handling when a unit is hit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventHit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- TODO: suppression - - end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1197,49 +1156,17 @@ function ARMYGROUP:_InitGroup() -- Units of the group. local units=self.group:GetUnits() - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - -- TODO: this is wrong when grouping is used! - local unittemplate=unit:GetTemplate() - - local element={} --#ARMYGROUP.Element - element.name=unit:GetName() - element.unit=unit - element.status=OPSGROUP.ElementStatus.INUTERO - element.typename=unit:GetTypeName() - element.skill=unittemplate.skill or "Unknown" - element.ai=true - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.size, element.length, element.height, element.width=unit:GetObjectSize() - element.ammo0=self:GetAmmoUnit(unit, false) - element.life0=unit:GetLife0() - element.life=element.life0 - - -- Debug text. - if self.verbose>=2 then - local text=string.format("Adding element %s: status=%s, skill=%s, life=%.3f category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", - element.name, element.status, element.skill, element.life, element.categoryname, element.category, element.size, element.length, element.height, element.width) - self:I(self.lid..text) - end - - -- Add element to table. - table.insert(self.elements, element) - - -- Get Descriptors. - self.descriptors=self.descriptors or unit:GetDesc() - - -- Set type name. - self.actype=self.actype or unit:GetTypeName() - - if unit:IsAlive() then - -- Trigger spawned event. - self:ElementSpawned(element) - end - + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) end - + + -- Get Descriptors. + self.descriptors=units[1]:GetDesc() + + -- Set type name. + self.actype=units[1]:GetTypeName() + -- Debug info. if self.verbose>=1 then local text=string.format("Initialized Army Group %s:\n", self.groupname) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 92f7e4edc..4ae70e3da 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -175,22 +175,13 @@ FLIGHTGROUP.Attribute = { --- Flight group element. -- @type FLIGHTGROUP.Element --- @field #string name Name of the element, i.e. the unit/client. --- @field Wrapper.Unit#UNIT unit Element unit object. --- @field Wrapper.Group#GROUP group Group object of the element. -- @field #string modex Tail number. --- @field #string skill Skill level. --- @field #boolean ai If true, element is AI. -- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. -- @field #table pylons Table of pylons. -- @field #number fuelmass Mass of fuel in kg. --- @field #number category Aircraft category. --- @field #string categoryname Aircraft category name. -- @field #string callsign Call sign, e.g. "Uzi 1-1". --- @field #string status Status, i.e. born, parking, taxiing. See @{#OPSGROUP.ElementStatus}. --- @field #number damage Damage of element in percent. -- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. - +-- @extends Ops.OpsGroup#OPSGROUP.Element --- FLIGHTGROUP class version. -- @field #string version @@ -243,7 +234,7 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults - --self:SetVerbosity(0) + self.isFlightgroup=true self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() @@ -251,7 +242,6 @@ function FLIGHTGROUP:New(group) self:SetDefaultROE() self:SetDefaultROT() self:SetDetection() - self.isFlightgroup=true -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) @@ -314,13 +304,6 @@ function FLIGHTGROUP:New(group) -- TODO: Add pseudo functions. - -- Debug trace. - if false then - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end - -- Add to data base. _DATABASE:AddFlightGroup(self) @@ -554,7 +537,7 @@ end --- Disable to automatically engage detected targets. -- @param #FLIGHTGROUP self --- @return #OPSGROUP self +-- @return #FLIGHTGROUP self function FLIGHTGROUP:SetEngageDetectedOff() self.engagedetectedOn=false return self @@ -747,7 +730,7 @@ function FLIGHTGROUP:GetFuelMin() local fuelmin=math.huge for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local unit=element.unit @@ -1119,64 +1102,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events +-- DCS Events ==> See also OPSGROUP ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Flightgroup event function, handling the birth of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventBirth(EventData) - - --env.info(string.format("EVENT: Birth for unit %s", tostring(EventData.IniUnitName))) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Set group. - self.group=self.group or EventData.IniGroup - - if self.respawning then - - local function reset() - self.respawning=nil - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Set homebase if not already set. - if EventData.Place then - self.homebase=self.homebase or EventData.Place - end - - if self.homebase and not self.destbase then - self.destbase=self.homebase - end - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Create element spawned event if not already present. - if not self:_IsElement(unitname) then - element=self:AddElementByName(unitname) - end - - -- Set element to spawned state. - self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) - self:ElementSpawned(element) - - end - - end - -end - --- Flightgroup event function handling the crash of a unit. -- @param #FLIGHTGROUP self -- @param Core.Event#EVENTDATA EventData Event data. @@ -1356,69 +1284,7 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) end ---- Flightgroup event function handling the crash of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventKill(EventData) - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - - -- Target name - local targetname=tostring(EventData.TgtUnitName) - - -- Debug info. - self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!", tostring(EventData.IniUnitName), targetname)) - - -- Check if this was a UNIT or STATIC object. - local target=UNIT:FindByName(targetname) - if not target then - target=STATIC:FindByName(targetname, false) - end - - -- Only count UNITS and STATICs (not SCENERY) - if target then - - -- Debug info. - self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!", tostring(EventData.IniUnitName), targetname)) - - -- Kill counter. - self.Nkills=self.Nkills+1 - - -- Check if on a mission. - local mission=self:GetMissionCurrent() - if mission then - mission.Nkills=mission.Nkills+1 -- Increase mission kill counter. - end - - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #FLIGHTGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function FLIGHTGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T3(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM functions @@ -1743,7 +1609,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To) self.Tparking=nil -- TODO: need a better check for the airbase. - local airbase=self:GetClosestAirbase() --self.group:GetCoordinate():GetClosestAirbase(nil, self.group:GetCoalition()) + local airbase=self:GetClosestAirbase() if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then @@ -2894,7 +2760,7 @@ function FLIGHTGROUP:_InitGroup() -- Add elemets. for _,unit in pairs(self.group:GetUnits()) do - local element=self:AddElementByName(unit:GetName()) + self:_AddElementByName(unit:GetName()) end -- Get first unit. This is used to extract other parameters. @@ -2960,10 +2826,11 @@ function FLIGHTGROUP:AddElementByName(unitname) local element={} --#FLIGHTGROUP.Element element.name=unitname - element.unit=unit element.status=OPSGROUP.ElementStatus.INUTERO + element.unit=unit element.group=unit:GetGroup() + -- TODO: this is wrong when grouping is used! local unittemplate=element.unit:GetTemplate() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 7fa71c335..a2b64b9eb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -611,6 +611,12 @@ function NAVYGROUP:onafterStatus(From, Event, To) self:__Status(-30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events ==> See OPSGROUP +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- See OPSGROUP! + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1032,94 +1038,6 @@ function NAVYGROUP:onafterStop(From, Event, To) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Events DCS -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Event function handling the birth of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventBirth(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - if self.respawning then - - local function reset() - self.respawning=nil - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) - self:ElementSpawned(element) - - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventDead(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) - - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) - self:ElementDestroyed(element) - end - - end - -end - ---- Flightgroup event function handling the crash of a unit. --- @param #NAVYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function NAVYGROUP:OnEventRemoveUnit(EventData) - - -- Check that this is the right group. - if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - local unit=EventData.IniUnit - local group=EventData.IniGroup - local unitname=EventData.IniUnitName - - -- Get element. - local element=self:GetElementByName(unitname) - - if element then - self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) - end - - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index bdc8d445d..2a0e9e157 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -95,6 +95,9 @@ -- @field #OPSGROUP.Ammo ammo Initial ammount of ammo. -- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- +-- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. +-- @field #table cargo Table containing all cargo of the carrier. +-- -- @extends Core.Fsm#FSM --- *A small group of determined and like-minded people can change the course of history.* --- Mahatma Gandhi @@ -149,20 +152,44 @@ OPSGROUP = { Ndestroyed = 0, Nkills = 0, weaponData = {}, + cargo = {}, } --- OPS group element. -- @type OPSGROUP.Element +-- -- @field #string name Name of the element, i.e. the unit. +-- @field #string status The element status. See @{#OPSGROUP.ElementStatus}. -- @field Wrapper.Unit#UNIT unit The UNIT object. --- @field #string status The element status. +-- @field Wrapper.Group#GROUP group The GROUP object. +-- @field DCS#Unit DCSunit The DCS unit object. +-- @field #boolean ai If true, element is AI. +-- @field #string skill Skill level. +-- -- @field #string typename Type name. +-- @field #number category Aircraft category. +-- @field #string categoryname Aircraft category name. +-- +-- @field #number size Size (max of length, width, height) in meters. -- @field #number length Length of element in meters. -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +-- +-- @field DCS#Vec3 vec3 Last known 3D position vector. +-- @field DCS#Vec3 orientX Last known ordientation vector in the direction of the nose X. +-- @field #number heading Last known heading in degrees. +-- -- @field #number life0 Initial life points. -- @field #number life Life points when last updated. +-- @field #number damage Damage of element in percent. +-- +-- @field DCS#Object.Desc descriptors Descriptors table. +-- @field #number weightEmpty Empty weight in kg. +-- @field #number weightMaxTotal Max. total weight in kg. +-- @field #number weightMaxCargo Max. cargo weight in kg. +-- @field #number weightCargo Current cargo weight in kg. +-- @field #number weight Current weight including cargo in kg. --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -328,7 +355,19 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. ---- NavyGroup version. +--- Cargo data. +-- @type OPSGROUP.CargoStatus +-- @field #string Reserved +-- @field #string Loaded + +--- Cargo data. +-- @type OPSGROUP.Cargo +-- @field #OPSGROUP opsgroup The cargo opsgroup. +-- @field #OPSGROUP.CargoStatus status Status of the cargo group. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. + +--- OpsGroup version. -- @field #string version OPSGROUP.version="0.7.1" @@ -401,6 +440,7 @@ function OPSGROUP:New(Group) self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. self:AddTransition("*", "Respawn", "*") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. + self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Status", "*") -- Status update. @@ -453,11 +493,28 @@ function OPSGROUP:New(Group) self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission. self:AddTransition("*", "MissionDone", "*") -- Mission is over. + self:AddTransition("*", "ElementInUtero", "*") -- An element is in utero again. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. + self:AddTransition("*", "Board", "Boarding") -- Group is boarding a cargo carrier. + self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. + self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. + + self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. + self:AddTransition("Pickingup", "Loading", "Loading") -- Carrier is loading cargo. + self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. + + self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. + + self:AddTransition("Loading", "Transport", "Transporting")-- Carrier is transporting cargo. + + self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. + self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. + self:AddTransition("Droppingoff", "Droppedoff", "Droppedoff") -- Carrier has dropped off all its cargo. + ------------------------ --- Pseudo Functions --- ------------------------ @@ -860,78 +917,244 @@ function OPSGROUP:GetDCSUnits() return nil end ---- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. + +--- Get current 2D position vector of the group. -- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get position of a specifc unit of the group. Default is the first existing unit in the group. +-- @return DCS#Vec2 Vector with x,y components. +function OPSGROUP:GetVec2(UnitName) + + local vec3=self:GetVec3(UnitName) + + if vec3 then + local vec2={x=vec3.x, y=vec3.z} + return vec2 + end + + return nil +end + + +--- Get current 3D position vector of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get position of a specifc unit of the group. Default is the first existing unit in the group. +-- @return DCS#Vec3 Vector with x,y,z components. +function OPSGROUP:GetVec3(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + + if unit then + local vec3=unit:getPoint() + return vec3 + end + + end + + return nil +end + +--- Get current coordinate of the group. +-- @param #OPSGROUP self +-- @param #boolean NewObject Create a new coordiante object. +-- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. +function OPSGROUP:GetCoordinate(NewObject) + + local vec3=self:GetVec3() + + if vec3 then + + self.coordinate=self.coordinate or COORDINATE:New(0,0,0) + + self.coordinate.x=vec3.x + self.coordinate.y=vec3.y + self.coordinate.z=vec3.z + + if NewObject then + local coord=COORDINATE:NewFromCoordinate(self.coordinate) + return coord + else + return self.coordinate + end + else + self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") + end + + return nil +end + +--- Get current velocity of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. +-- @return #number Velocity in m/s. +function OPSGROUP:GetVelocity(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local velvec3=unit:getVelocity() + + local vel=UTILS.VecNorm(velvec3) + + return vel + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") + end + + return nil +end + +--- Get current heading of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. +-- @return #number Current heading of the group in degrees. +function OPSGROUP:GetHeading(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local pos=unit:getPosition() + + local heading=math.atan2(pos.x.z, pos.x.x) + + if heading<0 then + heading=heading+ 2*math.pi + end + + heading=math.deg(heading) + + return heading + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") + end + + return nil +end + +--- Get current orientation of the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +-- @return DCS#Vec3 Orientation Y pointing "upwards". +-- @return DCS#Vec3 Orientation Z perpendicular to the "nose". +function OPSGROUP:GetOrientation(UnitName) + + if self:IsExist() then + + local unit=nil --DCS#Unit + + if UnitName then + unit=Unit.getByName(UnitName) + else + unit=self:GetDCSUnit() + end + + if unit then + + local pos=unit:getPosition() + + return pos.x, pos.y, pos.z + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") + end + + return nil +end + +--- Get current orientation of the first unit in the group. +-- @param #OPSGROUP self +-- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +function OPSGROUP:GetOrientationX(UnitName) + + local X,Y,Z=self:GetOrientation(UnitName) + + return X +end + + + +--- Check if task description is unique. +-- @param #OPSGROUP self +-- @param #string description Task destription +-- @return #boolean If true, no other task has the same description. +function OPSGROUP:CheckTaskDescriptionUnique(description) + + -- Loop over tasks in queue + for _,_task in pairs(self.taskqueue) do + local task=_task --#OPSGROUP.Task + if task.description==description then + return false + end + end + + return true +end + + +--- Despawn a unit of the group. A "Remove Unit" event is generated by default. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit -- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. -- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. -- @return #OPSGROUP self -function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) +function OPSGROUP:DespawnUnit(UnitName, Delay, NoEventRemoveUnit) - if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) - else + env.info("FF despawn element .."..tostring(UnitName)) - local DCSGroup=self:GetDCSGroup() + local element=self:GetElementByName(UnitName) + + if element then + + -- Get DCS unit object. + local DCSunit=Unit.getByName(UnitName) - if DCSGroup then + if DCSunit then - -- Destroy DCS group. - DCSGroup:destroy() + -- Despawn unit. + DCSunit:destroy() + -- Element goes back in utero. + self:ElementInUtero(element) + if not NoEventRemoveUnit then - - -- Get all units. - local units=self:GetDCSUnits() - - -- Create a "Remove Unit" event. - local EventTime=timer.getTime() - for i=1,#units do - self:CreateEventRemoveUnit(EventTime, units[i]) - end - - end - end - end - - return self -end - ---- Destroy group. The whole group is despawned and a *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated for all current units. --- @param #OPSGROUP self --- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. --- @return #OPSGROUP self -function OPSGROUP:Destroy(Delay) - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, OPSGROUP.Destroy, self) - else - - local DCSGroup=self:GetDCSGroup() - - if DCSGroup then - - self:T(self.lid.."Destroying group") - - -- Destroy DCS group. - DCSGroup:destroy() - - -- Get all units. - local units=self:GetDCSUnits() - - -- Create a "Unit Lost" event. - local EventTime=timer.getTime() - for i=1,#units do - if self.isAircraft then - self:CreateEventUnitLost(EventTime, units[i]) - else - self:CreateEventDead(EventTime, units[i]) - end + self:CreateEventRemoveUnit(timer.getTime(), DCSunit) end + end end - return self end --- Despawn an element/unit of the group. @@ -970,178 +1193,99 @@ function OPSGROUP:DespawnElement(Element, Delay, NoEventRemoveUnit) return self end ---- Get current 2D position vector of the group. +--- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. -- @param #OPSGROUP self --- @return DCS#Vec2 Vector with x,y components. -function OPSGROUP:GetVec2() +-- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. +-- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. +-- @return #OPSGROUP self +function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) - local vec3=self:GetVec3() - - if vec3 then - local vec2={x=vec3.x, y=vec3.z} - return vec2 - end - - return nil -end - - ---- Get current 3D position vector of the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Vector with x,y,z components. -function OPSGROUP:GetVec3() - if self:IsExist() then - - local unit=self:GetDCSUnit() - - if unit then - local vec3=unit:getPoint() - - return vec3 - end - - end - return nil -end - ---- Get current coordinate of the group. --- @param #OPSGROUP self --- @param #boolean NewObject Create a new coordiante object. --- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. -function OPSGROUP:GetCoordinate(NewObject) - - local vec3=self:GetVec3() - - if vec3 then - - self.coordinate=self.coordinate or COORDINATE:New(0,0,0) - - self.coordinate.x=vec3.x - self.coordinate.y=vec3.y - self.coordinate.z=vec3.z - - if NewObject then - local coord=COORDINATE:NewFromCoordinate(self.coordinate) - return coord - else - return self.coordinate - end + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) else - self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") - end - - return nil -end ---- Get current velocity of the group. --- @param #OPSGROUP self --- @return #number Velocity in m/s. -function OPSGROUP:GetVelocity() - if self:IsExist() then - - local unit=self:GetDCSUnit(1) + local DCSGroup=self:GetDCSGroup() - if unit then - - local velvec3=unit:getVelocity() - - local vel=UTILS.VecNorm(velvec3) - - return vel + if DCSGroup then + -- Get all units. + local units=self:GetDCSUnits() + + for i=1,#units do + local unit=units[i] + if unit then + local name=unit:getName() + if name then + self:DespawnUnit(name, 0, NoEventRemoveUnit) + end + end + end + end - else - self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") end - return nil + + return self end ---- Get current heading of the group. +--- Destroy a unit of the group. A *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated. -- @param #OPSGROUP self --- @return #number Current heading of the group in degrees. -function OPSGROUP:GetHeading() +-- @param #string UnitName Name of the unit which should be destroyed. +-- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. +-- @return #OPSGROUP self +function OPSGROUP:DestroyUnit(UnitName, Delay) - if self:IsExist() then + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.DestroyUnit, self, UnitName, 0) + else - local unit=self:GetDCSUnit() + local unit=Unit.getByName(UnitName) if unit then + + -- Create a "Unit Lost" event. + local EventTime=timer.getTime() - local pos=unit:getPosition() - - local heading=math.atan2(pos.x.z, pos.x.x) - - if heading<0 then - heading=heading+ 2*math.pi + if self.isAircraft then + self:CreateEventUnitLost(EventTime, unit) + else + self:CreateEventDead(EventTime, unit) end - heading=math.deg(heading) - - return heading end - + + end + +end + +--- Destroy group. The whole group is despawned and a *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated for all current units. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. +-- @return #OPSGROUP self +function OPSGROUP:Destroy(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Destroy, self, 0) else - self:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") - end - - return nil -end ---- Get current orientation of the first unit in the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. --- @return DCS#Vec3 Orientation Y pointing "upwards". --- @return DCS#Vec3 Orientation Z perpendicular to the "nose". -function OPSGROUP:GetOrientation() - - if self:IsExist() then - - local unit=self:GetDCSUnit() + -- Get all units. + local units=self:GetDCSUnits() - if unit then + if units then + + -- Create a "Unit Lost" event. + for _,unit in pairs(units) do + if unit then + self:DestroyUnit(unit:getName()) + end + end - local pos=unit:getPosition() - - return pos.x, pos.y, pos.z - end - - else - self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") - end - - return nil -end - ---- Get current orientation of the first unit in the group. --- @param #OPSGROUP self --- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. -function OPSGROUP:GetOrientationX() - - local X,Y,Z=self:GetOrientation() - - return X -end - - - ---- Check if task description is unique. --- @param #OPSGROUP self --- @param #string description Task destription --- @return #boolean If true, no other task has the same description. -function OPSGROUP:CheckTaskDescriptionUnique(description) - - -- Loop over tasks in queue - for _,_task in pairs(self.taskqueue) do - local task=_task --#OPSGROUP.Task - if task.description==description then - return false end + end - return true + return self end - --- Activate a *late activated* group. -- @param #OPSGROUP self -- @param #number delay (Optional) Delay in seconds before the group is activated. Default is immediately. @@ -1170,6 +1314,31 @@ function OPSGROUP:Activate(delay) return self end +--- Deactivate the group. Group will be respawned in late activated state. +-- @param #OPSGROUP self +-- @param #number delay (Optional) Delay in seconds before the group is deactivated. Default is immediately. +-- @return #OPSGROUP self +function OPSGROUP:Deactivate(delay) + + if delay and delay>0 then + self:ScheduleOnce(delay, OPSGROUP.Deactivate, self) + else + + if self:IsAlive()==true then + + self.template.lateActivation=true + + local template=UTILS.DeepCopy(self.template) + + self:_Respawn(0, template) + + end + + end + + return self +end + --- Self destruction of group. An explosion is created at the position of each element. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds. Default now. @@ -1742,6 +1911,150 @@ function OPSGROUP:RemoveWaypoint(wpindex) return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- DCS Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling the birth of a unit. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventBirth(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + if self.respawning then + + self:I(self.lid.."Respawning unit "..tostring(unitname)) + + local function reset() + self.respawning=nil + self:_CheckGroupDone() + end + + -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. + -- TODO: Can I do this more rigorously? + self:ScheduleOnce(1, reset) + + else + + -- Set homebase if not already set. + if self.isFlightgroup then + if EventData.Place then + self.homebase=self.homebase or EventData.Place + end + + if self.homebase and not self.destbase then + self.destbase=self.homebase + end + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + else + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) + end + + -- Get element. + local element=self:GetElementByName(unitname) + + -- Set element to spawned state. + self:ElementSpawned(element) + + end + + end + +end + +--- Event function handling the crash of a unit. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventDead(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) + + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) + self:ElementDestroyed(element) + end + + end + +end + +--- Event function handling when a unit is removed from the game. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventRemoveUnit(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:ElementDead(element) + end + + end + +end + +--- Event function handling the event that a unit achieved a kill. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventKill(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + + -- Target name + local targetname=tostring(EventData.TgtUnitName) + + -- Debug info. + self:T2(self.lid..string.format("EVENT: Unit %s killed object %s!", tostring(EventData.IniUnitName), targetname)) + + -- Check if this was a UNIT or STATIC object. + local target=UNIT:FindByName(targetname) + if not target then + target=STATIC:FindByName(targetname, false) + end + + -- Only count UNITS and STATICs (not SCENERY) + if target then + + -- Debug info. + self:T(self.lid..string.format("EVENT: Unit %s killed unit/static %s!", tostring(EventData.IniUnitName), targetname)) + + -- Kill counter. + self.Nkills=self.Nkills+1 + + -- Check if on a mission. + local mission=self:GetMissionCurrent() + if mission then + mission.Nkills=mission.Nkills+1 -- Increase mission kill counter. + end + + end + + end + +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task Functions @@ -3748,6 +4061,19 @@ function OPSGROUP:_UpdateLaser() end +--- On after "ElementInUtero" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Element The flight group element. +function OPSGROUP:onafterElementInUtero(From, Event, To, Element) + self:I(self.lid..string.format("Element in utero %s", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.INUTERO) + +end --- On after "ElementDestroyed" event. -- @param #OPSGROUP self @@ -3827,7 +4153,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #table Template The template used to respawn the group. +-- @param #table Template The template used to respawn the group. Default is the inital template of the group. function OPSGROUP:onafterRespawn(From, Event, To, Template) self:I(self.lid.."Respawning group!") @@ -3836,78 +4162,113 @@ function OPSGROUP:onafterRespawn(From, Event, To, Template) template.lateActivation=false - self:_Respawn(template,Reset) + --self.respawning=true + + self:_Respawn(0, template, Reset) end --- Respawn the group. -- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before respawn happens. Default 0. -- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #boolean Reset Reset positions if TRUE. -- @return #OPSGROUP self -function OPSGROUP:_Respawn(Template, Reset) +function OPSGROUP:_Respawn(Delay, Template, Reset) - env.info("FF _Respawn") + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) + else - -- Given template or get old. - Template=Template or UTILS.DeepCopy(self.template) + env.info("FF _Respawn") - -- Get correct heading. - local function _Heading(course) - local h - if course<=180 then - h=math.rad(course) + -- Given template or get old. + Template=Template or UTILS.DeepCopy(self.template) + + -- Get correct heading. + local function _Heading(course) + local h + if course<=180 then + h=math.rad(course) + else + h=-math.rad(360-course) + end + return h + end + + if self:IsAlive() then + + --- + -- Group is ALIVE + --- + + -- Get units. + local units=self.group:GetUnits() + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit:GetName()==Unit.name then + local vec3=unit:GetVec3() + local heading=unit:GetHeading() + Unit.x=vec3.x + Unit.y=vec3.z + Unit.alt=vec3.y + Unit.heading=math.rad(heading) + Unit.psi=-Unit.heading + end + end + + end + + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. + self:Despawn(0, true) + else - h=-math.rad(360-course) - end - return h - end - if self:IsAlive() then - - --- - -- Group is ALIVE - - -- Get units. - local units=self.group:GetUnits() - - -- Loop over template units. - for UnitID, Unit in pairs(Template.units) do - - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - if unit:GetName()==Unit.name then - local vec3=unit:GetVec3() - local heading=unit:GetHeading() + --- + -- Group is DESPAWNED + --- + + --[[ + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + local element=self:GetElementByName(Unit.name) + + if element then + local vec3=element.vec3 + local heading=element.heading Unit.x=vec3.x Unit.y=vec3.z Unit.alt=vec3.y Unit.heading=math.rad(heading) Unit.psi=-Unit.heading end + end - + + ]] + end - -- Despawn old group. Dont trigger any remove unit event since this is a respawn. - self:Despawn(0, true) + -- Currently respawning. + --self.respawning=true + + self:I({Template=Template}) + + -- Spawn new group. + _DATABASE:Spawn(Template) + + -- Reset events. + --self:ResetEvents() - else - end - -- Currently respawning. - self.respawning=true - - self:I({Template=Template}) - - -- Spawn new group. - _DATABASE:Spawn(Template) - - -- Reset events. - --self:ResetEvents() - return self end @@ -3974,6 +4335,140 @@ function OPSGROUP:onafterStop(From, Event, To) self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Cargo Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) + + env.info("FF load") + + local weight=500 + + local carrier=nil --#OPSGROUP.Element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + local cargobay=element.weightMaxCargo-element.weightCargo + + if cargobay>=weight then + carrier=element + break + end + + end + + if carrier then + + -- TODO: add function to set/add/get internal cargo. + carrier.weightCargo=carrier.weightCargo+weight + + -- This function is only really used for aircraft and sets the total internal cargo weight. + trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + + -- Embark ==> Loaded + CargoGroup:Embark(carrier) + + else + self:E(self.lid.."Cound not find a carrier with enough cargo capacity!") + end + +end + +--- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. +-- @param #number Heading Heading of group. +function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) + + --TODO: Add check if CargoGroup is + if CargoGroup:IsInUtero() then + CargoGroup:Unboard(Coordinate, Heading) + end + +end + +--- On after "Stop" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onafterEmbark(From, Event, To, Carrier) + + env.info("FF embark") + + -- Despawn this group. + self:Despawn(0, true) + + self.carrier=Carrier + +end + +--- On after "Unboard" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. +-- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. +function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) + + env.info("FF Unboard") + + -- Template for the respawned group. + local Template=UTILS.DeepCopy(self.template) + + -- Loop over template units. + for _,Unit in pairs(Template.units) do + + local element=self:GetElementByName(Unit.name) + + if element then + + local vec3=element.vec3 + + -- Relative pos vector. + local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 + + local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 + + -- Position. + Unit.x=cvec2.x+rvec2.x + Unit.y=cvec2.y+rvec2.y + Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) + + -- Heading. + Unit.heading=Heading and math.rad(Heading) or Unit.heading + Unit.psi=-Unit.heading + + end + + end + + -- Reduce carrier weight. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + self.carrier.weightCargo=self.carrier.weightCargo-element.weight + end + -- This function is only really used for aircraft and sets the total internal cargo weight. + trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + + -- Respawn group. + self:_Respawn(0, Template) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5429,7 +5924,7 @@ end -- @return #OPSGROUP self function OPSGROUP:_UpdatePosition() - if self:IsAlive() then + if self:IsAlive()~=nil then -- Backup last state to monitor differences. self.positionLast=self.position or self:GetVec3() @@ -5443,6 +5938,11 @@ function OPSGROUP:_UpdatePosition() self.orientX=self:GetOrientationX() self.velocity=self:GetVelocity() + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + element.vec3=self:GetVec3(element.name) + end + -- Update time. local Tnow=timer.getTime() self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0 @@ -5451,11 +5951,11 @@ function OPSGROUP:_UpdatePosition() if not self.traveldist then self.traveldist=0 end - + + -- Travel distance since last check. self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position, self.positionLast)) - -- Add up travelled distance. - + -- Add up travelled distance. self.traveldist=self.traveldist+self.travelds -- Debug info. @@ -5632,7 +6132,16 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) self:T3(self.lid..string.format("Element %s: %s", Element.name, Element.status)) end - if newstatus==OPSGROUP.ElementStatus.SPAWNED then + if newstatus==OPSGROUP.ElementStatus.INUTERO then + --- + -- INUTERO + --- + + if self:_AllSimilarStatus(newstatus) then + self:__InUtero(-0.5) + end + + elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then --- -- SPAWNED --- @@ -6048,6 +6557,102 @@ function OPSGROUP:_CoordinateFromObject(Object) return nil end +--- Add a unit/element to the OPS group. +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +-- @return #OPSGROUP.Element The element or nil. +function OPSGROUP:_AddElementByName(unitname) + + local unit=UNIT:FindByName(unitname) + + if unit then + + -- TODO: this is wrong when grouping is used! + local unittemplate=unit:GetTemplate() + + + local element={} --#OPSGROUP.Element + + -- Name and status. + element.name=unitname + element.status=OPSGROUP.ElementStatus.INUTERO + + -- Unit and group. + element.unit=unit + element.DCSunit=Unit.getByName(unitname) + element.group=unit:GetGroup() + + -- Skill etc. + element.skill=unittemplate.skill or "Unknown" + if element.skill=="Client" or element.skill=="Player" then + element.ai=false + element.client=CLIENT:FindByName(unitname) + else + element.ai=true + end + + -- Descriptors and type/category. + element.descriptors=unit:GetDesc() + self:I({desc=element.descriptors}) + + element.category=unit:GetUnitCategory() + element.categoryname=unit:GetCategoryName() + element.typename=unit:GetTypeName() + + -- Ammo. + element.ammo0=self:GetAmmoUnit(unit, false) + + -- Life points. + element.life=unit:GetLife() + element.life0=math.max(unit:GetLife0(), element.life) -- Some units report a life0 that is smaller than its initial life points. + + -- Size and dimensions. + element.size, element.length, element.height, element.width=unit:GetObjectSize() + + -- Weight and cargo. + element.weightEmpty=element.descriptors.massEmpty or 666 + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + element.weightCargo=0 + element.weight=element.weightEmpty+element.weightCargo + + + -- FLIGHTGROUP specific. + if self.isFlightgroup then + element.callsign=element.unit:GetCallsign() + element.modex=unittemplate.onboard_num + element.payload=unittemplate.payload + element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil + element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 + element.fuelmass=element.fuelmass0 + element.fuelrel=element.unit:GetFuel() + end + + -- Debug text. + local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", + element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, + element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) + self:I(self.lid..text) + + -- Debug text. + --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", + --element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) + --self:T(self.lid..text) + + -- Add element to table. + table.insert(self.elements, element) + + -- Trigger spawned event if alive. + if unit:IsAlive() then + self:ElementSpawned(element) + end + + return element + end + + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 6baf85d42963b95c1e4836e5ec4569bb0fae3395 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 26 Jan 2021 09:26:39 +0100 Subject: [PATCH 083/382] Update Mantis.lua Some Doc corrections --- Moose Development/Moose/Functional/Mantis.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a4ca58c19..78f7bc0a5 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1,4 +1,4 @@ - --- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses +--- **Functional** -- Modular, Automatic and Network capable Targeting and Interception System for Air Defenses -- -- === -- @@ -96,7 +96,7 @@ -- -- # 2. Start up your MANTIS with a basic setting -- --- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` +-- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` -- `myredmantis:Start()` -- -- [optional] Use @@ -111,7 +111,7 @@ -- -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: -- --- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` +-- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- -- # 3. Default settings From 7a07e15032eb8cd603f27550444d7ccb379542cb Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:06:08 +0100 Subject: [PATCH 084/382] Update Mantis.lua --- Moose Development/Moose/Functional/Mantis.lua | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 78f7bc0a5..a06b5ccc2 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -37,7 +37,7 @@ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging -- @field @{#Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR --- @field @{Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS +-- @field @{#Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS -- @field #boolean debug Switch on extra messages -- @field #boolean verbose Switch on extra logging -- @field #number checkradius Radius of the SAM sites @@ -181,6 +181,26 @@ do --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) --@return #MANTIS self + --@usage Start up your MANTIS with a basic setting + -- + -- `myredmantis = MANTIS:New("myredmantis","Red SAM","Red EWR",nil,"red",false)` + -- `myredmantis:Start()` + -- + -- [optional] Use + -- + -- * `MANTIS:SetEWRGrouping(radius)` + -- * `MANTIS:SetEWRRange(radius)` + -- * `MANTIS:SetSAMRadius(radius)` + -- * `MANTIS:SetDetectInterval(interval)` + -- * `MANTIS:SetAutoRelocate(hq, ewr)` + -- + -- before starting #MANTIS to fine-tune your setup. + -- + -- If you want to use a separate AWACS unit (default detection range: 250km) to support your EWR system, use e.g. the following setup: + -- + -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` + -- `mybluemantis:Start()` + -- function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs) -- DONE: Create some user functions for these @@ -307,7 +327,7 @@ do self.engagerange = range end - --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night + --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetNewSAMRangeWhileRunning(range) @@ -535,7 +555,7 @@ do end end - --- Function to check if any object is in the given SAM zone + --- (Internal) Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items -- @param samcoordinate Core.Point#COORDINATE Coordinate object. @@ -562,7 +582,7 @@ do return false end - --- Function to start the detection via EWR groups + --- (Internal) Function to start the detection via EWR groups -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() @@ -593,7 +613,7 @@ do return _MANTISdetection end - --- Function to start the detection via AWACS if defined as separate + --- (Internal) Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() @@ -625,7 +645,7 @@ do return _MANTISAwacs end - --- Function to set the SAM start state + --- (Internal) Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() From 6b91e686680606660841d22e06c414fa24241e5a Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 26 Jan 2021 12:39:49 +0100 Subject: [PATCH 085/382] OPS Cargo --- Moose Development/Moose/Core/Database.lua | 17 +- Moose Development/Moose/Ops/ArmyGroup.lua | 33 ++- Moose Development/Moose/Ops/FlightGroup.lua | 40 +--- Moose Development/Moose/Ops/NavyGroup.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 251 +++++++++++++++++++- 5 files changed, 295 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 5997f05ca..ecb2d28e1 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1420,19 +1420,18 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings ) self.PLAYERSETTINGS[PlayerName] = Settings end ---- Add a flight group to the data base. +--- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base. -- @param #DATABASE self --- @param Ops.FlightGroup#FLIGHTGROUP flightgroup -function DATABASE:AddFlightGroup(flightgroup) - self:T({NewFlightGroup=flightgroup.groupname}) - self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup +-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. +function DATABASE:AddOpsGroup(opsgroup) + self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end ---- Get a flight group from the data base. +--- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base. -- @param #DATABASE self --- @param #string groupname Group name of the flight group. Can also be passed as GROUP object. --- @return Ops.FlightGroup#FLIGHTGROUP Flight group object. -function DATABASE:GetFlightGroup(groupname) +-- @param #string groupname Group name of the group. Can also be passed as GROUP object. +-- @return Ops.OpsGroup#OPSGROUP OPS group object. +function DATABASE:GetOpsGroup(groupname) -- Get group and group name. if type(groupname)=="string" then diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 5a9570e75..2a085c6a9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -91,12 +91,19 @@ ARMYGROUP.version="0.4.0" --- Create a new ARMYGROUP class object. -- @param #ARMYGROUP self --- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`. +-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #ARMYGROUP self -function ARMYGROUP:New(Group) +function ARMYGROUP:New(group) + + -- First check if we already have an OPS group for this group. + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og + end -- Inherit everything from FSM class. - local self=BASE:Inherit(self, OPSGROUP:New(Group)) -- #ARMYGROUP + local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("ARMYGROUP %s | ", self.groupname) @@ -264,6 +271,26 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo return task end +--- Add a *scheduled* task to transport group(s). +-- @param #ARMYGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of cargo groups. Can also be a singe @{Wrapper.Group#GROUP} object. +-- @param Core.Zone#ZONE PickupZone Zone where the cargo is picked up. +-- @param Core.Zone#ZONE DeployZone Zone where the cargo is delivered to. +-- @param #string Clock Time when to start the attack. +-- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. +function ARMYGROUP:AddTaskCargoGroup(GroupSet, PickupZone, DeployZone, Clock, Prio) + + local DCStask={} + DCStask.id="CargoTransport" + DCStask.params={} + DCStask.params.cargoqueu=1 + + local task=self:AddTask(DCStask, Clock, nil, Prio) + + return task +end + --- Define a set of possible retreat zones. -- @param #ARMYGROUP self -- @param Core.Set#SET_ZONE RetreatZoneSet The retreat zone set. Default is an empty set. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 4ae70e3da..b4bc02f7f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -221,10 +221,10 @@ FLIGHTGROUP.version="0.6.1" function FLIGHTGROUP:New(group) -- First check if we already have a flight group for this group. - local fg=_DATABASE:GetFlightGroup(group) - if fg then - fg:I(fg.lid..string.format("WARNING: Flight group already exists in data base!")) - return fg + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og end -- Inherit everything from FSM class. @@ -304,9 +304,6 @@ function FLIGHTGROUP:New(group) -- TODO: Add pseudo functions. - -- Add to data base. - _DATABASE:AddFlightGroup(self) - -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) @@ -2143,7 +2140,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") -- Approach point: 10 NN in direction of runway. - if airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME then + if airbase:IsAirdrome() then --- -- Airdrome @@ -2156,7 +2153,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - elseif airbase:GetAirbaseCategory()==Airbase.Category.SHIP then + elseif airbase:IsShip() then --- -- Ship @@ -2169,33 +2166,12 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp if self.isAI then - local routeto=false - if fc or world.event.S_EVENT_KILL then - routeto=true - end - -- Clear all tasks. -- Warning, looks like this can make DCS CRASH! Had this after calling RTB once passed the final waypoint. --self:ClearTasks() - - -- Respawn? - if routeto then - -- Just route the group. Respawn might happen when going from holding to final. - self:Route(wp, 1) - - else - - -- Get group template. - local Template=self.group:GetTemplate() - - -- Set route points. - Template.route.points=wp - - --Respawn the group with new waypoints. - self:Respawn(Template) - - end + -- Just route the group. Respawn might happen when going from holding to final. + self:Route(wp, 1) end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a2b64b9eb..782788c2f 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -107,12 +107,19 @@ NAVYGROUP.version="0.5.0" --- Create a new NAVYGROUP class object. -- @param #NAVYGROUP self --- @param #string GroupName Name of the group. +-- @param Wrapper.Group#GROUP group The group object. Can also be given by its group name as `#string`. -- @return #NAVYGROUP self -function NAVYGROUP:New(GroupName) +function NAVYGROUP:New(group) + + -- First check if we already have an OPS group for this group. + local og=_DATABASE:GetOpsGroup(group) + if og then + og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) + return og + end -- Inherit everything from FSM class. - local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #NAVYGROUP + local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #NAVYGROUP -- Set some string id for output to DCS.log file. self.lid=string.format("NAVYGROUP %s | ", self.groupname) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2a0e9e157..c9af6e1da 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -97,6 +97,8 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #table cargo Table containing all cargo of the carrier. +-- @field #table cargoqueue Table containing cargo groups to be transported. +-- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- -- @extends Core.Fsm#FSM @@ -153,6 +155,7 @@ OPSGROUP = { Nkills = 0, weaponData = {}, cargo = {}, + cargoqueue = {}, } @@ -355,12 +358,21 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. ---- Cargo data. +--- Cargo status. -- @type OPSGROUP.CargoStatus +-- @field #string Waiting -- @field #string Reserved -- @field #string Loaded +-- @field #string Delivered ---- Cargo data. +--- Cargo transport data. +-- @type OPSGROUP.CargoTransport +-- @field #table cargos Cargos. Each element is a #OPSGROUP.Cargo +-- @field #string status Status of the cargo group. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. + +--- Cargo group data. -- @type OPSGROUP.Cargo -- @field #OPSGROUP opsgroup The cargo opsgroup. -- @field #OPSGROUP.CargoStatus status Status of the cargo group. @@ -386,20 +398,20 @@ OPSGROUP.version="0.7.1" --- Create a new OPSGROUP class object. -- @param #OPSGROUP self --- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`. +-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #OPSGROUP self -function OPSGROUP:New(Group) +function OPSGROUP:New(group) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSGROUP -- Get group and group name. - if type(Group)=="string" then - self.groupname=Group + if type(group)=="string" then + self.groupname=group self.group=GROUP:FindByName(self.groupname) else - self.group=Group - self.groupname=Group:GetName() + self.group=group + self.groupname=group:GetName() end -- Set some string id for output to DCS.log file. @@ -504,12 +516,13 @@ function OPSGROUP:New(Group) self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. - self:AddTransition("Pickingup", "Loading", "Loading") -- Carrier is loading cargo. + self:AddTransition("*", "Loading", "Loading") -- Carrier is loading cargo. self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. + self:AddTransition("Loading", "Loaded", "Loaded") -- Carrier loads cargo into carrier. self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. - self:AddTransition("Loading", "Transport", "Transporting")-- Carrier is transporting cargo. + self:AddTransition("Loading", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. @@ -538,6 +551,10 @@ function OPSGROUP:New(Group) -- TODO: Add pseudo functions. + + -- Add to data base. + _DATABASE:AddOpsGroup(self) + return self end @@ -4339,6 +4356,189 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Create a +-- @param #OPSGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoTransport Cargo transport. +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + + local transport={} --#OPSGROUP.CargoTransport + + transport.pickupzone=Pickupzone + transport.deployzone=Deployzone + transport.id=1 + transport.status="Planning" + + transport.cargos={} + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + env.info("FF adding cargo group "..cargo.opsgroup:GetName()) + table.insert(transport.cargos, cargo) + else + for _,group in pairs(GroupSet.Set) do + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + table.insert(transport.cargos, cargo) + end + end + + return transport +end + +--- Create a +-- @param #OPSGROUP self +-- @param Wrapper.Group#GROUP group The GROUP object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.Cargo Cargo data. +function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) + + local opsgroup=nil + + if group:IsInstanceOf("OPSGROUP") then + opsgroup=group + else + + opsgroup=_DATABASE:GetOpsGroup(group) + + if not opsgroup then + if group:IsAir() then + opsgroup=FLIGHTGROUP:New(group) + elseif group:IsShip() then + opsgroup=NAVYGROUP:New(group) + else + opsgroup=ARMYGROUP:New(group) + end + + end + + end + + local cargo={} --#OPSGROUP.Cargo + + cargo.opsgroup=opsgroup + cargo.status="Unknown" + cargo.pickupzone=Pickupzone + cargo.deployzone=Deployzone + + return cargo +end + +--- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Pickup zone. +function OPSGROUP:onafterPickup(From, Event, To, Zone) + + env.info("FF pickup") + + local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + if inzone then + + -- We are already in the pickup zone ==> initiate loading. + self:Loading() + + else + + -- Get a random coordinate in the pickup zone and let the carrier go there. + local Coordinate=Zone:GetRandomCoordinate() + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + end + +end + +--- Get total weight of the group including cargo. +-- @param #OPSGROUP self +-- @return #number Total weight in kg. +function OPSGROUP:GetWeightTotal() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + weight=weight+element.weight + end + + end + + return weight +end + +--- Get weight of the internal cargo the group is carriing right now. +-- @param #OPSGROUP self +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargo() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + weight=weight+element.weightCargo + end + + end + + return weight +end + + +--- On after "Loading" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterLoading(From, Event, To) + + env.info("FF loading") + + --- Find a carrier which can load a given weight. + local function _findCarrier(weight) + local carrier=nil --#OPSGROUP.Element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + local cargobay=element.weightMaxCargo-element.weightCargo + + if cargobay>=weight then + return element + end + + end + + return nil + end + + + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --#OPSGROUP.Cargo + + local weight=cargo.opsgroup:GetWeightTotal() + + local carrier=_findCarrier(weight) + + if carrier then + + -- Order cargo group to board the carrier. + cargo.opsgroup:Board(carrier) + + else + + env.info("FF cannot board carrier") + + end + end + +end + --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -4398,7 +4598,28 @@ function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading end ---- On after "Stop" event. +--- +-- Cargo Group Functions +--- + +--- On after "Board" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onafterBoard(From, Event, To, Carrier) + + env.info("FF board") + + self.carrier=Carrier + + self:Embark(Carrier) + +end + + +--- On after "Embark" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -5201,10 +5422,16 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() + elseif opsgroup:Is("Pickingup") then + --TODO: make IsPickingup() function. + + opsgroup:FullStop() + opsgroup:Loading() + elseif opsgroup:IsEngaging() then -- Nothing to do really. - + else -- Trigger DetourReached event. From 155b06edc822ae894f66a2c50f45709677f476de Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 28 Jan 2021 10:04:00 +0100 Subject: [PATCH 086/382] Update Task_A2G_Dispatcher.lua Added injection of Task Name into DetectedItem; useful when using also Designate with same Detection Object to ensure menu entries are harmonized. --- Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 448d7f545..05bf2d11f 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -753,6 +753,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -761,6 +762,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... if TargetSetUnit then Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -769,6 +771,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... if TargetSetUnit then Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end end From c71c6c5178043a481ec9c69baed4f5677b9c05fc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 28 Jan 2021 10:10:39 +0100 Subject: [PATCH 087/382] Update Designate.lua Use of added injection of Task Name into DetectedItem; useful when using also Designate with same Detection Object to ensure menu entries are harmonized. --- Moose Development/Moose/Functional/Designate.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Designate.lua b/Moose Development/Moose/Functional/Designate.lua index a11aa21b8..72aaf9959 100644 --- a/Moose Development/Moose/Functional/Designate.lua +++ b/Moose Development/Moose/Functional/Designate.lua @@ -1001,7 +1001,13 @@ do -- DESIGNATE local ID = self.Detection:GetDetectedItemID( DetectedItem ) local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup ) - MenuText = string.format( "(%3s) %s", Designating, MenuText ) + -- Use injected MenuName from TaskA2GDispatcher if using same Detection Object + if DetectedItem.DesignateMenuName then + MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName ) + else + MenuText = string.format( "(%3s) %s", Designating, MenuText ) + end + local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName ) -- Build the Lasing menu. From 242462b9ba636d0ee9fc7c89788635ec6f829391 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 29 Jan 2021 00:32:38 +0100 Subject: [PATCH 088/382] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 63 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 385 ++++++++++++++++------ 2 files changed, 316 insertions(+), 132 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2a085c6a9..78d060050 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -469,6 +469,35 @@ function ARMYGROUP:onafterStatus(From, Event, To) end self:I(self.lid..text) end + + + --- + -- Cargo + --- + + if self.cargoTransport then + + local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) + + if self:IsPickingup() then + + elseif self:IsLoading() then + + elseif self:IsLoaded() then + + end + + end + + self:I(self.lid..text) + end --- @@ -870,40 +899,6 @@ function ARMYGROUP:onafterRetreated(From, Event, To) end ---- On after "Board" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. -function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) - - local Coordinate=Carrier.unit:GetCoordinate() - - local Speed=UTILS.KmphToKnots(self.speedMax*0.2) - - local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) - - -end - ---- On after "Pickup" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier to board. -function ARMYGROUP:onafterBoard(From, Event, To, Carrier, Formation) - - local Coordinate=Carrier.unit:GetCoordinate() - - local Speed=UTILS.KmphToKnots(self.speedMax*0.2) - - local waypoint=self:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, true) - - -end - --- On after "EngageTarget" event. -- @param #ARMYGROUP self -- @param #string From From state. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c9af6e1da..366bbc5ff 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -99,6 +99,8 @@ -- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. +-- @field #string cargoStatus Cargo status of this group acting as cargo. +-- @field #string carrierStatus Carrier status of this group acting as cargo carrier. -- -- @extends Core.Fsm#FSM @@ -358,24 +360,50 @@ OPSGROUP.TaskType={ -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. -- @field #string formation Ground formation. Similar to action but on/off road. +--- Cargo Carrier status. +-- @type OPSGROUP.CarrierStatus +-- @field #string EMPTY Carrier is empty and ready for cargo transport. +-- @field #string PICKUP Carrier is on its way to pickup cargo. +-- @field #string LOADING Carrier is loading cargo. +-- @field #string LOADED Carrier has loaded cargo. +-- @field #string TRANSPORTING Carrier is transporting cargo. +OPSGROUP.CarrierStatus={ + EMPTY="empty", + PICKUP="pickup", + LOADING="loading", + LOADED="loaded", + TRANSPORTING="transporting", +} + --- Cargo status. -- @type OPSGROUP.CargoStatus --- @field #string Waiting --- @field #string Reserved --- @field #string Loaded --- @field #string Delivered +-- @field #string NOTCARGO This group is no cargo yet. +-- @field #string WAITING Cargo is awaiting transporter. +-- @field #string ASSIGNED Cargo is assigned to a carrier. +-- @field #string BOARDING Cargo is boarding a carrier. +-- @field #string LOADED Cargo is loaded into a carrier. +-- @field #string DELIVERED Cargo was delivered at its destination. +OPSGROUP.CargoStatus={ + NOTCARGO="not cargo", + WAITING="waiting for carrier", + ASSIGNED="assigned to carrier", + BOARDING="boarding", + LOADED="loaded", + DELIVERED="delivered", +} + --- Cargo transport data. -- @type OPSGROUP.CargoTransport --- @field #table cargos Cargos. Each element is a #OPSGROUP.Cargo --- @field #string status Status of the cargo group. +-- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- Cargo group data. --- @type OPSGROUP.Cargo +-- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. --- @field #OPSGROUP.CargoStatus status Status of the cargo group. +-- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. @@ -439,6 +467,8 @@ function OPSGROUP:New(group) self.spot.timer=TIMER:New(self._UpdateLaser, self) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrierStatus=OPSGROUP.CarrierStatus.EMPTY -- Init task counter. self.taskcurrent=0 @@ -515,18 +545,12 @@ function OPSGROUP:New(group) self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. - self:AddTransition("*", "Pickup", "Pickingup") -- Carrier and is on route to pick up cargo. - self:AddTransition("*", "Loading", "Loading") -- Carrier is loading cargo. - self:AddTransition("Loading", "Load", "Loading") -- Carrier loads cargo into carrier. - self:AddTransition("Loading", "Loaded", "Loaded") -- Carrier loads cargo into carrier. - - self:AddTransition("Loading", "Pickup", "Pickingup") -- Carrier is picking up another cargo. - - self:AddTransition("Loading", "Transport", "*") -- Carrier is transporting cargo. - - self:AddTransition("Transporting", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. - self:AddTransition("Droppingoff", "Dropoff", "Droppingoff") -- Carrier is dropping off cargo. - self:AddTransition("Droppingoff", "Droppedoff", "Droppedoff") -- Carrier has dropped off all its cargo. + self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. + self:AddTransition("*", "Loading", "*") -- Carrier is loading cargo. + self:AddTransition("*", "Load", "*") -- Carrier loads cargo into carrier. + self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. + self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. + self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. ------------------------ --- Pseudo Functions --- @@ -1495,6 +1519,37 @@ function OPSGROUP:IsEngaging() return self:is("Engaging") end +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsPickingup() + return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP +end + + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsLoading() + return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING +end + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsLoaded() + return self.carrierStatus==OPSGROUP.CarrierStatus.LOADED +end + +--- Check if the group is picking up cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is picking up. +function OPSGROUP:IsTransporting() + return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING +end + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4356,30 +4411,45 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a +--- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. -- @param Core.Zone#ZONE Deployzone Deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone) + + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + + table.insert(self.cargoqueue, cargotransport) + + return cargotransport +end + +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoTransport Cargo transport. function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) local transport={} --#OPSGROUP.CargoTransport transport.pickupzone=Pickupzone transport.deployzone=Deployzone - transport.id=1 - transport.status="Planning" - + transport.uid=1 + transport.status="Planning" transport.cargos={} + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) env.info("FF adding cargo group "..cargo.opsgroup:GetName()) - table.insert(transport.cargos, cargo) + table.insert(transport.cargos, cargo) else for _,group in pairs(GroupSet.Set) do - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - table.insert(transport.cargos, cargo) + local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + table.insert(transport.cargos, cargo) end end @@ -4391,7 +4461,7 @@ end -- @param Wrapper.Group#GROUP group The GROUP object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. -- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.Cargo Cargo data. +-- @return #OPSGROUP.CargoGroup Cargo group data. function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) local opsgroup=nil @@ -4425,18 +4495,83 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end ---- On after "Load" event. Carrier loads a cargo group into ints cargo bay. +--- Get total weight of the group including cargo. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Total weight in kg. +function OPSGROUP:GetWeightTotal(UnitName) + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weight + + end + + end + + return weight +end + +--- Get weight of the internal cargo the group is carriing right now. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargo(UnitName) + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weightCargo + + end + + end + + return weight +end + + +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoGroup CargoGroup Cargo group object. +-- @return #OPSGROUP self +function OPSGROUP:_AddCargoGroup(CargoGroup) + + --CargoGroup.status=OPSGROUP.CargoStatus. + + table.insert(self.cargo, CargoGroup) + + return self +end + +--- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Pickup zone. -function OPSGROUP:onafterPickup(From, Event, To, Zone) +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. +function OPSGROUP:onafterPickup(From, Event, To, CargoTransport) env.info("FF pickup") + + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + + local Zone=CargoTransport.pickupzone local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + self.cargoTransport=CargoTransport + + + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -4454,52 +4589,21 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) end ---- Get total weight of the group including cargo. --- @param #OPSGROUP self --- @return #number Total weight in kg. -function OPSGROUP:GetWeightTotal() - - local weight=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if element and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weight - end - - end - - return weight -end - ---- Get weight of the internal cargo the group is carriing right now. --- @param #OPSGROUP self --- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargo() - - local weight=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if element and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weightCargo - end - - end - - return weight -end - - --- On after "Loading" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSGROUP:onafterLoading(From, Event, To) - +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. +function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) env.info("FF loading") + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + + + CargoTransport=CargoTransport or self.cargoTransport + --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element @@ -4518,8 +4622,8 @@ function OPSGROUP:onafterLoading(From, Event, To) end - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --#OPSGROUP.Cargo + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() @@ -4528,7 +4632,8 @@ function OPSGROUP:onafterLoading(From, Event, To) if carrier then -- Order cargo group to board the carrier. - cargo.opsgroup:Board(carrier) + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) else @@ -4546,23 +4651,12 @@ end -- @param #string To To state. -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) - env.info("FF load") - local weight=500 + local weight=CargoGroup:GetWeightTotal() + + local carrier=CargoGroup.carrier - local carrier=nil --#OPSGROUP.Element - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - local cargobay=element.weightMaxCargo-element.weightCargo - - if cargobay>=weight then - carrier=element - break - end - - end if carrier then @@ -4575,12 +4669,91 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) -- Embark ==> Loaded CargoGroup:Embark(carrier) + env.info("FF carrier loaded (todo)") + self:Loaded() + else - self:E(self.lid.."Cound not find a carrier with enough cargo capacity!") + self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") end end +--- On after "Loaded" event. Carrier has loaded all (possible) cargo at the pickup zone. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterLoaded(From, Event, To) + env.info("FF loaded") + + --TODO: analyze current cargo for deploy zones and initiate transport. + + env.info("FF ordering carrier to transport") + self:Transport(self.cargoTransport.deployzone) + + +end + +--- On after "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterTransport(From, Event, To, Zone) + env.info("FF transport") + + -- Set carrier status. + self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + + -- Check if already in deploy zone. + local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + if inzone then + + -- We are already in the pickup zone ==> initiate loading. + self:Deploy() + + else + + -- Get a random coordinate in the pickup zone and let the carrier go there. + local Coordinate=Zone:GetRandomCoordinate() + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + end + +end + +--- On after "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterDeploy(From, Event, To, Zone) + env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + + for _,_cargo in pairs(self.cargo) do + local cargo=_cargo --#OPSGROUP.CargoGroup + + local zone=Zone or cargo.deployzone + + if zone:IsCoordinateInZone(self:GetCoordinate()) then + + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + -- Unload. + self:Unload(cargo.opsgroup, Coordinate, Heading) + + end + + end + +end + --- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -4591,7 +4764,7 @@ end -- @param #number Heading Heading of group. function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) - --TODO: Add check if CargoGroup is + --TODO: Add check if CargoGroup is cargo of this carrier. if CargoGroup:IsInUtero() then CargoGroup:Unboard(Coordinate, Heading) end @@ -4607,14 +4780,20 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element -function OPSGROUP:onafterBoard(From, Event, To, Carrier) - +function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) env.info("FF board") - - self.carrier=Carrier - self:Embark(Carrier) + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.BOARDING + + -- Set carrier. + self.carrier=Carrier + self.carrierGroup=CarrierGroup + + -- Trigger embark event. + self.carrierGroup:Load(self) end @@ -4626,8 +4805,10 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterEmbark(From, Event, To, Carrier) - env.info("FF embark") + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.LOADED -- Despawn this group. self:Despawn(0, true) @@ -4644,8 +4825,10 @@ end -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) - env.info("FF Unboard") + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.DELIVERED -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) @@ -5422,12 +5605,18 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Retreated event. opsgroup:Retreated() - elseif opsgroup:Is("Pickingup") then - --TODO: make IsPickingup() function. + elseif opsgroup:IsPickingup() then opsgroup:FullStop() + opsgroup:Loading() + elseif opsgroup:IsTransporting() then + + opsgroup:FullStop() + + opsgroup:Deploy() + elseif opsgroup:IsEngaging() then -- Nothing to do really. From 261d1cbeba6192579268e210280079bd00c1c9ce Mon Sep 17 00:00:00 2001 From: rollnthndr Date: Fri, 29 Jan 2021 18:15:20 -0700 Subject: [PATCH 089/382] Added a note pointing out that callsigns need to be chosen depending on the type of unit that this task is being assigned to. --- Moose Development/Moose/Wrapper/Controllable.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 595b0e473..4c527e997 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1467,6 +1467,7 @@ end --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. +-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators. -- @param #CONTROLLABLE self -- @param Wrapper.Group#GROUP AttackGroup Target GROUP object. -- @param #number WeaponType Bitmask of weapon types, which are allowed to use. @@ -1474,7 +1475,7 @@ end -- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz. -- @param #number Modulation Modulation of radio for communication. Default 0=AM. --- @param #number CallsignName Callsign enumerator name of the FAC. +-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units) -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber ) From 50d2e42cc30bac2e3687ea1138507aba09bcee72 Mon Sep 17 00:00:00 2001 From: rollnthndr Date: Fri, 29 Jan 2021 18:31:19 -0700 Subject: [PATCH 090/382] Added VSCode settings folder and workspace file to the ignore list. --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 5e42c0c93..96ee72d9a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,13 @@ local.properties .buildpath +##################### +## Visual Studio Code +##################### +*.code-workspace +.vscode/ + + ################# ## Visual Studio ################# From bcf8973eede4c6c2fc82faed08489776202366fc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 30 Jan 2021 21:29:29 +0100 Subject: [PATCH 091/382] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 68 ++++++++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 13 ++- Moose Development/Moose/Ops/OpsGroup.lua | 90 ++++++++++++++------- 3 files changed, 123 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 78d060050..95a622aa5 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -353,10 +353,10 @@ end function ARMYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -476,7 +476,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) --- if self.cargoTransport then - + local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -484,19 +484,57 @@ function ARMYGROUP:onafterStatus(From, Event, To) local name=cargo.opsgroup:GetName() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - - if self:IsPickingup() then - - elseif self:IsLoading() then - - elseif self:IsLoaded() then - - end - + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) end - self:I(self.lid..text) + self:I(self.lid..text) + + + if self:IsNotCarrier() then + + env.info("FF not carrier ==> pickup") + + -- Initiate the cargo transport process. + self:Pickup(self.cargoTransport.pickupzone) + + elseif self:IsPickingup() then + + env.info("FF picking up") + + elseif self:IsLoading() then + + env.info("FF loading") + + local boarding=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then + boarding=true + end + + end + + -- Boarding finished ==> Transport cargo. + if not boarding then + env.info("FF boarding finished ==> Loaded") + self:Loaded() + end + + elseif self:IsLoaded() then + + env.info("FF loaded") + + elseif self:IsTransporting() then + + env.info("FF transporting") + + elseif self:IsUnloading() then + + env.info("FF unloading") + + end + end @@ -1190,7 +1228,7 @@ function ARMYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b4bc02f7f..0fd9130d6 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1010,6 +1010,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Engage Detected Targets --- if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then + env.info("FF 100") -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1019,8 +1020,12 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) for _,_group in pairs(self.detectedgroups:GetSet()) do local group=_group --Wrapper.Group#GROUP + env.info("FF 200") + if group and group:IsAlive() then + env.info("FF 300") + -- Get 3D vector of target. local targetVec3=group:GetVec3() @@ -1029,11 +1034,13 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if distance<=self.engagedetectedRmax and distance1 then -- Route group to all defined waypoints remaining. - self:Route(wp, 1) + self:Route(wp) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 366bbc5ff..9341875a9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -362,17 +362,21 @@ OPSGROUP.TaskType={ --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus +-- @field #string NOTCARRIER This group is not a carrier yet. -- @field #string EMPTY Carrier is empty and ready for cargo transport. -- @field #string PICKUP Carrier is on its way to pickup cargo. -- @field #string LOADING Carrier is loading cargo. -- @field #string LOADED Carrier has loaded cargo. -- @field #string TRANSPORTING Carrier is transporting cargo. +-- @field #string UNLOADING Carrier is unloading cargo. OPSGROUP.CarrierStatus={ + NOTCARRIER="not carrier", EMPTY="empty", PICKUP="pickup", LOADING="loading", LOADED="loaded", TRANSPORTING="transporting", + UNLOADING="unloading", } --- Cargo status. @@ -468,7 +472,7 @@ function OPSGROUP:New(group) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - self.carrierStatus=OPSGROUP.CarrierStatus.EMPTY + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER -- Init task counter. self.taskcurrent=0 @@ -1519,6 +1523,13 @@ function OPSGROUP:IsEngaging() return self:is("Engaging") end +--- Check if the group is not a carrier yet. +-- @param #OPSGROUP self +-- @return #boolean If true, group is not a carrier. +function OPSGROUP:IsNotCarrier() + return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER +end + --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1548,7 +1559,12 @@ function OPSGROUP:IsTransporting() return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING end - +--- Check if the group is unloading cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is unloading. +function OPSGROUP:IsUnloading() + return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions @@ -4556,22 +4572,16 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. -function OPSGROUP:onafterPickup(From, Event, To, CargoTransport) +-- @param Core.Zone#ZONE Zone Pickup zone. +function OPSGROUP:onafterPickup(From, Event, To, Zone) env.info("FF pickup") -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP - - local Zone=CargoTransport.pickupzone local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) - - self.cargoTransport=CargoTransport - - - + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -4594,16 +4604,12 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport assignment. -function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) +function OPSGROUP:onafterLoading(From, Event, To) env.info("FF loading") -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING - - CargoTransport=CargoTransport or self.cargoTransport - --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element @@ -4622,7 +4628,7 @@ function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) end - for _,_cargo in pairs(CargoTransport.cargos) do + for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() @@ -4631,6 +4637,9 @@ function OPSGROUP:onafterLoading(From, Event, To, CargoTransport) if carrier then + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + -- Order cargo group to board the carrier. env.info("FF order group to board carrier") cargo.opsgroup:Board(self, carrier) @@ -4664,13 +4673,13 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) carrier.weightCargo=carrier.weightCargo+weight -- This function is only really used for aircraft and sets the total internal cargo weight. - trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + --trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo -- Embark ==> Loaded CargoGroup:Embark(carrier) - env.info("FF carrier loaded (todo)") - self:Loaded() + --env.info("FF carrier loaded (todo)") + --self:Loaded() else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -4734,21 +4743,27 @@ end -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + + self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING - for _,_cargo in pairs(self.cargo) do + for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - local zone=Zone or cargo.deployzone + env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + + local zone=Zone or cargo.deployzone --Core.Zone#ZONE - if zone:IsCoordinateInZone(self:GetCoordinate()) then + --if zone:IsCoordinateInZone(self:GetCoordinate()) then local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) -- Unload. - self:Unload(cargo.opsgroup, Coordinate, Heading) + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + cargo.opsgroup:Unboard(Coordinate, Heading) + --self:Unload(cargo.opsgroup, Coordinate, Heading) - end + --end end @@ -4792,8 +4807,15 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup + -- Add to current cargo of carrier. + --CarrierGroup:_AddCargoGroup(self) + + --TODO: make cargo run to carrier + --TODO: check if cargo is mobile. if not ==> load + --TODO: check if cargo is alive=true. if only exists ==> load. + -- Trigger embark event. - self.carrierGroup:Load(self) + self.carrierGroup:__Load(10, self) end @@ -4827,11 +4849,12 @@ end function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) env.info("FF Unboard") - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.DELIVERED - -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) + + env.info("FF template") + self:I({template=Template}) + self:I({template=self.template}) -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -4866,7 +4889,14 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) self.carrier.weightCargo=self.carrier.weightCargo-element.weight end -- This function is only really used for aircraft and sets the total internal cargo weight. - trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + if self.isFlightgroup then + trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + end + + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrier=nil + self.carrierGroup=nil -- Respawn group. self:_Respawn(0, Template) From fe55555c67552c9c73cd1f1c48162cce61bf53de Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 30 Jan 2021 23:28:07 +0100 Subject: [PATCH 092/382] OPS - Fixed inAir check for spawned groups - Reduced SCHEDULER min delay to 0.001 sec (was 0.1 sec) --- Moose Development/Moose/AI/AI_Formation.lua | 4 ++-- Moose Development/Moose/Core/ScheduleDispatcher.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 7 ++++--- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Wrapper/Unit.lua | 7 ++++--- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 17c1b5345..aaf4fbe54 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -1140,8 +1140,8 @@ end -- @param DCS#Vec3 CV2 Vec3 function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) - if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then - + if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then + self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) FollowGroup:OptionROTEvadeFire() diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index bfd1dabb4..db04a1500 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -122,7 +122,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr self.Schedule[Scheduler][CallID].Function = ScheduleFunction self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 ) - self.Schedule[Scheduler][CallID].Start = Start + 0.1 + self.Schedule[Scheduler][CallID].Start = Start + 0.001 self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Stop = Stop diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ce641b949..ad0f1a6ce 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1169,7 +1169,8 @@ function FLIGHTGROUP:OnEventBirth(EventData) -- Set element to spawned state. self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) - self:ElementSpawned(element) + -- This is delayed by a millisec because inAir check for units spawned in air failed (returned false even though the unit was spawned in air). + self:__ElementSpawned(0.0, element) end @@ -1436,7 +1437,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) - if Element.unit:InAir() then + if Element.unit:InAir(true) then -- Trigger ElementAirborne event. Add a little delay because spawn is also delayed! self:__ElementAirborne(0.11, Element) else @@ -2008,7 +2009,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) if #wp>1 then -- Route group to all defined waypoints remaining. - self:Route(wp, 1) + self:Route(wp) else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c202ab0ff..b8f52b52e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2223,7 +2223,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) -- Set task for group. - self:SetTask(TaskFinal, 1) + self:SetTask(TaskFinal) end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 705e29303..beb43fc4c 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1174,8 +1174,9 @@ end --- Returns true if the UNIT is in the air. -- @param #UNIT self +-- @param #boolean NoHeloCheck If true, no additonal checks for helos are performed. -- @return #boolean Return true if in the air or #nil if the UNIT is not existing or alive. -function UNIT:InAir() +function UNIT:InAir(NoHeloCheck) self:F2( self.UnitName ) -- Get DCS unit object. @@ -1185,14 +1186,14 @@ function UNIT:InAir() -- Get DCS result of whether unit is in air or not. local UnitInAir = DCSUnit:inAir() - + -- Get unit category. local UnitCategory = DCSUnit:getDesc().category -- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not. -- This is a workaround since DCS currently does not acknoledge that helos land on buildings. -- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier! - if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then + if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then local VelocityVec3 = DCSUnit:getVelocity() local Velocity = UTILS.VecNorm(VelocityVec3) local Coordinate = DCSUnit:getPoint() From 016198e7f22d979fcd220a6e7f07e9b009b29161 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 31 Jan 2021 23:28:04 +0100 Subject: [PATCH 093/382] OPS Cargo --- Moose Development/Moose/Core/Database.lua | 2 + Moose Development/Moose/Ops/ArmyGroup.lua | 42 +++++-- Moose Development/Moose/Ops/FlightGroup.lua | 7 +- Moose Development/Moose/Ops/NavyGroup.lua | 3 + Moose Development/Moose/Ops/OpsGroup.lua | 122 +++++++++++++++----- 5 files changed, 133 insertions(+), 43 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index ecb2d28e1..8ae42a151 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1424,6 +1424,7 @@ end -- @param #DATABASE self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. function DATABASE:AddOpsGroup(opsgroup) + env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end @@ -1439,6 +1440,7 @@ function DATABASE:GetOpsGroup(groupname) groupname=groupname:GetName() end + env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 95a622aa5..93df801d9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -171,6 +171,9 @@ function ARMYGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30) + + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) return self end @@ -477,22 +480,24 @@ function ARMYGROUP:onafterStatus(From, Event, To) if self.cargoTransport then - local text=string.format("Cargo: %s %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - + -- Debug + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local name=cargo.opsgroup:GetName() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - end - + text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) + --self:I({template=cargo.opsgroup.template}) + end self:I(self.lid..text) if self:IsNotCarrier() then env.info("FF not carrier ==> pickup") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. -- Initiate the cargo transport process. self:Pickup(self.cargoTransport.pickupzone) @@ -500,6 +505,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) elseif self:IsPickingup() then env.info("FF picking up") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then @@ -509,7 +516,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then + if cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then boarding=true end @@ -523,15 +530,32 @@ function ARMYGROUP:onafterStatus(From, Event, To) elseif self:IsLoaded() then - env.info("FF loaded") + env.info("FF loaded (nothing to do?)") elseif self:IsTransporting() then - env.info("FF transporting") + env.info("FF transporting (nothing to do)") elseif self:IsUnloading() then - env.info("FF unloading") + env.info("FF unloading ==> Checking if all cargo was delivered") + + local delivered=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + delivered=false + break + end + + end + + -- Boarding finished ==> Transport cargo. + if delivered then + env.info("FF unloading finished ==> Unloaded") + self:Unloaded() + end end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 7417a35cc..9cefad402 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -332,6 +332,9 @@ function FLIGHTGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(3, 10) + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) + return self end @@ -2603,9 +2606,7 @@ function FLIGHTGROUP:onafterStop(From, Event, To) -- Call OPSGROUP function. self:GetParent(self).onafterStop(self, From, Event, To) - - -- Remove flight from data base. - _DATABASE.FLIGHTGROUPS[self.groupname]=nil + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 782788c2f..5cf389465 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -188,6 +188,9 @@ function NAVYGROUP:New(group) -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 60) + + -- Add OPSGROUP to _DATABASE. + _DATABASE:AddOpsGroup(self) return self end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8b13d0d86..ef76d7532 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -410,6 +410,7 @@ OPSGROUP.CargoStatus={ -- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field #boolean delivered If true, group was delivered. --- OpsGroup version. -- @field #string version @@ -555,6 +556,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. ------------------------ --- Pseudo Functions --- @@ -581,7 +583,7 @@ function OPSGROUP:New(group) -- Add to data base. - _DATABASE:AddOpsGroup(self) + --_DATABASE:AddOpsGroup(self) return self end @@ -4419,8 +4421,11 @@ function OPSGROUP:onafterStop(From, Event, To) self:E(self.lid..text) end + -- Remove flight from data base. + _DATABASE.FLIGHTGROUPS[self.groupname]=nil + -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4496,12 +4501,13 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) else opsgroup=ARMYGROUP:New(group) end - + else + env.info("FF found opsgroup in createcargo") end end - local cargo={} --#OPSGROUP.Cargo + local cargo={} --#OPSGROUP.CargoGroup cargo.opsgroup=opsgroup cargo.status="Unknown" @@ -4631,22 +4637,29 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() + if not cargo.delivered then - local carrier=_findCarrier(weight) - - if carrier then - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() - else - - env.info("FF cannot board carrier") + local carrier=_findCarrier(weight) + + if carrier then + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) + + --TODO: only one group for testing + break + + else + + env.info("FF cannot board carrier") + + end end end @@ -4744,26 +4757,33 @@ end function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - local zone=Zone or cargo.deployzone --Core.Zone#ZONE + if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.LOADED and cargo.opsgroup.carrierGroup:GetName()==self.groupname then - --if zone:IsCoordinateInZone(self:GetCoordinate()) then - - local Coordinate=zone:GetRandomCoordinate() - local Heading=math.random(0,359) + env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + + local zone=Zone or cargo.deployzone --Core.Zone#ZONE - -- Unload. - env.info("FF unload cargo "..cargo.opsgroup:GetName()) - cargo.opsgroup:Unboard(Coordinate, Heading) - --self:Unload(cargo.opsgroup, Coordinate, Heading) - - --end + --if zone:IsCoordinateInZone(self:GetCoordinate()) then + + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + cargo.delivered=true + + -- Unload. + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + cargo.opsgroup:Unboard(Coordinate, Heading) + --self:Unload(cargo.opsgroup, Coordinate, Heading) + + --end + + end end @@ -4786,6 +4806,46 @@ function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading end +--- On after "Unloaded" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE Zone Deploy zone. +function OPSGROUP:onafterUnloaded(From, Event, To) + env.info("FF unloaded") + + local pickup=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + -- Check for waiting or undelivered non cargo groups. + --if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.WAITING or (cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO and not cargo.delivered) then + if not cargo.delivered then + pickup=true + break + end + + end + + if pickup then + + env.info("FF cargo left ==> pickup") + self:Pickup(self.cargoTransport.pickupzone) + + else + + -- No current transport assignment. + self.cargoTransport=nil + + env.info("FF all delivered ==> check group done") + self:_CheckGroupDone() + + end + +end + + --- -- Cargo Group Functions --- From ee3ead9aac4f9e07e4f66c4bad3cd7a2575834eb Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Feb 2021 23:20:55 +0100 Subject: [PATCH 094/382] OPS Cargo --- Moose Development/Moose/DCS.lua | 18 +- Moose Development/Moose/Ops/ArmyGroup.lua | 87 +--- Moose Development/Moose/Ops/FlightGroup.lua | 206 ++++++++- Moose Development/Moose/Ops/OpsGroup.lua | 486 +++++++++++++++----- 4 files changed, 585 insertions(+), 212 deletions(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 2d190d215..6824d9c4f 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -460,6 +460,22 @@ do -- Types --@field #boolean lateActivated --@field #boolean uncontrolled + --- DCS template data structure. + -- @type Template + -- @field #boolean uncontrolled Aircraft is uncontrolled. + -- @field #boolean lateActivation Group is late activated. + -- @field #number x 2D Position on x-axis in meters. + -- @field #number y 2D Position on y-axis in meters. + -- @field #table units Unit list. + -- + + --- Unit data structure. + --@type Template.Unit + --@field #string name Name of the unit. + --@field #number x + --@field #number y + --@field #number alt + end -- @@ -1452,4 +1468,4 @@ do -- AI AI = {} --#AI -end -- AI \ No newline at end of file +end -- AI diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 93df801d9..2cf8ab17a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -464,8 +464,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) local ammo=self:GetAmmoElement(element) -- Output text for element. - text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d", - i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + text=text..string.format("\n[%d] %s: status=%s, life=%.1f/%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d, cargo=%d/%d kg", + i, name, status, life, life0, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, element.weightCargo, element.weightMaxCargo) end if #self.elements==0 then text=text.." none!" @@ -478,88 +478,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- Cargo --- - if self.cargoTransport then - - -- Debug - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - text=text..string.format("\n- %s [%s]: %s", name, gstatus, cstatus) - --self:I({template=cargo.opsgroup.template}) - end - self:I(self.lid..text) - - - if self:IsNotCarrier() then - - env.info("FF not carrier ==> pickup") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - - -- Initiate the cargo transport process. - self:Pickup(self.cargoTransport.pickupzone) - - elseif self:IsPickingup() then - - env.info("FF picking up") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - - elseif self:IsLoading() then - - env.info("FF loading") - - local boarding=false - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName() and cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.BOARDING then - boarding=true - end - - end - - -- Boarding finished ==> Transport cargo. - if not boarding then - env.info("FF boarding finished ==> Loaded") - self:Loaded() - end - - elseif self:IsLoaded() then - - env.info("FF loaded (nothing to do?)") - - elseif self:IsTransporting() then - - env.info("FF transporting (nothing to do)") - - elseif self:IsUnloading() then - - env.info("FF unloading ==> Checking if all cargo was delivered") - - local delivered=true - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then - delivered=false - break - end - - end - - -- Boarding finished ==> Transport cargo. - if delivered then - env.info("FF unloading finished ==> Unloaded") - self:Unloaded() - end - - end - - end + self:_CheckCargoTransport() --- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 9cefad402..c84cba1be 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -249,6 +249,7 @@ function FLIGHTGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State + self:AddTransition("*", "LandAtAirbase", "Inbound") -- Helo group is ordered to land at a specific point. self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. @@ -795,7 +796,7 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) local text=string.format("Element %s is dead at t=%.3f! Maybe despawned without notice or landed at a too small airbase. Calling ElementDead in 60 sec to give other events a chance", tostring(element.name), timer.getTime()) self:E(self.lid..text) - self:__ElementDead(60, element) + --self:__ElementDead(60, element) end end @@ -1013,7 +1014,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Engage Detected Targets --- if self:IsAirborne() and self.detectionOn and self.engagedetectedOn and not (self.fuellow or self.fuelcritical) then - env.info("FF 100") -- Target. local targetgroup=nil --Wrapper.Group#GROUP @@ -1023,12 +1023,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) for _,_group in pairs(self.detectedgroups:GetSet()) do local group=_group --Wrapper.Group#GROUP - env.info("FF 200") - if group and group:IsAlive() then - env.info("FF 300") - -- Get 3D vector of target. local targetVec3=group:GetVec3() @@ -1037,8 +1033,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if distance<=self.engagedetectedRmax and distanceself.currentwp then + self.passedfinalwp=false + end + + -- Speed in knots. + Speed=Speed or self.speedCruise + + -- Get coordinate of airbase. + local Coordinate=Airbase:GetCoordinate() + + -- Create air waypoint. + local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO,COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, nil, Airbase, {}, "Landing Temp", nil) + + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Set altitude. + if Altitude then + waypoint.alt=UTILS.FeetToMeters(Altitude) + end + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) + + -- Debug info. + self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) + + -- Update route. + if Updateroute==nil or Updateroute==true then + self:__UpdateRoute(-1) + end + + return waypoint +end + --- Check if a unit is an element of the flightgroup. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ef76d7532..77de1541b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -96,6 +96,7 @@ -- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. +-- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. @@ -546,7 +547,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. - self:AddTransition("*", "Board", "Boarding") -- Group is boarding a cargo carrier. + self:AddTransition("*", "Board", "*") -- Group is boarding a cargo carrier. self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. @@ -556,6 +557,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. ------------------------ @@ -1255,6 +1257,9 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if DCSGroup then + -- Clear any task ==> makes DCS crash! + --self.group:ClearTasks() + -- Get all units. local units=self:GetDCSUnits() @@ -1539,7 +1544,6 @@ function OPSGROUP:IsPickingup() return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP end - --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1547,16 +1551,9 @@ function OPSGROUP:IsLoading() return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING end ---- Check if the group is picking up cargo. +--- Check if the group is transporting cargo. -- @param #OPSGROUP self --- @return #boolean If true, group is picking up. -function OPSGROUP:IsLoaded() - return self.carrierStatus==OPSGROUP.CarrierStatus.LOADED -end - ---- Check if the group is picking up cargo. --- @param #OPSGROUP self --- @return #boolean If true, group is picking up. +-- @return #boolean If true, group is transporting. function OPSGROUP:IsTransporting() return self.carrierStatus==OPSGROUP.CarrierStatus.TRANSPORTING end @@ -1568,6 +1565,48 @@ function OPSGROUP:IsUnloading() return self.carrierStatus==OPSGROUP.CarrierStatus.UNLOADING end + +--- Check if the group is **not** cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is *not* cargo. +function OPSGROUP:IsNotCargo() + return self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO +end + +--- Check if the group is currently boarding a carrier. +-- @param #OPSGROUP self +-- @param #string CarrierGroupName (Optional) Additionally check if group is boarding this particular carrier group. +-- @return #boolean If true, group is boarding. +function OPSGROUP:IsBoarding(CarrierGroupName) + if CarrierGroupName then + if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + return false + end + end + return self.cargoStatus==OPSGROUP.CargoStatus.BOARDING +end + +--- Check if the group is currently loaded into a carrier. +-- @param #OPSGROUP self +-- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into this particular carrier group. +-- @return #boolean If true, group is loaded. +function OPSGROUP:IsLoaded(CarrierGroupName) + if CarrierGroupName then + if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + return false + end + end + return self.cargoStatus==OPSGROUP.CargoStatus.LOADED +end + +--- Check if the group is cargo and waiting for a carrier to pick it up. +-- @param #OPSGROUP self +-- @return #boolean If true, group is waiting for a carrier. +function OPSGROUP:IsWaitingAsCargo() + return self.cargoStatus==OPSGROUP.CargoStatus.WAITING +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2562,9 +2601,14 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) if self.taskcurrent>0 then self:TaskCancel() end - + -- Set current task. self.taskcurrent=Task.id + + -- + if self:GetTaskCurrent()==nil then + table.insert(self.taskqueue, Task) + end -- Set time stamp. Task.timestamp=timer.getAbsTime() @@ -4432,6 +4476,111 @@ end -- Cargo Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Check cargo transport assignments. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:_CheckCargoTransport() + + if self.cargoTransport then + + -- TODO: Check if this group can actually transport any cargo. + + -- Debug info. + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + text=text..string.format("\n- %s [%s]: %s (weight %.1f kg)", name, gstatus, cstatus, weight) + end + self:I(self.lid..text) + + + if self:IsNotCarrier() then + + env.info("FF not carrier ==> pickup") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. + + -- Initiate the cargo transport process. + self:Pickup(self.cargoTransport.pickupzone) + + elseif self:IsPickingup() then + + env.info("FF picking up") + + --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. + + elseif self:IsLoading() then + + env.info("FF loading") + + local boarding=false + local gotcargo=false + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + -- Check if anyone is still boarding. + if cargo.opsgroup:IsBoarding(self.groupname) then + boarding=true + end + + -- Check if we have any cargo to transport. + if cargo.opsgroup:IsLoaded(self.groupname) then + gotcargo=true + end + + end + + -- Boarding finished ==> Transport cargo. + if gotcargo and not boarding then + env.info("FF boarding finished ==> Loaded") + self:Loaded() + end + + -- No cargo and noone is boarding ==> check again if we can make anyone board. + if not gotcargo and not boarding then + self:Loading() + end + + elseif self:IsLoaded() then + + env.info("FF loaded (nothing to do?)") + + elseif self:IsTransporting() then + + env.info("FF transporting (nothing to do)") + + elseif self:IsUnloading() then + + env.info("FF unloading ==> Checking if all cargo was delivered") + + local delivered=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + delivered=false + break + end + + end + + -- Boarding finished ==> Transport cargo. + if delivered then + env.info("FF unloading finished ==> Unloaded") + self:Unloaded() + end + + end + + end + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. @@ -4474,6 +4623,12 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) end end + local text=string.format("Created Cargo Transport (UID=%d) from %s -->%s", transport.uid, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for _,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal()) + end + return transport end @@ -4536,7 +4691,7 @@ function OPSGROUP:GetWeightTotal(UnitName) end return weight -end +end --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -4559,6 +4714,41 @@ function OPSGROUP:GetWeightCargo(UnitName) return weight end +--- Add weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #number Weight Cargo weight to be added in kg. +function OPSGROUP:AddWeightCargo(UnitName, Weight) + + local element=self:GetElementByName(UnitName) + + if element and element.unit and element.unit:IsAlive() then + + -- Add weight. + element.weightCargo=element.weightCargo+Weight + + -- For airborne units, we set the weight in game. + if self.isFlightgroup then + trigger.action.setUnitInternalCargo(element.name, element.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + end + + end + + return self +end + +--- Reduce weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. +-- @param #number Weight Cargo weight to be reduced in kg. +function OPSGROUP:RedWeightCargo(UnitName, Weight) + + -- Reduce weight. + self:AddWeightCargo(UnitName, -Weight) + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self @@ -4580,12 +4770,13 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Pickup zone. function OPSGROUP:onafterPickup(From, Event, To, Zone) - - env.info("FF pickup") + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP - + self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + + -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) if inzone then @@ -4598,8 +4789,16 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + if self.isFlightgroup then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + else + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + end + end @@ -4611,25 +4810,29 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) - env.info("FF loading") + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + + -- Create a temp array and monitor the free cargo space for each element. + local cargobay={} + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + cargobay[element.name]=element.weightMaxCargo-element.weightCargo + end + --- Find a carrier which can load a given weight. local function _findCarrier(weight) local carrier=nil --#OPSGROUP.Element for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - local cargobay=element.weightMaxCargo-element.weightCargo - - if cargobay>=weight then + local element=_element --#OPSGROUP.Element + if cargobay[element.name]>=weight then return element - end - - end - + end + end return nil end @@ -4637,30 +4840,37 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if not cargo.delivered then + if cargo.opsgroup:IsNotCargo() and not cargo.delivered then - local weight=cargo.opsgroup:GetWeightTotal() + -- Check if cargo is in pickup zone. + local inzone=self.cargoTransport.pickupzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local carrier=_findCarrier(weight) + -- First check if cargo is not delivered yet. + if inzone then - if carrier then - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() - --TODO: only one group for testing - break + local carrier=_findCarrier(weight) - else - - env.info("FF cannot board carrier") + if carrier then + + -- Decrease free cargo bay. + cargobay[carrier.name]=cargobay[carrier.name]-weight + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + env.info("FF order group to board carrier") + cargo.opsgroup:Board(self, carrier) + + else + + env.info("FF cannot board carrier") + + end end - end end @@ -4682,18 +4892,12 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) if carrier then - -- TODO: add function to set/add/get internal cargo. - carrier.weightCargo=carrier.weightCargo+weight - - -- This function is only really used for aircraft and sets the total internal cargo weight. - --trigger.action.setUnitInternalCargo(carrier.name, carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo + -- Add weight to carrier. + self:AddWeightCargo(carrier.name, weight) -- Embark ==> Loaded CargoGroup:Embark(carrier) - - --env.info("FF carrier loaded (todo)") - --self:Loaded() - + else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") end @@ -4708,12 +4912,15 @@ end function OPSGROUP:onafterLoaded(From, Event, To) env.info("FF loaded") - --TODO: analyze current cargo for deploy zones and initiate transport. - - env.info("FF ordering carrier to transport") - self:Transport(self.cargoTransport.deployzone) - - + -- Cancel landedAt task. + if self.isFlightgroup and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end + + -- Order group to transport. + self:__Transport(1, self.cargoTransport.deployzone) + end --- On after "Transport" event. @@ -4723,8 +4930,9 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterTransport(From, Event, To, Zone) - env.info("FF transport") - + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) + -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING @@ -4734,28 +4942,38 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) if inzone then -- We are already in the pickup zone ==> initiate loading. - self:Deploy() + self:Deploy(Zone) else -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + if self.isFlightgroup then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + local dist=self:GetCoordinate():Get2DDistance(waypoint.coordinate) + env.info(string.format("FF adding transport detour wp with uid=%d at dist=%d m", waypoint.uid, dist)) + else + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + end end end ---- On after "Transport" event. +--- On after "Deploy" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterDeploy(From, Event, To, Zone) - env.info("FF deploy at zone ".. (Zone and Zone:GetName() or "Not given!")) + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING @@ -4763,7 +4981,8 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.LOADED and cargo.opsgroup.carrierGroup:GetName()==self.groupname then + -- Check that cargo is loaded into this group. + if cargo.opsgroup:IsLoaded(self.groupname) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -4774,12 +4993,12 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) + -- Cargo was delivered. cargo.delivered=true -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) - cargo.opsgroup:Unboard(Coordinate, Heading) - --self:Unload(cargo.opsgroup, Coordinate, Heading) + self:Unload(cargo.opsgroup, Coordinate, Heading) --end @@ -4794,14 +5013,14 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. +-- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. -function OPSGROUP:onafterUnload(From, Event, To, CargoGroup, Coordinate, Heading) +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) --TODO: Add check if CargoGroup is cargo of this carrier. - if CargoGroup:IsInUtero() then - CargoGroup:Unboard(Coordinate, Heading) + if OpsGroup:IsInUtero() then + OpsGroup:Unboard(Coordinate, Heading) end end @@ -4815,6 +5034,12 @@ end function OPSGROUP:onafterUnloaded(From, Event, To) env.info("FF unloaded") + -- Cancel landedAt task. + if self.isFlightgroup and self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end + local pickup=false for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4839,7 +5064,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self.cargoTransport=nil env.info("FF all delivered ==> check group done") - self:_CheckGroupDone() + self:_CheckGroupDone(0.1) end @@ -4858,7 +5083,8 @@ end -- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) - env.info("FF board") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING @@ -4874,8 +5100,20 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) --TODO: check if cargo is mobile. if not ==> load --TODO: check if cargo is alive=true. if only exists ==> load. - -- Trigger embark event. - self.carrierGroup:__Load(10, self) + if self.speedMax>0 then + + local Coordinate=Carrier.unit:GetCoordinate() + + --TODO: NAVYGROUP and FLIGHTGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + + else + + -- Trigger Load event in 10 seconds. + self.carrierGroup:__Load(10, self) + + end end @@ -4887,7 +5125,8 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterEmbark(From, Event, To, Carrier) - env.info("FF embark") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.LOADED @@ -4907,14 +5146,12 @@ end -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) - env.info("FF Unboard") + -- Debug info. + self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + -- Template for the respawned group. - local Template=UTILS.DeepCopy(self.template) - - env.info("FF template") - self:I({template=Template}) - self:I({template=self.template}) + local Template=UTILS.DeepCopy(self.template) --DCS#Template -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -4942,16 +5179,10 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) end end - + -- Reduce carrier weight. - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - self.carrier.weightCargo=self.carrier.weightCargo-element.weight - end - -- This function is only really used for aircraft and sets the total internal cargo weight. - if self.isFlightgroup then - trigger.action.setUnitInternalCargo(self.carrier.name, self.carrier.weightCargo) --https://wiki.hoggitworld.com/view/DCS_func_setUnitInternalCargo - end + local weight=self:GetWeightTotal() + self.carrierGroup:RedWeightCargo(self.carrier.name, weight) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO @@ -5669,7 +5900,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Debug message. local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:T(opsgroup.lid..text) + opsgroup:I(opsgroup.lid..text) -- Trigger PassingWaypoint event. if waypoint.astar then @@ -5697,15 +5928,48 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsPickingup() then - opsgroup:FullStop() + if opsgroup.isFlightgroup then - opsgroup:Loading() + -- Land at current pos and wait for 60 min max. + opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + else + -- Stop and loading. + opsgroup:FullStop() + opsgroup:Loading() + end + elseif opsgroup:IsTransporting() then - - opsgroup:FullStop() + + if opsgroup.isFlightgroup then + + env.info("FF passing waypointg in state istransporting ==> land at") - opsgroup:Deploy() + -- Land at current pos and wait for 60 min max. + opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) + + else + -- Stop and loading. + opsgroup:FullStop() + opsgroup:Deploy() + end + + elseif opsgroup:IsBoarding() then + + if opsgroup.carrierGroup and opsgroup.carrierGroup:IsAlive() then + + if opsgroup.carrier and opsgroup.carrier.unit and opsgroup.carrier.unit:IsAlive() then + + -- Load group into the carrier. + opsgroup.carrierGroup:Load(opsgroup) + + else + opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") + end + + else + opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier GROUP as it is NOT alive!") + end elseif opsgroup:IsEngaging() then @@ -6644,7 +6908,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__InUtero(-0.5) + self:InUtero() end elseif newstatus==OPSGROUP.ElementStatus.SPAWNED then @@ -6761,13 +7025,17 @@ end -- @return #OPSGROUP.Element The element. function OPSGROUP:GetElementByName(unitname) - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element + if unitname and type(unitname)=="string" then - if element.name==unitname then - return element + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.name==unitname then + return element + end + end - + end return nil @@ -7117,7 +7385,7 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) element.weightCargo=0 element.weight=element.weightEmpty+element.weightCargo From 7d83c251a8c0a80dc8c30c09c10f920192a52944 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Feb 2021 22:51:18 +0100 Subject: [PATCH 095/382] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 31 +- Moose Development/Moose/Ops/NavyGroup.lua | 58 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 298 ++++++++++++++------ 3 files changed, 241 insertions(+), 146 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c84cba1be..efef49eee 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -57,7 +57,6 @@ -- @field #number Tparking Abs. mission time stamp when the group was spawned uncontrolled and is parking. -- @field #table menu F10 radio menu. -- @field #string controlstatus Flight control status. --- @field #boolean ishelo If true, the is a helicopter group. -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. @@ -146,7 +145,7 @@ FLIGHTGROUP = { Tholding = nil, Tparking = nil, menu = nil, - ishelo = nil, + isHelo = nil, } @@ -1004,7 +1003,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Airboss Helo --- - if self.ishelo and self.airboss and self:IsHolding() then + if self.isHelo and self.airboss and self:IsHolding() then if self.airboss:IsRecovering() or self:IsFuelCritical() then self:ClearToLand() end @@ -1445,7 +1444,7 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) else -- Helos with skids land directly on parking spots. - if self.ishelo then + if self.isHelo then local Spot=self:GetParkingSpot(Element, 10, airbase) @@ -2204,8 +2203,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) - SpeedHold=SpeedHold or (self.ishelo and 80 or 250) - SpeedLand=SpeedLand or (self.ishelo and 40 or 170) + SpeedHold=SpeedHold or (self.isHelo and 80 or 250) + SpeedLand=SpeedLand or (self.isHelo and 40 or 170) -- Clear holding time in any case. self.Tholding=nil @@ -2214,7 +2213,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d", airbase:GetName(), SpeedTo, SpeedHold, SpeedLand) self:T(self.lid..text) - local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000 + local althold=self.isHelo and 1000+math.random(10)*100 or math.random(4,10)*1000 -- Holding points. local c0=self.group:GetCoordinate() @@ -2244,8 +2243,8 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) end -- Altitude above ground for a glide slope of 3 degrees. - local x1=self.ishelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) - local x2=self.ishelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) + local x1=self.isHelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) + local x2=self.isHelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) local alpha=math.rad(3) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) @@ -2365,8 +2364,8 @@ end function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) Coord=Coord or self.group:GetCoordinate() - Altitude=Altitude or (self.ishelo and 1000 or 10000) - Speed=Speed or (self.ishelo and 80 or 250) + Altitude=Altitude or (self.isHelo and 1000 or 10000) + Speed=Speed or (self.isHelo and 80 or 250) -- Debug message. local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) @@ -2464,7 +2463,7 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) elseif self.airboss then - if self.ishelo then + if self.isHelo then local carrierpos=self.airboss:GetCoordinate() local carrierheading=self.airboss:GetHeading() @@ -2578,7 +2577,7 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate where to land. Default is current position. -- @param #number Duration The duration in seconds to remain on ground. Default 600 sec (10 min). function FLIGHTGROUP:onbeforeLandAt(From, Event, To, Coordinate, Duration) - return self.ishelo + return self.isHelo end --- On after "LandAt" event. Order helicopter to land at a specific point. @@ -2804,7 +2803,7 @@ function FLIGHTGROUP:_InitGroup() self.isGround=false -- Helo group. - self.ishelo=group:IsHelicopter() + self.isHelo=group:IsHelicopter() -- Is (template) group uncontrolled. self.isUncontrolled=self.template.uncontrolled @@ -2816,7 +2815,7 @@ function FLIGHTGROUP:_InitGroup() self.speedMax=group:GetSpeedMax() -- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings. - local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) + local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) -- Cruise speed: 70% of max speed but within limit. self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit) @@ -2844,7 +2843,7 @@ function FLIGHTGROUP:_InitGroup() self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. - if self.ishelo then + if self.isHelo then self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 else self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 5cf389465..00b1c3de0 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -609,6 +609,11 @@ function NAVYGROUP:onafterStatus(From, Event, To) end + --- + -- Cargo + --- + + self:_CheckCargoTransport() --- -- Tasks & Missions @@ -1164,50 +1169,19 @@ function NAVYGROUP:_InitGroup() -- Get all units of the group. local units=self.group:GetUnits() - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - - -- Get unit template. - local unittemplate=unit:GetTemplate() - - local element={} --#NAVYGROUP.Element - element.name=unit:GetName() - element.unit=unit - element.status=OPSGROUP.ElementStatus.INUTERO - element.typename=unit:GetTypeName() - element.skill=unittemplate.skill or "Unknown" - element.ai=true - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.size, element.length, element.height, element.width=unit:GetObjectSize() - element.ammo0=self:GetAmmoUnit(unit, false) - - -- Debug text. - if self.verbose>=2 then - local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", - element.name, element.status, element.skill, element.categoryname, element.category, element.size, element.length, element.height, element.width) - self:I(self.lid..text) - end - - -- Add element to table. - table.insert(self.elements, element) - - -- Get Descriptors. - self.descriptors=self.descriptors or unit:GetDesc() - - -- Set type name. - self.actype=self.actype or unit:GetTypeName() - - if unit:IsAlive() then - -- Trigger spawned event. - self:ElementSpawned(element) - end - + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) end - - + + -- Get Descriptors. + self.descriptors=units[1]:GetDesc() + + -- Set type name. + self.actype=units[1]:GetTypeName() + -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 77de1541b..082e4ae7c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -25,6 +25,7 @@ -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. +-- @field #boolean isHelo If true, the is a helicopter group. -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -97,7 +98,6 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. --- @field #table cargo Table containing all cargo of the carrier. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. @@ -157,7 +157,6 @@ OPSGROUP = { Ndestroyed = 0, Nkills = 0, weaponData = {}, - cargo = {}, cargoqueue = {}, } @@ -402,8 +401,13 @@ OPSGROUP.CargoStatus={ -- @type OPSGROUP.CargoTransport -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. -- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. +-- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #number importance Importance of this transport. Smaller=higher. +-- @field #number Tstart Start time in abs. seconds. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. +-- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -3513,7 +3517,6 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) elseif self.isArmygroup then NAVYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude) end - else @@ -4298,7 +4301,7 @@ function OPSGROUP:onafterRespawn(From, Event, To, Template) --self.respawning=true - self:_Respawn(0, template, Reset) + self:_Respawn(0, template) end @@ -4314,7 +4317,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) else - env.info("FF _Respawn") + self:I(self.lid.."FF _Respawn") -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) @@ -4390,9 +4393,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end - -- Currently respawning. - --self.respawning=true - + -- Debug output. self:I({Template=Template}) -- Spawn new group. @@ -4481,26 +4482,93 @@ end -- @return #OPSGROUP self function OPSGROUP:_CheckCargoTransport() + -- Abs. missin time in seconds. + local Time=timer.getAbsTime() + if self.cargoTransport then - -- TODO: Check if this group can actually transport any cargo. + + local done=true + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + --TODO: check if cargo is too heavy for this carrier group ==> elseif + + end + + if done then + self.cargoTransport.status="Delivered" + end + + end + + -- Check if there is anything in the queue. + if not self.cargoTransport then + + -- Current position. + local coord=self:GetCoordinate() + + -- Sort results table wrt prio and distance to pickup zone. + local function _sort(a, b) + local transportA=a --#OPSGROUP.CargoTransport + local transportB=b --#OPSGROUP.CargoTransport + local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) + local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) + return (transportA.prio=cargotransport.Tstart and cargotransport.status~="Delivered" and (cargotransport.importance==nil or cargotransport.importance<=vip) then + self.cargoTransport=cargotransport + break + end + + end + + end + + -- Now handle the transport. + if self.cargoTransport then -- Debug info. - local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup:GetName() - local gstatus=cargo.opsgroup:GetState() - local cstatus=cargo.opsgroup.cargoStatus - local weight=cargo.opsgroup:GetWeightTotal() - text=text..string.format("\n- %s [%s]: %s (weight %.1f kg)", name, gstatus, cstatus, weight) - end - self:I(self.lid..text) + if self.verbose>=0 then + local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) + for _,_cargo in pairs(self.cargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local name=cargo.opsgroup:GetName() + local gstatus=cargo.opsgroup:GetState() + local cstatus=cargo.opsgroup.cargoStatus + local weight=cargo.opsgroup:GetWeightTotal() + text=text..string.format("\n- %s (%.1f kg) [%s]: %s delivered=%s", name, weight, gstatus, cstatus, tostring(cargo.delivered)) + end + self:I(self.lid..text) + end if self:IsNotCarrier() then - env.info("FF not carrier ==> pickup") + self:I(self.lid.."Not carrier ==> pickup") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. @@ -4509,13 +4577,13 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsPickingup() then - env.info("FF picking up") + self:I(self.lid.."Picking up...") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then - env.info("FF loading") + self:I(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -4536,7 +4604,7 @@ function OPSGROUP:_CheckCargoTransport() -- Boarding finished ==> Transport cargo. if gotcargo and not boarding then - env.info("FF boarding finished ==> Loaded") + self:I(self.lid.."Boarding finished ==> Loaded") self:Loaded() end @@ -4544,18 +4612,14 @@ function OPSGROUP:_CheckCargoTransport() if not gotcargo and not boarding then self:Loading() end - - elseif self:IsLoaded() then - - env.info("FF loaded (nothing to do?)") elseif self:IsTransporting() then - env.info("FF transporting (nothing to do)") + self:I(self.lid.."Transporting (nothing to do)") elseif self:IsUnloading() then - env.info("FF unloading ==> Checking if all cargo was delivered") + self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -4570,7 +4634,7 @@ function OPSGROUP:_CheckCargoTransport() -- Boarding finished ==> Transport cargo. if delivered then - env.info("FF unloading finished ==> Unloaded") + self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() end @@ -4584,12 +4648,17 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). +-- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. +-- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. +-- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. +-- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) table.insert(self.cargoqueue, cargotransport) @@ -4599,40 +4668,72 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). +-- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. +-- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. +-- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. +-- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) - local transport={} --#OPSGROUP.CargoTransport + -- Current mission time. + local Tnow=timer.getAbsTime() + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Data structure. + local transport={} --#OPSGROUP.CargoTransport transport.pickupzone=Pickupzone transport.deployzone=Deployzone transport.uid=1 - transport.status="Planning" + transport.status="Planning" + transport.embarkzone=Embarkzone or Pickupzone + transport.disembarkzone=Disembarkzone or Deployzone + transport.prio=Prio or 50 + transport.importance=Importance + transport.Tstart=Tstart transport.cargos={} + -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + -- We got a single GROUP or OPSGROUP objectg. local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - env.info("FF adding cargo group "..cargo.opsgroup:GetName()) table.insert(transport.cargos, cargo) else + -- We got a SET_GROUP object. for _,group in pairs(GroupSet.Set) do local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) table.insert(transport.cargos, cargo) end end - local text=string.format("Created Cargo Transport (UID=%d) from %s -->%s", transport.uid, transport.pickupzone:GetName(), transport.deployzone:GetName()) - for _,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal()) + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + transport.uid, transport.pickupzone:GetName(), transport.embarkzone:GetName(), transport.deployzone:GetName(), transport.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #transport.cargos, Weight) + self:I(self.lid..text) end return transport end ---- Create a +--- Create a cargo group data structure. -- @param #OPSGROUP self -- @param Wrapper.Group#GROUP group The GROUP object. -- @param Core.Zone#ZONE Pickupzone Pickup zone. @@ -4665,6 +4766,7 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) local cargo={} --#OPSGROUP.CargoGroup cargo.opsgroup=opsgroup + cargo.delivered=false cargo.status="Unknown" cargo.pickupzone=Pickupzone cargo.deployzone=Deployzone @@ -4743,26 +4845,12 @@ end -- @param #number Weight Cargo weight to be reduced in kg. function OPSGROUP:RedWeightCargo(UnitName, Weight) - -- Reduce weight. + -- Reduce weight by adding negative weight. self:AddWeightCargo(UnitName, -Weight) return self end - ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param #OPSGROUP.CargoGroup CargoGroup Cargo group object. --- @return #OPSGROUP self -function OPSGROUP:_AddCargoGroup(CargoGroup) - - --CargoGroup.status=OPSGROUP.CargoStatus. - - table.insert(self.cargo, CargoGroup) - - return self -end - --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4789,17 +4877,23 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + -- Add waypoint. if self.isFlightgroup then - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) - waypoint.detour=true - else - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + if self.isHelo then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + else + --TODO: airplane! pickup at airbase. check if already at airbase or make plane go there. + end + elseif self.isNavygroup then + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + elseif self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true end - end end @@ -4840,20 +4934,26 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup + env.info("FF trying cargo!") + if cargo.opsgroup:IsNotCargo() and not cargo.delivered then -- Check if cargo is in pickup zone. - local inzone=self.cargoTransport.pickupzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) + local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) -- First check if cargo is not delivered yet. if inzone then + env.info("FF trying cargo 2!") + local weight=cargo.opsgroup:GetWeightTotal() local carrier=_findCarrier(weight) if carrier then + env.info("FF trying cargo3!") + -- Decrease free cargo bay. cargobay[carrier.name]=cargobay[carrier.name]-weight @@ -4869,9 +4969,15 @@ function OPSGROUP:onafterLoading(From, Event, To) env.info("FF cannot board carrier") end - + + else + env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end + + else + env.info("FF cargo already cargo or delivered") end + end end @@ -4949,16 +5055,24 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - --TODO: Add NAVYGROUP and FLIGHTGROUP waypoint + -- Set waypoint. if self.isFlightgroup then - Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) - waypoint.detour=true - local dist=self:GetCoordinate():Get2DDistance(waypoint.coordinate) - env.info(string.format("FF adding transport detour wp with uid=%d at dist=%d m", waypoint.uid, dist)) - else + -- FLIGHTGROUP + if self.isHelo then + Coordinate:SetAltitude(200) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + waypoint.detour=true + else + -- TODO: airplane! let plane fly to airbase. + end + elseif self.isArmygroup then + -- ARMYGROUP local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) waypoint.detour=true + elseif self.isNavygroup then + -- NAVYGROUP + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true end end @@ -4986,7 +5100,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - local zone=Zone or cargo.deployzone --Core.Zone#ZONE + local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE --if zone:IsCoordinateInZone(self:GetCoordinate()) then @@ -5093,20 +5207,21 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup - -- Add to current cargo of carrier. - --CarrierGroup:_AddCargoGroup(self) + -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. + local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) - --TODO: make cargo run to carrier - --TODO: check if cargo is mobile. if not ==> load - --TODO: check if cargo is alive=true. if only exists ==> load. - - if self.speedMax>0 then + if board then + -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() - --TODO: NAVYGROUP and FLIGHTGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - waypoint.detour=true + if self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + waypoint.detour=true + else + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + end else @@ -5129,11 +5244,18 @@ function OPSGROUP:onafterEmbark(From, Event, To, Carrier) self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.LOADED + self.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} -- Despawn this group. self:Despawn(0, true) + -- Set carrier (again). self.carrier=Carrier end From 8bb9f0d7c0172635b566de42fb7dd46fcc53fd99 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Feb 2021 09:41:24 +0100 Subject: [PATCH 096/382] OPS Cargo --- Moose Development/Moose/Core/Point.lua | 17 +- Moose Development/Moose/Ops/FlightGroup.lua | 77 ++- Moose Development/Moose/Ops/OpsGroup.lua | 504 +++++++++++++++++--- 3 files changed, 504 insertions(+), 94 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 864b56bd1..612cd4cfd 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1745,10 +1745,9 @@ do -- COORDINATE --- Creates an explosion at the point of a certain intensity. -- @param #COORDINATE self -- @param #number ExplosionIntensity Intensity of the explosion in kg TNT. Default 100 kg. - -- @param #number Delay Delay before explosion in seconds. + -- @param #number Delay (Optional) Delay before explosion is triggered in seconds. -- @return #COORDINATE self function COORDINATE:Explosion( ExplosionIntensity, Delay ) - self:F2( { ExplosionIntensity } ) ExplosionIntensity=ExplosionIntensity or 100 if Delay and Delay>0 then self:ScheduleOnce(Delay, self.Explosion, self, ExplosionIntensity) @@ -1760,11 +1759,17 @@ do -- COORDINATE --- Creates an illumination bomb at the point. -- @param #COORDINATE self - -- @param #number power Power of illumination bomb in Candela. + -- @param #number Power Power of illumination bomb in Candela. Default 1000 cd. + -- @param #number Delay (Optional) Delay before bomb is ignited in seconds. -- @return #COORDINATE self - function COORDINATE:IlluminationBomb(power) - self:F2() - trigger.action.illuminationBomb( self:GetVec3(), power ) + function COORDINATE:IlluminationBomb(Power, Delay) + Power=Power or 1000 + if Delay and Delay>0 then + self:ScheduleOnce(Delay, self.IlluminationBomb, self, Power) + else + trigger.action.illuminationBomb(self:GetVec3(), Power) + end + return self end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index efef49eee..1c78a5144 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -31,10 +31,6 @@ --- FLIGHTGROUP class. -- @type FLIGHTGROUP --- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. --- @field Wrapper.Airbase#AIRBASE destbase The destination base of the flight group. --- @field Core.Zone#ZONE homezone The home zone of the flight group. Set when spawn happens in air. --- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #string actype Type name of the aircraft. -- @field #number rangemax Max range in km. -- @field #number ceiling Max altitude the aircraft can fly at in meters. @@ -690,10 +686,17 @@ function FLIGHTGROUP:StartUncontrolled(delay) self:ScheduleOnce(delay, FLIGHTGROUP.StartUncontrolled, self) else - if self:IsAlive() then - --TODO: check Alive==true and Alive==false ==> Activate first - self:T(self.lid.."Starting uncontrolled group") - self.group:StartUncontrolled(delay) + local alive=self:IsAlive() + + if alive~=nil then + -- Check if group is already active. + local _delay=0 + if alive==false then + self:Activate() + _delay=1 + end + self:I(self.lid.."Starting uncontrolled group") + self.group:StartUncontrolled(_delay) self.isUncontrolled=true else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") @@ -1443,18 +1446,21 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) else + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) + -- Helos with skids land directly on parking spots. if self.isHelo then local Spot=self:GetParkingSpot(Element, 10, airbase) - self:_SetElementParkingAt(Element, Spot) + if Spot then + self:_SetElementParkingAt(Element, Spot) + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) + end end - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) - end end @@ -1469,6 +1475,7 @@ end function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) + -- Set element parking. self:_SetElementParkingAt(Element, Parking) -- Set element status. @@ -1661,9 +1668,22 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterAirborne(From, Event, To) self:T(self.lid..string.format("Flight airborne")) + + -- No current airbase any more. + self.currbase=nil if self.isAI then - self:_CheckGroupDone(1) + if self:IsTransporting() then + env.info("FF transporting land at airbase ") + local airbase=self.cargoTransport.deployzone:GetAirbase() + self:LandAtAirbase(airbase) + elseif self:IsPickingup() then + env.info("FF pickingup land at airbase ") + local airbase=self.cargoTransport.pickupzone:GetAirbase() + self:LandAtAirbase(airbase) + else + self:_CheckGroupDone(1) + end else self:_UpdateMenu() end @@ -1715,7 +1735,6 @@ function FLIGHTGROUP:onafterLandedAt(From, Event, To) end - --- On after "Arrived" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -1801,6 +1820,13 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) -- Reset. self.isLandingAtAirbase=nil + -- Init (un-)loading process. + if self:IsPickingup() then + self:__Loading(-1) + elseif self:IsTransporting() then + self:__Deploy(-1) + end + else -- Depawn after 5 min. Important to trigger dead events before DCS despawns on its own without any notification. self:Despawn(5*60) @@ -2031,6 +2057,9 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Number of mission remaining. local nMissions=self:CountRemainingMissison() + + -- Number of cargo transports remaining. + local nTransports=self:CountRemainingTransports() -- Final waypoint passed? if self.passedfinalwp then @@ -2039,7 +2068,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay) if self.currentmission==nil and self.taskcurrent==0 then -- Number of remaining tasks/missions? - if nTasks==0 and nMissions==0 then + if nTasks==0 and nMissions==0 and nTransports==0 then local destbase=self.destbase or self.homebase local destzone=self.destzone or self.homezone @@ -2201,6 +2230,9 @@ end -- @param #number SpeedLand Landing speed in knots. Default 170 kts. function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) + -- Set current airbase. + self.currbase=airbase + -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) SpeedHold=SpeedHold or (self.isHelo and 80 or 250) @@ -2287,10 +2319,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - elseif airbase:IsShip() then + elseif airbase:IsShip() or airbase:IsHelipad() then --- - -- Ship + -- Ship or Helipad --- local pland=airbase:GetCoordinate() @@ -2713,6 +2745,8 @@ function FLIGHTGROUP:onafterStop(From, Event, To) end end + + self.currbase=nil -- Handle events: self:UnHandleEvent(EVENTS.Birth) @@ -3542,12 +3576,21 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) + -- Coordinate of unit landed local coord=element.unit:GetCoordinate() + -- Airbase. airbase=airbase or self:GetClosestAirbase() --coord:GetClosestAirbase(nil, self:GetCoalition()) -- TODO: replace by airbase.parking if AIRBASE is updated. local parking=airbase:GetParkingSpotsTable() + + -- If airbase is ship, translate parking coords. Alternatively, we just move the coordinate of the unit to the origin of the map, which is way more efficient. + if airbase and airbase:IsShip() then + coord.x=0 + coord.z=0 + maxdist=100 + end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot local dist=nil diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 082e4ae7c..8ef7b1815 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -33,6 +33,11 @@ -- @field #boolean isGround If true, group is some ground unit. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. +-- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. +-- @field Wrapper.Airbase#AIRBASE destbase The destination base of the flight group. +-- @field Wrapper.Airbase#AIRBASE currbase The current airbase of the flight group, i.e. where it is currently located or landing at. +-- @field Core.Zone#ZONE homezone The home zone of the flight group. Set when spawn happens in air. +-- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #number currentwp Current waypoint index. This is the index of the last passed waypoint. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #table taskqueue Queue of tasks. @@ -99,6 +104,7 @@ -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargoqueue Table containing cargo groups to be transported. +-- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. @@ -158,6 +164,7 @@ OPSGROUP = { Nkills = 0, weaponData = {}, cargoqueue = {}, + cargoBay = {}, } @@ -396,11 +403,23 @@ OPSGROUP.CargoStatus={ DELIVERED="delivered", } +--- Cargo transport status. +-- @type OPSGROUP.TransportStatus +-- @field #string PLANNING Planning state. +-- @field #string SCHEDULED Transport is scheduled in the cargo queue. +-- @field #string EXECUTING Transport is being executed. +-- @field #string DELIVERED Transport was delivered. +OPSGROUP.TransportStatus={ + PLANNING="planning", + SCHEDULED="scheduled", + EXECUTING="executing", + DELIVERED="delivered", +} --- Cargo transport data. -- @type OPSGROUP.CargoTransport -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. --- @field #string status Status of the carrier. See @{#OPSGROUP.CarrierStatus}. +-- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in abs. seconds. @@ -408,6 +427,7 @@ OPSGROUP.CargoStatus={ -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field #OPSGROUP carrierGroup The new carrier group. --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -1420,6 +1440,27 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) end +--- Check if this is a FLIGHTGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is an airplane or helo group. +function OPSGROUP:IsFlightgroup() + return self.isFlightgroup +end + +--- Check if this is a ARMYGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is a ground group. +function OPSGROUP:IsArmygroup() + return self.isArmygroup +end + +--- Check if this is a NAVYGROUP. +-- @param #OPSGROUP self +-- @return #boolean If true, this is a ship group. +function OPSGROUP:IsNavygroup() + return self.isNavygroup +end + --- Check if group is exists. -- @param #OPSGROUP self @@ -2076,13 +2117,18 @@ function OPSGROUP:OnEventBirth(EventData) -- Set homebase if not already set. if self.isFlightgroup then + if EventData.Place then self.homebase=self.homebase or EventData.Place + self.currbase=EventData.Place + else + self.currbase=nil end if self.homebase and not self.destbase then self.destbase=self.homebase end + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) @@ -2929,6 +2975,28 @@ function OPSGROUP:CountRemainingMissison() return N end +--- Count remaining cargo transport assignments. +-- @param #OPSGROUP self +-- @return #number Number of unfinished transports in the queue. +function OPSGROUP:CountRemainingTransports() + + local N=0 + + -- Loop over mission queue. + for _,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + + -- Count not delivered (executing or scheduled) assignments. + if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then + + N=N+1 + + end + end + + return N +end + --- Get next mission. -- @param #OPSGROUP self -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. @@ -4485,6 +4553,31 @@ function OPSGROUP:_CheckCargoTransport() -- Abs. missin time in seconds. local Time=timer.getAbsTime() + -- Cargo queue info. + local text="Cargo bay:" + for cargogroupname, carriername in pairs(self.cargoBay) do + text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) + end + self:I(self.lid..text) + + -- Cargo queue info. + local text="Cargo queue:" + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for j,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local state=cargo.opsgroup:GetState() + local status=cargo.opsgroup.cargoStatus + local name=cargo.opsgroup.groupname + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" + local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + end + end + self:I(self.lid..text) + + if self.cargoTransport then -- TODO: Check if this group can actually transport any cargo. @@ -4505,7 +4598,7 @@ function OPSGROUP:_CheckCargoTransport() end if done then - self.cargoTransport.status="Delivered" + self.cargoTransport.status=OPSGROUP.TransportStatus.DELIVERED end end @@ -4539,7 +4632,8 @@ function OPSGROUP:_CheckCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status~="Delivered" and (cargotransport.importance==nil or cargotransport.importance<=vip) then + if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + cargotransport.status=OPSGROUP.TransportStatus.EXECUTING self.cargoTransport=cargotransport break end @@ -4560,7 +4654,9 @@ function OPSGROUP:_CheckCargoTransport() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus local weight=cargo.opsgroup:GetWeightTotal() - text=text..string.format("\n- %s (%.1f kg) [%s]: %s delivered=%s", name, weight, gstatus, cstatus, tostring(cargo.delivered)) + local carriername=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carriername, carriergroup, tostring(cargo.delivered)) end self:I(self.lid..text) end @@ -4577,13 +4673,15 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsPickingup() then - self:I(self.lid.."Picking up...") + -- Debug Info. + self:T(self.lid.."Picking up...") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. elseif self:IsLoading() then self:I(self.lid.."Loading...") + self.Tloading=self.Tloading or Time local boarding=false local gotcargo=false @@ -4608,14 +4706,15 @@ function OPSGROUP:_CheckCargoTransport() self:Loaded() end - -- No cargo and noone is boarding ==> check again if we can make anyone board. + -- No cargo and no one is boarding ==> check again if we can make anyone board. if not gotcargo and not boarding then self:Loading() end elseif self:IsTransporting() then - self:I(self.lid.."Transporting (nothing to do)") + -- Debug info. + self:T(self.lid.."Transporting (nothing to do)") elseif self:IsUnloading() then @@ -4641,6 +4740,8 @@ function OPSGROUP:_CheckCargoTransport() end end + + -- TODO: Remove delivered transports from cargo queue! return self end @@ -4655,16 +4756,39 @@ end -- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. +-- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) + -- Create a new cargo transport assignment. + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) + -- Set state to SCHEDULED. + cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + + --Add to cargo queue table.insert(self.cargoqueue, cargotransport) return cargotransport end +--- Delete a cargo transport assignment from the cargo queue +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport do be deleted. +-- @return #OPSGROUP self +function OPSGROUP:DelCargoTransport(CargoTransport) + + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + if transport.uid==CargoTransport.uid then + table.remove(self.cargoqueue, i) + return self + end + end + + return self +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. @@ -4675,8 +4799,9 @@ end -- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. +-- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4691,15 +4816,16 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Data structure. local transport={} --#OPSGROUP.CargoTransport + transport.uid=1 + transport.status=OPSGROUP.TransportStatus.PLANNING transport.pickupzone=Pickupzone transport.deployzone=Deployzone - transport.uid=1 - transport.status="Planning" transport.embarkzone=Embarkzone or Pickupzone transport.disembarkzone=Disembarkzone or Deployzone transport.prio=Prio or 50 transport.importance=Importance transport.Tstart=Tstart + transport.carrierGroup=NewCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -4793,7 +4919,27 @@ function OPSGROUP:GetWeightTotal(UnitName) end return weight -end +end + +--- Get free cargo bay weight. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Total weight in kg. +function OPSGROUP:GetFreeCargobay(UnitName) + + local Free=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then + local free=element.weightMaxCargo-element.weightCargo + Free=Free+free + end + end + + return Free +end + --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -4851,6 +4997,24 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end +--- Add weight to the internal cargo of an element of the group. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #OPSGROUP.Element Carrier able to transport the cargo. +function OPSGROUP:FindCarrierForCargo(CargoGroup) + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local free=self:GetFreeCargobay(element.name) + if free>=weight then + return element + end + end + + return nil +end + --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4866,8 +5030,27 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + + local airbasePickup=nil --Wrapper.Airbase#AIRBASE + if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + airbasePickup=Zone:GetAirbase() + end + + -- Check if group is already ready for loading. + local ready4loading=false + if self:IsArmygroup() or self:IsNavygroup() then + ready4loading=inzone + else + -- Aircraft is already parking at the pickup airbase. + ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + + -- If a helo is landed in the zone, we also are ready for loading. + if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then + ready4loading=true + end + end - if inzone then + if ready4loading then -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -4879,12 +5062,40 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Add waypoint. if self.isFlightgroup then - if self.isHelo then + + --- + -- Pickup at airbase + --- + + if airbasePickup then + + local airbaseCurrent=self.currbase + + if airbaseCurrent then + + -- Activate uncontrolled group. + if self:IsParking() then + self:StartUncontrolled() + end + + else + -- Order group to land at an airbase. + self:LandAtAirbase(airbasePickup) + end + + elseif self.isHelo or self.isVTOL then + + --- + -- Helo or VTOL can also land in a zone + --- + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + else - --TODO: airplane! pickup at airbase. check if already at airbase or make plane go there. + self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end elseif self.isNavygroup then local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) @@ -4910,6 +5121,9 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + -- Loading time stamp. + self.Tloading=timer.getAbsTime() + -- Create a temp array and monitor the free cargo space for each element. local cargobay={} for _,_element in pairs(self.elements) do @@ -4934,8 +5148,6 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - env.info("FF trying cargo!") - if cargo.opsgroup:IsNotCargo() and not cargo.delivered then -- Check if cargo is in pickup zone. @@ -4944,16 +5156,12 @@ function OPSGROUP:onafterLoading(From, Event, To) -- First check if cargo is not delivered yet. if inzone then - env.info("FF trying cargo 2!") - local weight=cargo.opsgroup:GetWeightTotal() local carrier=_findCarrier(weight) if carrier then - env.info("FF trying cargo3!") - -- Decrease free cargo bay. cargobay[carrier.name]=cargobay[carrier.name]-weight @@ -4961,7 +5169,6 @@ function OPSGROUP:onafterLoading(From, Event, To) cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED -- Order cargo group to board the carrier. - env.info("FF order group to board carrier") cargo.opsgroup:Board(self, carrier) else @@ -4974,8 +5181,14 @@ function OPSGROUP:onafterLoading(From, Event, To) env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - else - env.info("FF cargo already cargo or delivered") + else + + env.info("FF cargo already cargo or delivered") + + if not cargo.delivered then + + end + end end @@ -4988,21 +5201,33 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. -function OPSGROUP:onafterLoad(From, Event, To, CargoGroup) - env.info("FF load") - - local weight=CargoGroup:GetWeightTotal() +-- @param #OPSGROUP.Element Carrier The carrier element/unit. +function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) + -- Debug info. + self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) - local carrier=CargoGroup.carrier - + -- Carrier element. + local carrier=Carrier or CargoGroup.carrier --#OPSGROUP.Element + + -- No carrier provided. + if not carrier then + -- Try to find a carrier manually. + carrier=self:FindCarrierForCargo(CargoGroup) + end if carrier then + + -- Cargo weight. + local weight=CargoGroup:GetWeightTotal() -- Add weight to carrier. self:AddWeightCargo(carrier.name, weight) - -- Embark ==> Loaded - CargoGroup:Embark(carrier) + -- Fill cargo bay. + self.cargoBay[CargoGroup.groupname]=carrier.name + + -- Embark ==> Loaded. + CargoGroup:Embark(self, carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5041,10 +5266,31 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + + --TODO: This is all very similar to the onafterPickup() function. Could make it general. -- Check if already in deploy zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) + local airbaseDeploy=nil --Wrapper.Airbase#AIRBASE + if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + airbaseDeploy=Zone:GetAirbase() + end + + -- Check if group is already ready for loading. + local ready2deploy=false + if self:IsArmygroup() or self:IsNavygroup() then + ready2deploy=inzone + else + -- Aircraft is already parking at the pickup airbase. + ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() + + -- If a helo is landed in the zone, we also are ready for loading. + if ready2deploy==false and self.isHelo and self:IsLandedAt() and inzone then + ready2deploy=true + end + end + if inzone then -- We are already in the pickup zone ==> initiate loading. @@ -5052,27 +5298,59 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) else - -- Get a random coordinate in the pickup zone and let the carrier go there. + -- Get a random coordinate in the deploy zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate() - - -- Set waypoint. + + -- Add waypoint. if self.isFlightgroup then - -- FLIGHTGROUP - if self.isHelo then + + --- + -- Deploy at airbase + --- + + if airbaseDeploy then + + local airbaseCurrent=self.currbase + + if airbaseCurrent then + + -- Activate uncontrolled group. + if self:IsParking() then + self:StartUncontrolled() + end + + else + -- Order group to land at an airbase. + self:LandAtAirbase(airbaseDeploy) + end + + elseif self.isHelo or self.isVTOL then + + --- + -- Helo or VTOL can also land in a zone + --- + + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + else - -- TODO: airplane! let plane fly to airbase. + self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") end + elseif self.isArmygroup then + -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + elseif self.isNavygroup then + -- NAVYGROUP local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + waypoint.detour=true + end end @@ -5100,21 +5378,42 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) env.info("FF deploy cargo "..cargo.opsgroup:GetName()) + -- Deploy or disembark zone. local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE - --if zone:IsCoordinateInZone(self:GetCoordinate()) then + -- New carrier group. + local carrierGroup=self.cargoTransport.carrierGroup + + -- Cargo was delivered (somehow). + cargo.delivered=true + + if carrierGroup then + + local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) + + if carrier then + self:Unload(cargo.opsgroup) + carrierGroup:Load(cargo.opsgroup, carrier) + else + env.info("ERROR: No element of the group can take this cargo!") + end + + elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + + -- + env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + + else + -- Random coordinate in local Coordinate=zone:GetRandomCoordinate() local Heading=math.random(0,359) - - -- Cargo was delivered. - cargo.delivered=true - + -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) self:Unload(cargo.opsgroup, Coordinate, Heading) - --end + end end @@ -5122,6 +5421,19 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) end +--- On before "Unload" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. +-- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. +-- @param #number Heading Heading of group. +function OPSGROUP:onbeforeUnload(From, Event, To, OpsGroup, Coordinate, Heading) + --TODO: Add check if CargoGroup is cargo of this carrier. + return true +end + --- On after "Unload" event. Carrier unloads a cargo group from its cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -5132,9 +5444,33 @@ end -- @param #number Heading Heading of group. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) - --TODO: Add check if CargoGroup is cargo of this carrier. - if OpsGroup:IsInUtero() then + -- Not in cargo bay any more. + self.cargoBay[OpsGroup.groupname]=nil + + if Coordinate then + + --- + -- Respawn at a coordinate. + --- + OpsGroup:Unboard(Coordinate, Heading) + else + + --- + -- Just remove from this carrier. + --- + + -- Set cargo status. + OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- Reduce carrier weight. + local weight=OpsGroup:GetWeightTotal() + self:RedWeightCargo(OpsGroup.carrier.name, weight) + + -- No carrier. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil + end end @@ -5146,7 +5482,8 @@ end -- @param #string To To state. -- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterUnloaded(From, Event, To) - env.info("FF unloaded") + -- Debug info + self:I(self.lid.."Cargo unloaded..") -- Cancel landedAt task. if self.isFlightgroup and self:IsLandedAt() then @@ -5154,6 +5491,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:TaskCancel(Task) end + -- Check if there is still cargo to pickup. local pickup=false for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -5169,15 +5507,30 @@ function OPSGROUP:onafterUnloaded(From, Event, To) if pickup then - env.info("FF cargo left ==> pickup") + -- Pickup the next batch. + self:I(self.lid.."Still cargo left ==> pickup") self:Pickup(self.cargoTransport.pickupzone) else + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + -- No current transport assignment. self.cargoTransport=nil + + -- Startup uncontrolled aircraft to allow it to go back. + if self:IsFlightgroup() then + if self:IsUncontrolled() then + self:StartUncontrolled() + end + end - env.info("FF all delivered ==> check group done") + -- Check group done. + self:I(self.lid.."All cargo delivered ==> check group done") self:_CheckGroupDone(0.1) end @@ -5238,8 +5591,9 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.Element Carrier The OPSGROUP element -function OPSGROUP:onafterEmbark(From, Event, To, Carrier) +-- @param #OPSGROUP CarrierGroup The carrier OPSGROUP. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element carriing this group. +function OPSGROUP:onafterEmbark(From, Event, To, CarrierGroup, Carrier) -- Debug info. self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) @@ -5252,11 +5606,14 @@ function OPSGROUP:onafterEmbark(From, Event, To, Carrier) end self.waypoints={} - -- Despawn this group. - self:Despawn(0, true) - -- Set carrier (again). self.carrier=Carrier + self.carrierGroup=CarrierGroup + + -- Despawn this group. + if self:IsAlive() then + self:Despawn(0, true) + end end @@ -5267,10 +5624,21 @@ end -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. -- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. -function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) +-- @param #number Delay Delay in seconds, before the group is respawned. +function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) -- Debug info. self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + -- Set cargo status. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- Reduce carrier weight. + local weight=self:GetWeightTotal() + self.carrierGroup:RedWeightCargo(self.carrier.name, weight) + + -- No carrier. + self.carrier=nil + self.carrierGroup=nil -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -5301,18 +5669,9 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading) end end - - -- Reduce carrier weight. - local weight=self:GetWeightTotal() - self.carrierGroup:RedWeightCargo(self.carrier.name, weight) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - self.carrier=nil - self.carrierGroup=nil -- Respawn group. - self:_Respawn(0, Template) + self:_Respawn(Delay or 0, Template) end @@ -5522,7 +5881,9 @@ function OPSGROUP:_CheckGroupDone(delay) local speed=self:GetSpeedToWaypoint(i) -- Start route at first waypoint. - self:UpdateRoute(i, speed) + --self:UpdateRoute(i, speed) + + self:Cruise(speed) self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots", i, speed)) @@ -5556,7 +5917,8 @@ function OPSGROUP:_CheckGroupDone(delay) if #self.waypoints>0 then self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) - self:UpdateRoute() + --self:UpdateRoute() + self:Cruise() else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) self:__FullStop(-1) From c5a4776b3a82384e45a5e784e385fefe3099a936 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Feb 2021 01:24:13 +0100 Subject: [PATCH 097/382] OPS Cargo --- Moose Development/Moose/Core/Zone.lua | 58 ++-- Moose Development/Moose/Ops/ArmyGroup.lua | 2 + Moose Development/Moose/Ops/FlightGroup.lua | 29 +- Moose Development/Moose/Ops/NavyGroup.lua | 5 +- Moose Development/Moose/Ops/OpsGroup.lua | 323 +++++++++++++------- 5 files changed, 289 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 88d487622..9fd532dbd 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1057,24 +1057,48 @@ end --- Returns a random Vec2 location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! -- @return DCS#Vec2 The random location within the zone. -function ZONE_RADIUS:GetRandomVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) +function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) - local Point = {} local Vec2 = self:GetVec2() local _inner = inner or 0 local _outer = outer or self:GetRadius() + + if surfacetypes and type(surfacetypes)~="table" then + surfacetypes={surfacetypes} + end - local angle = math.random() * math.pi * 2; - Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); - Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); + local function _getpoint() + local point = {} + local angle = math.random() * math.pi * 2 + point.x = Vec2.x + math.cos(angle) * math.random(_inner, _outer) + point.y = Vec2.y + math.sin(angle) * math.random(_inner, _outer) + return point + end + + local function _checkSurface(point) + for _,sf in pairs(surfacetypes) do + if sf==land.getSurfaceType(point) then + return true + end + end + return false + end - self:T( { Point } ) + local point=_getpoint() - return Point + if surfacetypes then + local N=1 ; local Nmax=1000 + while _checkSurface(point)==false and N<=Nmax do + point=_getpoint() + N=N+1 + end + end + + return point end --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. @@ -1126,15 +1150,15 @@ end --- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. -- @param #ZONE_RADIUS self --- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. --- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. --- @return Core.Point#COORDINATE -function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) - self:F( self.ZoneName, inner, outer ) +-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0. +-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @return Core.Point#COORDINATE The random coordinate. +function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) + local vec2=self:GetRandomVec2(inner, outer, surfacetypes) - self:T3( { Coordinate = Coordinate } ) + local Coordinate = COORDINATE:NewFromVec2(vec2) return Coordinate end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2cf8ab17a..0e062ef69 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1176,6 +1176,8 @@ function ARMYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1c78a5144..f0743bb8c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -358,6 +358,14 @@ function FLIGHTGROUP:SetAirwing(airwing) return self end +--- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetVTOL() + self.isVTOL=true + return self +end + --- Get airwing the flight group belongs to. -- @param #FLIGHTGROUP self -- @return Ops.AirWing#AIRWING The AIRWING object. @@ -1674,13 +1682,17 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.isAI then if self:IsTransporting() then - env.info("FF transporting land at airbase ") - local airbase=self.cargoTransport.deployzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF transporting land at airbase ") + local airbase=self.cargoTransport.deployzone:GetAirbase() + self:LandAtAirbase(airbase) + end elseif self:IsPickingup() then - env.info("FF pickingup land at airbase ") - local airbase=self.cargoTransport.pickupzone:GetAirbase() - self:LandAtAirbase(airbase) + if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF pickingup land at airbase ") + local airbase=self.cargoTransport.pickupzone:GetAirbase() + self:LandAtAirbase(airbase) + end else self:_CheckGroupDone(1) end @@ -2919,12 +2931,14 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) text=text..string.format("AI = %s\n", tostring(self.isAI)) @@ -3295,6 +3309,7 @@ function FLIGHTGROUP:InitWaypoints() -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() self.destbase=self.destbase or self:GetDestinationFromWaypoints() + self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. if self.destbase then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 00b1c3de0..38e66f59c 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -677,7 +677,8 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set radio. if self.radioDefault then - self:SwitchRadio() + -- CAREFUL: This makes DCS crash for some ships like speed boats or Higgins boats! (On a respawn for example). Looks like the command SetFrequency is causing this. + --self:SwitchRadio() else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end @@ -1186,6 +1187,8 @@ function NAVYGROUP:_InitGroup() text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8ef7b1815..3f9bd6cab 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -19,13 +19,14 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group Group object. --- @field #table template Template of the group. +-- @field DCS#Template template Template table of the group. -- @field #boolean isLateActivated Is the group late activated. -- @field #boolean isUncontrolled Is the group uncontrolled. -- @field #boolean isFlightgroup Is a FLIGHTGROUP. -- @field #boolean isArmygroup Is an ARMYGROUP. -- @field #boolean isNavygroup Is a NAVYGROUP. -- @field #boolean isHelo If true, the is a helicopter group. +-- @field #boolean isVTOL If true, the is capable of Vertical TakeOff and Landing (VTOL). -- @field #table elements Table of elements, i.e. units of the group. -- @field #boolean isAI If true, group is purely AI. -- @field #boolean isAircraft If true, group is airplane or helicopter. @@ -108,6 +109,7 @@ -- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. +-- @field #number cargocounter Running number of cargo UIDs. -- -- @extends Core.Fsm#FSM @@ -165,6 +167,7 @@ OPSGROUP = { weaponData = {}, cargoqueue = {}, cargoBay = {}, + cargocounter = 1, } @@ -499,6 +502,7 @@ function OPSGROUP:New(group) self:SetLaser(1688, true, false, 0.5) self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + self.cargocounter=1 -- Init task counter. self.taskcurrent=0 @@ -582,7 +586,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. - self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its cargo. + self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. + self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. ------------------------ --- Pseudo Functions --- @@ -1283,7 +1288,7 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) -- Clear any task ==> makes DCS crash! --self.group:ClearTasks() - + -- Get all units. local units=self:GetDCSUnits() @@ -1292,11 +1297,12 @@ function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) if unit then local name=unit:getName() if name then + -- Despawn the unit. self:DespawnUnit(name, 0, NoEventRemoveUnit) end end end - + end end @@ -3002,6 +3008,11 @@ end -- @return Ops.Auftrag#AUFTRAG Next mission or *nil*. function OPSGROUP:_GetNextMission() + -- Check if group is acting as carrier or cargo at the moment. + if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsLoaded() then + return nil + end + -- Number of missions. local Nmissions=#self.missionqueue @@ -4376,7 +4387,7 @@ end --- Respawn the group. -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before respawn happens. Default 0. --- @param #table Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. +-- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. -- @param #boolean Reset Reset positions if TRUE. -- @return #OPSGROUP self function OPSGROUP:_Respawn(Delay, Template, Reset) @@ -4389,17 +4400,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) - - -- Get correct heading. - local function _Heading(course) - local h - if course<=180 then - h=math.rad(course) - else - h=-math.rad(360-course) - end - return h - end if self:IsAlive() then @@ -4467,6 +4467,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Spawn new group. _DATABASE:Spawn(Template) + -- Set activation and controlled state. + self.isLateActivated=Template.lateActivation + self.isUncontrolled=Template.uncontrolled + -- Reset events. --self:ResetEvents() @@ -4577,69 +4581,15 @@ function OPSGROUP:_CheckCargoTransport() end self:I(self.lid..text) - - if self.cargoTransport then - -- TODO: Check if this group can actually transport any cargo. - - local done=true - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - else - done=false --Someone is not done! - end - - --TODO: check if cargo is too heavy for this carrier group ==> elseif - - end + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + self:_CheckDelivered(transport) + end - if done then - self.cargoTransport.status=OPSGROUP.TransportStatus.DELIVERED - end - - end - -- Check if there is anything in the queue. - if not self.cargoTransport then - - -- Current position. - local coord=self:GetCoordinate() - - -- Sort results table wrt prio and distance to pickup zone. - local function _sort(a, b) - local transportA=a --#OPSGROUP.CargoTransport - local transportB=b --#OPSGROUP.CargoTransport - local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) - local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) - return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then - cargotransport.status=OPSGROUP.TransportStatus.EXECUTING - self.cargoTransport=cargotransport - break - end - - end - + if not self.cargoTransport then + self.cargoTransport=self:_GetNextCargoTransport() end -- Now handle the transport. @@ -4718,6 +4668,7 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsUnloading() then + -- Debug info. self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true @@ -4731,7 +4682,7 @@ function OPSGROUP:_CheckCargoTransport() end - -- Boarding finished ==> Transport cargo. + -- Unloading finished ==> pickup next batch or call it a day. if delivered then self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() @@ -4746,6 +4697,83 @@ function OPSGROUP:_CheckCargoTransport() return self end +--- Get cargo transport from cargo queue. +-- @param #OPSGROUP self +-- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +function OPSGROUP:_GetNextCargoTransport() + + -- Abs. mission time in seconds. + local Time=timer.getAbsTime() + + -- Current position. + local coord=self:GetCoordinate() + + -- Sort results table wrt prio and distance to pickup zone. + local function _sort(a, b) + local transportA=a --#OPSGROUP.CargoTransport + local transportB=b --#OPSGROUP.CargoTransport + local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) + local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) + return (transportA.prio=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + return cargotransport + end + + end + + return nil +end + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSGROUP self +-- @param #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @return #boolean If true, all cargo was delivered. +function OPSGROUP:_CheckDelivered(CargoTransport) + + -- TODO: Check if this group can actually transport any cargo. + + local done=true + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + --TODO: check if ALL remaining cargo is too heavy for this carrier group ==> el + + end + + if done then + self:Delivered(CargoTransport) + end + + -- Debug info. + self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + + return done +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. @@ -4763,11 +4791,15 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo -- Create a new cargo transport assignment. local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) - -- Set state to SCHEDULED. - cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + if cargotransport then - --Add to cargo queue - table.insert(self.cargoqueue, cargotransport) + -- Set state to SCHEDULED. + cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, cargotransport) + + end return cargotransport end @@ -4816,7 +4848,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Data structure. local transport={} --#OPSGROUP.CargoTransport - transport.uid=1 + transport.uid=self.cargocounter transport.status=OPSGROUP.TransportStatus.PLANNING transport.pickupzone=Pickupzone transport.deployzone=Deployzone @@ -4830,15 +4862,27 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + -- We got a single GROUP or OPSGROUP objectg. local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - table.insert(transport.cargos, cargo) - else - -- We got a SET_GROUP object. - for _,group in pairs(GroupSet.Set) do - local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then table.insert(transport.cargos, cargo) end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo and self:CanCargo(cargo.opsgroup) then + table.insert(transport.cargos, cargo) + end + + end end -- Debug info. @@ -4856,7 +4900,13 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I self:I(self.lid..text) end - return transport + if #transport.cargos>0 then + self.cargocounter=self.cargocounter+1 + return transport + else + self:E(self.lid.."ERROR: Cargo too heavy for this carrier group!") + return nil + end end --- Create a cargo group data structure. @@ -4884,7 +4934,7 @@ function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) opsgroup=ARMYGROUP:New(group) end else - env.info("FF found opsgroup in createcargo") + --env.info("FF found opsgroup in createcargo") end end @@ -4997,6 +5047,25 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end +--- Check if the group can be carrier of a cargo group. +-- **Note** that the cargo group *cannot* be split into units, i.e. the largest cargo bay of any element of the group must be able to load the whole cargo group in one piece. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #boolean If `true`, there is an element of the group that can load the whole cargo group. +function OPSGROUP:CanCargo(CargoGroup) + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local can=element.weightMaxCargo>=weight + if can then + return true + end + end + + return false +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. @@ -5033,6 +5102,7 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) local airbasePickup=nil --Wrapper.Airbase#AIRBASE if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then + env.info("FF 001") airbasePickup=Zone:GetAirbase() end @@ -5041,16 +5111,21 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) if self:IsArmygroup() or self:IsNavygroup() then ready4loading=inzone else + env.info("FF 002 currbase="..(self.currbase and self.currbase:GetName() or "unknown")) + -- Aircraft is already parking at the pickup airbase. ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then + if ready4loading==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready4loading=true + env.info("FF 003") end end if ready4loading then + + env.info("FF 004") -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -5062,24 +5137,28 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Add waypoint. if self.isFlightgroup then - - --- - -- Pickup at airbase - --- if airbasePickup then + + --- + -- Pickup at airbase + --- local airbaseCurrent=self.currbase if airbaseCurrent then + + env.info("FF 100") -- Activate uncontrolled group. if self:IsParking() then + env.info("FF 200") self:StartUncontrolled() end else -- Order group to land at an airbase. + env.info("FF 300") self:LandAtAirbase(airbasePickup) end @@ -5286,7 +5365,7 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) ready2deploy=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready2deploy==false and self.isHelo and self:IsLandedAt() and inzone then + if ready2deploy==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then ready2deploy=true end end @@ -5454,6 +5533,7 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) --- OpsGroup:Unboard(Coordinate, Heading) + else --- @@ -5480,7 +5560,6 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. function OPSGROUP:onafterUnloaded(From, Event, To) -- Debug info self:I(self.lid.."Cargo unloaded..") @@ -5519,8 +5598,8 @@ function OPSGROUP:onafterUnloaded(From, Event, To) -- This is not a carrier anymore. self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- No current transport assignment. - self.cargoTransport=nil + -- Everything delivered. + self:Delivered(self.cargoTransport) -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then @@ -5537,6 +5616,27 @@ function OPSGROUP:onafterUnloaded(From, Event, To) end +--- On after "Delivered" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.CargoTransport CargoTransport +function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) + + -- Set cargo status. + CargoTransport.status=OPSGROUP.TransportStatus.DELIVERED + + -- Check if this was the current transport. + if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then + self.cargoTransport=nil + end + + -- Remove cargo transport from cargo queue. + self:DelCargoTransport(CargoTransport) + +end + --- -- Cargo Group Functions @@ -5569,7 +5669,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local Coordinate=Carrier.unit:GetCoordinate() if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) @@ -5642,6 +5742,9 @@ function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) -- Template for the respawned group. local Template=UTILS.DeepCopy(self.template) --DCS#Template + + -- No late activation. + Template.lateActivation=false -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -7869,11 +7972,25 @@ function OPSGROUP:_AddElementByName(unitname) -- Weight and cargo. element.weightEmpty=element.descriptors.massEmpty or 666 - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. - element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) element.weightCargo=0 - element.weight=element.weightEmpty+element.weightCargo + element.weight=element.weightEmpty+element.weightCargo + -- Looks like only aircraft have a massMax value in the descriptors. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. + + + if self.isArmygroup then + + element.weightMaxTotal=element.weightEmpty+10*95 --If max mass is not given, we assume 10 soldiers. + + elseif self.isNavygroup then + + element.weightMaxTotal=element.weightEmpty+10*1000 + + end + + -- Max cargo weight + element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) -- FLIGHTGROUP specific. if self.isFlightgroup then From 8c55541d0e01dd3164bbedafcfb89720e4a0c396 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 8 Feb 2021 16:27:58 +0100 Subject: [PATCH 098/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 57 ++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3f9bd6cab..76448b6be 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4310,9 +4310,12 @@ function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) -- Increase counter. self.Ndestroyed=self.Ndestroyed+1 + + -- Element is dead. + self:ElementDead(Element) -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) + --self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) end @@ -4323,7 +4326,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) + self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -4361,6 +4364,19 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + + -- Check cargo bay and declare cargo groups dead + for groupname, carriername in pairs(self.cargoBay or {}) do + if Element.name==carriername then + local opsgroup=_DATABASE:GetOpsGroup(groupname) + if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then + for _,element in pairs(opsgroup.elements) do + env.info("FF cargo element dead "..element.name) + opsgroup:ElementDead(element) + end + end + end + end end @@ -4584,7 +4600,10 @@ function OPSGROUP:_CheckCargoTransport() -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport - self:_CheckDelivered(transport) + local delivered=self:_CheckDelivered(transport) + if delivered then + self:Delivered(transport) + end end -- Check if there is anything in the queue. @@ -4742,12 +4761,10 @@ end --- Check if all cargo of this transport assignment was delivered. -- @param #OPSGROUP self --- @param #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @param #OPSGROUP.CargoTransport CargoTransport The next due cargo transport or `nil`. -- @return #boolean If true, all cargo was delivered. function OPSGROUP:_CheckDelivered(CargoTransport) - -- TODO: Check if this group can actually transport any cargo. - local done=true for _,_cargo in pairs(CargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4759,15 +4776,9 @@ function OPSGROUP:_CheckDelivered(CargoTransport) else done=false --Someone is not done! end - - --TODO: check if ALL remaining cargo is too heavy for this carrier group ==> el - + end - if done then - self:Delivered(CargoTransport) - end - -- Debug info. self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) @@ -5453,7 +5464,8 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. - if cargo.opsgroup:IsLoaded(self.groupname) then + -- TODO: Could be that the element carriing this cargo group is DEAD! + if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -5570,21 +5582,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:TaskCancel(Task) end - -- Check if there is still cargo to pickup. - local pickup=false - for _,_cargo in pairs(self.cargoTransport.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - -- Check for waiting or undelivered non cargo groups. - --if cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.WAITING or (cargo.opsgroup.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO and not cargo.delivered) then - if not cargo.delivered then - pickup=true - break - end - - end + -- Check everything was delivered (or is dead). + local delivered=self:_CheckDelivered(self.cargoTransport) - if pickup then + if not delivered then -- Pickup the next batch. self:I(self.lid.."Still cargo left ==> pickup") From 1daa2857b2283dfd52237d6896b628197417bc14 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 10 Feb 2021 13:19:53 +0100 Subject: [PATCH 099/382] Update Auftrag.lua Corrected docu in NewTanker, 1 is actually boom, and 0 probe --- Moose Development/Moose/Ops/Auftrag.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7a952cba8..f285e461d 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -688,7 +688,7 @@ end -- @param #number Speed Orbit speed in knots. Default 350 kts. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). -- @param #number Leg Length of race-track in NM. Default 10 NM. --- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type. +-- @param #number RefuelSystem Refueling system (1=boom, 0=probe). This info is *only* for AIRWINGs so they launch the right tanker type. -- @return #AUFTRAG self function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem) From 0656c33e0595a6ca18f8e32788979b93982a1749 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 10 Feb 2021 23:50:37 +0100 Subject: [PATCH 100/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 358 ++++++++++++----------- 1 file changed, 181 insertions(+), 177 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 76448b6be..68212dd62 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -431,6 +431,7 @@ OPSGROUP.TransportStatus={ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field #OPSGROUP carrierGroup The new carrier group. +-- @field #OPSGROUP carrierGroupFrom The carrier group --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -575,8 +576,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. - self:AddTransition("*", "Board", "*") -- Group is boarding a cargo carrier. - self:AddTransition("*", "Embark", "*") -- Group was loaded into a cargo carrier. + self:AddTransition("*", "Board", "*") -- Group is ordered to board the carrier. + self:AddTransition("*", "Embarked", "*") -- Group was loaded into a cargo carrier. self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. @@ -1039,13 +1040,13 @@ function OPSGROUP:GetVec3(UnitName) return nil end ---- Get current coordinate of the group. +--- Get current coordinate of the group. If the current position cannot be determined, the last known position is returned. -- @param #OPSGROUP self -- @param #boolean NewObject Create a new coordiante object. -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=self:GetVec3() + local vec3=self:GetVec3() or self.position if vec3 then @@ -1062,7 +1063,7 @@ function OPSGROUP:GetCoordinate(NewObject) return self.coordinate end else - self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") + self:E(self.lid.."WARNING: Cannot get coordinate!") end return nil @@ -3009,7 +3010,7 @@ end function OPSGROUP:_GetNextMission() -- Check if group is acting as carrier or cargo at the moment. - if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsLoaded() then + if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsUnloading() or self:IsLoaded() then return nil end @@ -4796,11 +4797,12 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. +-- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) if cargotransport then @@ -4843,8 +4845,9 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. +-- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4869,6 +4872,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I transport.importance=Importance transport.Tstart=Tstart transport.carrierGroup=NewCarrierGroup + transport.carrierGroupFrom=FromCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -5188,11 +5192,15 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end elseif self.isNavygroup then + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + waypoint.detour=true + elseif self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + end end @@ -5238,47 +5246,47 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if cargo.opsgroup:IsNotCargo() and not cargo.delivered then + if not cargo.delivered then - -- Check if cargo is in pickup zone. - local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) + if cargo.opsgroup:IsNotCargo() then - -- First check if cargo is not delivered yet. - if inzone then - - local weight=cargo.opsgroup:GetWeightTotal() + -- Check if cargo is in pickup zone. + local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - local carrier=_findCarrier(weight) + -- First check if cargo is not delivered yet. + if inzone then - if carrier then - - -- Decrease free cargo bay. - cargobay[carrier.name]=cargobay[carrier.name]-weight - - -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED - - -- Order cargo group to board the carrier. - cargo.opsgroup:Board(self, carrier) + local weight=cargo.opsgroup:GetWeightTotal() + local carrier=_findCarrier(weight) + + if carrier then + + -- Decrease free cargo bay. + cargobay[carrier.name]=cargobay[carrier.name]-weight + + -- Set cargo status. + cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + + -- Order cargo group to board the carrier. + cargo.opsgroup:Board(self, carrier) + + else + + env.info("FF cannot board carrier") + + end + else - - env.info("FF cannot board carrier") - + env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - - else - env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) - end - else - - env.info("FF cargo already cargo or delivered") - - if not cargo.delivered then + elseif self.cargoTransport.carrierGroupFrom and cargo.opsgroup:IsLoaded(self.cargoTransport.carrierGroupFrom:GetName()) then end - + + else + env.info("FF cargo already delivered") end end @@ -5316,8 +5324,33 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Fill cargo bay. self.cargoBay[CargoGroup.groupname]=carrier.name - -- Embark ==> Loaded. - CargoGroup:Embark(self, carrier) + --- + -- Embark Cargo + --- + + -- Debug info. + CargoGroup:I(CargoGroup.lid..string.format("New cargo status %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) + + -- Set cargo status. + CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- Clear all waypoints. + for i=1,#CargoGroup.waypoints do + table.remove(CargoGroup.waypoints, i) + end + CargoGroup.waypoints={} + + -- Set carrier (again). + CargoGroup.carrier=carrier + CargoGroup.carrierGroup=self + + -- Despawn this group. + if CargoGroup:IsAlive() then + CargoGroup:Despawn(0, true) + end + + -- Trigger embarked event. + --CargoGroup:Embarked(self, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5464,7 +5497,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local cargo=_cargo --#OPSGROUP.CargoGroup -- Check that cargo is loaded into this group. - -- TODO: Could be that the element carriing this cargo group is DEAD! + -- NOTE: Could be that the element carriing this cargo group is DEAD, which would mean that the cargo group is also DEAD. if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) @@ -5483,6 +5516,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) if carrier then + -- Unload from this and directly load into the other carrier. self:Unload(cargo.opsgroup) carrierGroup:Load(cargo.opsgroup, carrier) else @@ -5496,13 +5530,20 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) else - -- Random coordinate in - local Coordinate=zone:GetRandomCoordinate() - local Heading=math.random(0,359) - - -- Unload. - env.info("FF unload cargo "..cargo.opsgroup:GetName()) - self:Unload(cargo.opsgroup, Coordinate, Heading) + + if not self.cargoTransport.inactiveUnload then + + -- Random coordinate/heading in the zone. + local Coordinate=zone:GetRandomCoordinate() + local Heading=math.random(0,359) + + -- Unload. + env.info("FF unload cargo "..cargo.opsgroup:GetName()) + self:Unload(cargo.opsgroup, Coordinate, Heading) + else + env.info("FF unload cargo Inactive "..cargo.opsgroup:GetName()) + self:Unload(cargo.opsgroup) + end end @@ -5533,35 +5574,78 @@ end -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. -function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading) +-- @param #boolean Activated If true, group is active. If false, group is spawned in late activated state. +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) -- Not in cargo bay any more. self.cargoBay[OpsGroup.groupname]=nil + + -- Reduce carrier weight. + local weight=OpsGroup:GetWeightTotal() + self:RedWeightCargo(OpsGroup.carrier.name, weight) + + + -- Debug info. + OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + + -- Set cargo status. + OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- No carrier. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil if Coordinate then --- -- Respawn at a coordinate. --- - - OpsGroup:Unboard(Coordinate, Heading) + + -- Template for the respawned group. + local Template=UTILS.DeepCopy(OpsGroup.template) --DCS#Template + -- No late activation. + Template.lateActivation=Activated + + -- Loop over template units. + for _,Unit in pairs(Template.units) do + + local element=OpsGroup:GetElementByName(Unit.name) + + if element then + + local vec3=element.vec3 + + -- Relative pos vector. + local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 + + local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 + + -- Position. + Unit.x=cvec2.x+rvec2.x + Unit.y=cvec2.y+rvec2.y + Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) + + -- Heading. + Unit.heading=Heading and math.rad(Heading) or Unit.heading + Unit.psi=-Unit.heading + + end + + end + + -- Respawn group. + OpsGroup:_Respawn(0, Template) + else --- -- Just remove from this carrier. --- - - -- Set cargo status. - OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - - -- Reduce carrier weight. - local weight=OpsGroup:GetWeightTotal() - self:RedWeightCargo(OpsGroup.carrier.name, weight) - -- No carrier. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil + -- Nothing to do. + + OpsGroup.position=self:GetVec3() end @@ -5593,26 +5677,10 @@ function OPSGROUP:onafterUnloaded(From, Event, To) else - -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) - - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- Everything delivered. + self:I(self.lid.."Still ALL unloaded ==> delivered") self:Delivered(self.cargoTransport) - - -- Startup uncontrolled aircraft to allow it to go back. - if self:IsFlightgroup() then - if self:IsUncontrolled() then - self:StartUncontrolled() - end - end - - -- Check group done. - self:I(self.lid.."All cargo delivered ==> check group done") - self:_CheckGroupDone(0.1) - + end end @@ -5630,6 +5698,35 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then + + -- Debug info. + self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + + if self:IsPickingup() then + -- Delete pickup waypoint? + elseif self:IsLoading() then + -- Nothing to do? + elseif self:IsTransporting() then + -- This should not happen. Carrier is transporting, how can the cargo be delivered? + elseif self:IsUnloading() then + -- Nothing to do? + end + + -- Startup uncontrolled aircraft to allow it to go back. + if self:IsFlightgroup() then + if self:IsUncontrolled() then + self:StartUncontrolled() + end + end + + -- Check group done. + self:I(self.lid.."All cargo delivered ==> check group done") + self:_CheckGroupDone(0.1) + + -- No current transport any more. self.cargoTransport=nil end @@ -5662,7 +5759,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrierGroup=CarrierGroup -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) + local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) and self.carrierGroup:IsLateActivated()==false if board then @@ -5680,105 +5777,12 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) else -- Trigger Load event in 10 seconds. - self.carrierGroup:__Load(10, self) + self.carrierGroup:Load(self) end end - ---- On after "Embark" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #OPSGROUP CarrierGroup The carrier OPSGROUP. --- @param #OPSGROUP.Element Carrier The OPSGROUP element carriing this group. -function OPSGROUP:onafterEmbark(From, Event, To, CarrierGroup, Carrier) - -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.LOADED)) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.LOADED - - -- Clear all waypoints. - for i=1,#self.waypoints do - table.remove(self.waypoints, i) - end - self.waypoints={} - - -- Set carrier (again). - self.carrier=Carrier - self.carrierGroup=CarrierGroup - - -- Despawn this group. - if self:IsAlive() then - self:Despawn(0, true) - end - -end - ---- On after "Unboard" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. Can also be a DCS#Vec3 object. --- @param #number Heading Heading the group has in degrees. Default is last known heading of the group. --- @param #number Delay Delay in seconds, before the group is respawned. -function OPSGROUP:onafterUnboard(From, Event, To, Coordinate, Heading, Delay) - -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) - - -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - - -- Reduce carrier weight. - local weight=self:GetWeightTotal() - self.carrierGroup:RedWeightCargo(self.carrier.name, weight) - - -- No carrier. - self.carrier=nil - self.carrierGroup=nil - - -- Template for the respawned group. - local Template=UTILS.DeepCopy(self.template) --DCS#Template - - -- No late activation. - Template.lateActivation=false - - -- Loop over template units. - for _,Unit in pairs(Template.units) do - - local element=self:GetElementByName(Unit.name) - - if element then - - local vec3=element.vec3 - - -- Relative pos vector. - local rvec2={x=Unit.x-Template.x, y=Unit.y-Template.y} --DCS#Vec2 - - local cvec2={x=Coordinate.x, y=Coordinate.z} --DCS#Vec2 - - -- Position. - Unit.x=cvec2.x+rvec2.x - Unit.y=cvec2.y+rvec2.y - Unit.alt=land.getHeight({x=Unit.x, y=Unit.y}) - - -- Heading. - Unit.heading=Heading and math.rad(Heading) or Unit.heading - Unit.psi=-Unit.heading - - end - - end - - -- Respawn group. - self:_Respawn(Delay or 0, Template) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 96b0393f91658b2ff7deaf4ee8c0a9d50399ede0 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 11 Feb 2021 23:58:56 +0100 Subject: [PATCH 101/382] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 8 +- Moose Development/Moose/Ops/OpsGroup.lua | 263 ++++++++++++-------- 2 files changed, 169 insertions(+), 102 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f0743bb8c..64cdfea98 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1738,11 +1738,11 @@ end function FLIGHTGROUP:onafterLandedAt(From, Event, To) self:T(self.lid..string.format("Flight landed at")) - + -- Trigger (un-)loading process. if self:IsPickingup() then - self:Loading() + self:__Loading(-1) elseif self:IsTransporting() then - self:Deploy() + self:__Unloading(-1) end end @@ -1836,7 +1836,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) if self:IsPickingup() then self:__Loading(-1) elseif self:IsTransporting() then - self:__Deploy(-1) + self:__Unloading(-1) end else diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 68212dd62..f249f9a6d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -578,14 +578,14 @@ function OPSGROUP:New(group) self:AddTransition("*", "Board", "*") -- Group is ordered to board the carrier. self:AddTransition("*", "Embarked", "*") -- Group was loaded into a cargo carrier. - self:AddTransition("InUtero", "Unboard", "*") -- Group was unloaded from a cargo carrier. + self:AddTransition("*", "Disembarked", "*") -- Group was unloaded from a cargo carrier. self:AddTransition("*", "Pickup", "*") -- Carrier and is on route to pick up cargo. self:AddTransition("*", "Loading", "*") -- Carrier is loading cargo. self:AddTransition("*", "Load", "*") -- Carrier loads cargo into carrier. - self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned cargo into carrier. + self:AddTransition("*", "Loaded", "*") -- Carrier loaded all assigned/possible cargo into carrier. self:AddTransition("*", "Transport", "*") -- Carrier is transporting cargo. - self:AddTransition("*", "Deploy", "*") -- Carrier is dropping off cargo. + self:AddTransition("*", "Unloading", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. @@ -613,10 +613,6 @@ function OPSGROUP:New(group) -- TODO: Add pseudo functions. - - -- Add to data base. - --_DATABASE:AddOpsGroup(self) - return self end @@ -4366,7 +4362,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - -- Check cargo bay and declare cargo groups dead + -- Check cargo bay and declare cargo groups dead. for groupname, carriername in pairs(self.cargoBay or {}) do if Element.name==carriername then local opsgroup=_DATABASE:GetOpsGroup(groupname) @@ -4574,29 +4570,37 @@ function OPSGROUP:_CheckCargoTransport() -- Abs. missin time in seconds. local Time=timer.getAbsTime() - -- Cargo queue info. - local text="Cargo bay:" - for cargogroupname, carriername in pairs(self.cargoBay) do - text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) - end - self:I(self.lid..text) - - -- Cargo queue info. - local text="Cargo queue:" - for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport - text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) - for j,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local state=cargo.opsgroup:GetState() - local status=cargo.opsgroup.cargoStatus - local name=cargo.opsgroup.groupname - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" - local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" - text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + -- Cargo bay debug info. + if self.verbose>=0 then + local text="" + for cargogroupname, carriername in pairs(self.cargoBay) do + text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) + end + if text~="" then + self:I(self.lid.."Cargo bay:"..text) + end + end + + -- Cargo queue debug info. + if self.verbose>=0 then + local text="Cargo queue:" + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --#OPSGROUP.CargoTransport + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + for j,_cargo in pairs(transport.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local state=cargo.opsgroup:GetState() + local status=cargo.opsgroup.cargoStatus + local name=cargo.opsgroup.groupname + local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" + local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + end + end + if text~="" then + self:I(self.lid.."Cargo queue:"..text) end end - self:I(self.lid..text) -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do @@ -4609,7 +4613,8 @@ function OPSGROUP:_CheckCargoTransport() -- Check if there is anything in the queue. if not self.cargoTransport then - self.cargoTransport=self:_GetNextCargoTransport() + self.cargoTransport=self:_GetNextCargoTransport() + self.cargoTransport.status=OPSGROUP.TransportStatus.EXECUTING end -- Now handle the transport. @@ -4634,6 +4639,7 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then + -- Debug info. self:I(self.lid.."Not carrier ==> pickup") --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. @@ -4650,7 +4656,11 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsLoading() then + -- Debug info. self:I(self.lid.."Loading...") + + -- Set loading time stamp. + --TODO: Check max loading time. If exceeded ==> abort transport. self.Tloading=self.Tloading or Time local boarding=false @@ -4706,6 +4716,8 @@ function OPSGROUP:_CheckCargoTransport() if delivered then self:I(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() + else + self:Unloading() end end @@ -4717,6 +4729,45 @@ function OPSGROUP:_CheckCargoTransport() return self end +--- Add OPSGROUP to cargo bay of a carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @param #OPSGROUP.Element CarrierElement The element of the carrier. +function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) + + -- Cargo weight. + local weight=CargoGroup:GetWeightTotal() + + -- Add weight to carrier. + self:AddWeightCargo(CarrierElement.name, weight) + + -- Fill cargo bay. + self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + return self +end + +--- Remove OPSGROUP from cargo bay of a carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @param #OPSGROUP.Element CarrierElement The element of the carrier. +function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) + + if self.cargoBay[CargoGroup.groupname] then + + -- Not in cargo bay any more. + self.cargoBay[CargoGroup.groupname]=nil + + -- Reduce carrier weight. + local weight=CargoGroup:GetWeightTotal() + self:RedWeightCargo(CargoGroup.carrier.name, weight) + + else + env.info("ERROR: Group is not in cargo bay. Cannot remove it!") + end + +end + --- Get cargo transport from cargo queue. -- @param #OPSGROUP self -- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. @@ -4781,7 +4832,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) end -- Debug info. - self:I(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) return done end @@ -4844,10 +4895,9 @@ end -- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. +-- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) +function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Current mission time. local Tnow=timer.getAbsTime() @@ -4871,8 +4921,7 @@ function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, I transport.prio=Prio or 50 transport.importance=Importance transport.Tstart=Tstart - transport.carrierGroup=NewCarrierGroup - transport.carrierGroupFrom=FromCarrierGroup + transport.carrierGroup=DisembarkCarrierGroup transport.cargos={} -- Check type of GroupSet provided. @@ -5104,20 +5153,21 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Pickup zone. -function OPSGROUP:onafterPickup(From, Event, To, Zone) +function OPSGROUP:onafterPickup(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + -- Pickup zone. + local Zone=self.cargoTransport.pickupzone + -- Check if already in the pickup zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) local airbasePickup=nil --Wrapper.Airbase#AIRBASE if Zone and Zone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF 001") airbasePickup=Zone:GetAirbase() end @@ -5126,22 +5176,18 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) if self:IsArmygroup() or self:IsNavygroup() then ready4loading=inzone else - env.info("FF 002 currbase="..(self.currbase and self.currbase:GetName() or "unknown")) -- Aircraft is already parking at the pickup airbase. ready4loading=self.currbase and self.currbase:GetName()==Zone:GetName() and self:IsParking() -- If a helo is landed in the zone, we also are ready for loading. - if ready4loading==false and (self.isHelo or self.isVTOL) and self:IsLandedAt() and inzone then + if ready4loading==false and self.isHelo and self:IsLandedAt() and inzone then ready4loading=true - env.info("FF 003") end end if ready4loading then - - env.info("FF 004") - + -- We are already in the pickup zone ==> initiate loading. self:Loading() @@ -5159,28 +5205,27 @@ function OPSGROUP:onafterPickup(From, Event, To, Zone) -- Pickup at airbase --- + -- Current airbase. local airbaseCurrent=self.currbase if airbaseCurrent then - - env.info("FF 100") -- Activate uncontrolled group. if self:IsParking() then - env.info("FF 200") self:StartUncontrolled() end else + -- Order group to land at an airbase. - env.info("FF 300") self:LandAtAirbase(airbasePickup) + end - elseif self.isHelo or self.isVTOL then + elseif self.isHelo then --- - -- Helo or VTOL can also land in a zone + -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. @@ -5315,14 +5360,8 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if carrier then - -- Cargo weight. - local weight=CargoGroup:GetWeightTotal() - - -- Add weight to carrier. - self:AddWeightCargo(carrier.name, weight) - - -- Fill cargo bay. - self.cargoBay[CargoGroup.groupname]=carrier.name + -- Add into carrier bay. + self:_AddCargobay(CargoGroup, carrier) --- -- Embark Cargo @@ -5350,7 +5389,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) end -- Trigger embarked event. - --CargoGroup:Embarked(self, Carrier) + CargoGroup:Embarked(self, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5382,8 +5421,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. -function OPSGROUP:onafterTransport(From, Event, To, Zone) +function OPSGROUP:onafterTransport(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) @@ -5391,7 +5429,10 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING --TODO: This is all very similar to the onafterPickup() function. Could make it general. - + + -- Deploy zone. + local Zone=self.cargoTransport.deployzone + -- Check if already in deploy zone. local inzone=Zone:IsCoordinateInZone(self:GetCoordinate()) @@ -5416,8 +5457,8 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) if inzone then - -- We are already in the pickup zone ==> initiate loading. - self:Deploy(Zone) + -- We are already in deploy zone ==> initiate unloading. + self:Unloading() else @@ -5480,18 +5521,20 @@ function OPSGROUP:onafterTransport(From, Event, To, Zone) end ---- On after "Deploy" event. +--- On after "Unloading" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Zone#ZONE Zone Deploy zone. -function OPSGROUP:onafterDeploy(From, Event, To, Zone) +function OPSGROUP:onafterUnloading(From, Event, To) -- Debug info. self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING + + -- Deploy zone. + local zone=self.cargoTransport.disembarkzone --Core.Zone#ZONE for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5501,10 +5544,7 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - -- Deploy or disembark zone. - local zone=Zone or self.cargoTransport.disembarkzone --Core.Zone#ZONE - + -- New carrier group. local carrierGroup=self.cargoTransport.carrierGroup @@ -5513,6 +5553,11 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) if carrierGroup then + --- + -- Delivered to another carrier group. + --- + + -- Get new carrier. local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) if carrier then @@ -5524,12 +5569,19 @@ function OPSGROUP:onafterDeploy(From, Event, To, Zone) end elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + + --- + -- Delivered to a ship via helo or VTOL + --- -- env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") else - + + --- + -- Delivered to deploy zone + --- if not self.cargoTransport.inactiveUnload then @@ -5574,16 +5626,11 @@ end -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. -- @param #number Heading Heading of group. --- @param #boolean Activated If true, group is active. If false, group is spawned in late activated state. +-- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) - - -- Not in cargo bay any more. - self.cargoBay[OpsGroup.groupname]=nil - - -- Reduce carrier weight. - local weight=OpsGroup:GetWeightTotal() - self:RedWeightCargo(OpsGroup.carrier.name, weight) + -- Remove group from carrier bay. + self:_DelCargobay(OpsGroup) -- Debug info. OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) @@ -5605,7 +5652,11 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, local Template=UTILS.DeepCopy(OpsGroup.template) --DCS#Template -- No late activation. - Template.lateActivation=Activated + if Activated==false then + Template.lateActivation=true + else + Template.lateActivation=false + end -- Loop over template units. for _,Unit in pairs(Template.units) do @@ -5758,27 +5809,43 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self.carrier=Carrier self.carrierGroup=CarrierGroup - -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsLateActivated()==false and (self.isArmygroup or self.isNavygroup) and self.carrierGroup:IsLateActivated()==false + -- Army or navy group. + local isArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() - if board then + -- Check that carrier is standing still. + if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then - -- TODO: Implement embarkzone. - local Coordinate=Carrier.unit:GetCoordinate() - - if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. + local board=(self.speedMax>0) and (not self:IsLateActivated()) and (self.isArmygroup or self.isNavygroup) and (not self.carrierGroup:IsLateActivated()) + + if board then + + -- Debug info. + self:I(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) + + -- TODO: Implement embarkzone. + local Coordinate=Carrier.unit:GetCoordinate() + + if self.isArmygroup then + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + else + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) + waypoint.detour=true + end + else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + + env.info("FF board with direct load") + + -- Trigger Load event. + self.carrierGroup:Load(self) + end else - - -- Trigger Load event in 10 seconds. - self.carrierGroup:Load(self) - + env.info("FF Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") + self:__Board(-10, CarrierGroup, Carrier) end end @@ -6543,7 +6610,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. opsgroup:FullStop() - opsgroup:Deploy() + opsgroup:Unloading() end elseif opsgroup:IsBoarding() then From 74e84a73ddd12aa02ad96392cd5aa0c92f74faad Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 13 Feb 2021 00:09:25 +0100 Subject: [PATCH 102/382] OPS Cargo --- Moose Development/Moose/Ops/FlightGroup.lua | 12 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 65 +++++++++++++-------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 64cdfea98..ba95760fc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -187,16 +187,12 @@ FLIGHTGROUP.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: VTOL aircraft. --- TODO: Use new UnitLost event instead of crash/dead. --- TODO: Options EPLRS, Afterburner restrict etc. --- DONE: Add TACAN beacon. --- TODO: Damage? --- TODO: shot events? --- TODO: Marks to add waypoints/tasks on-the-fly. -- TODO: Mark assigned parking spot on F10 map. -- TODO: Let user request a parking spot via F10 marker :) --- TODO: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results? --- TODO: Out of AG/AA missiles. Safe state of out-of-ammo. +-- DONE: Use new UnitLost event instead of crash/dead. +-- DONE: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results? +-- DONE: Out of AG/AA missiles. Safe state of out-of-ammo. +-- DONE: Add TACAN beacon. -- DONE: Add tasks. -- DONE: Waypoints, read, add, insert, detour. -- DONE: Get ammo. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index f249f9a6d..fe5ea4e1e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -425,13 +425,12 @@ OPSGROUP.TransportStatus={ -- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. --- @field #number Tstart Start time in abs. seconds. +-- @field #number Tstart Start time in *abs.* seconds. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field #OPSGROUP carrierGroup The new carrier group. --- @field #OPSGROUP carrierGroupFrom The carrier group --- Cargo group data. -- @type OPSGROUP.CargoGroup @@ -453,6 +452,10 @@ OPSGROUP.version="0.7.1" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. +-- TODO: Options EPLRS, Afterburner restrict etc. +-- TODO: Damage? +-- TODO: Shot events? +-- TODO: Marks to add waypoints/tasks on-the-fly. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -4583,7 +4586,7 @@ function OPSGROUP:_CheckCargoTransport() -- Cargo queue debug info. if self.verbose>=0 then - local text="Cargo queue:" + local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#OPSGROUP.CargoTransport text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) @@ -4614,7 +4617,6 @@ function OPSGROUP:_CheckCargoTransport() -- Check if there is anything in the queue. if not self.cargoTransport then self.cargoTransport=self:_GetNextCargoTransport() - self.cargoTransport.status=OPSGROUP.TransportStatus.EXECUTING end -- Now handle the transport. @@ -4641,19 +4643,15 @@ function OPSGROUP:_CheckCargoTransport() -- Debug info. self:I(self.lid.."Not carrier ==> pickup") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. -- Initiate the cargo transport process. - self:Pickup(self.cargoTransport.pickupzone) + self:Pickup() elseif self:IsPickingup() then -- Debug Info. self:T(self.lid.."Picking up...") - - --TODO: Check if there is still cargo left. Maybe someone else already picked it up or it got destroyed. - + elseif self:IsLoading() then -- Debug info. @@ -4723,8 +4721,6 @@ function OPSGROUP:_CheckCargoTransport() end end - - -- TODO: Remove delivered transports from cargo queue! return self end @@ -4763,7 +4759,7 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) self:RedWeightCargo(CargoGroup.carrier.name, weight) else - env.info("ERROR: Group is not in cargo bay. Cannot remove it!") + env.error("ERROR: Group is not in cargo bay. Cannot remove it!") end end @@ -4803,6 +4799,7 @@ function OPSGROUP:_GetNextCargoTransport() local cargotransport=_cargotransport --#OPSGROUP.CargoTransport if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4847,13 +4844,12 @@ end -- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP NewCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @param #OPSGROUP FromCarrierGroup (Optional) The OPSGROUP where the cargo loaded from. +-- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. -- @return #OPSGROUP.CargoTransport Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) +function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, NewCarrierGroup, FromCarrierGroup) + local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) if cargotransport then @@ -5189,7 +5185,10 @@ function OPSGROUP:onafterPickup(From, Event, To) if ready4loading then -- We are already in the pickup zone ==> initiate loading. - self:Loading() + if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then + self:FullStop() + end + self:__Loading(-5) else @@ -5252,6 +5251,15 @@ function OPSGROUP:onafterPickup(From, Event, To) end +--- On before "Loading" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeLoading(From, Event, To) + +end + --- On after "Loading" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5325,9 +5333,7 @@ function OPSGROUP:onafterLoading(From, Event, To) else env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end - - elseif self.cargoTransport.carrierGroupFrom and cargo.opsgroup:IsLoaded(self.cargoTransport.carrierGroupFrom:GetName()) then - + end else @@ -5699,6 +5705,9 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, OpsGroup.position=self:GetVec3() end + + -- Trigger "Disembarked" event. + OpsGroup:Disembarked() end @@ -5816,7 +5825,12 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=(self.speedMax>0) and (not self:IsLateActivated()) and (self.isArmygroup or self.isNavygroup) and (not self.carrierGroup:IsLateActivated()) + local board=self.speedMax>0 and self:IsAlive() and isArmyOrNavy and self.carrierGroup:IsAlive() + + -- Armygroup cannot board ship ==> Load directly. + if self:IsArmygroup() and self.carrierGroup:IsNavygroup() then + board=false + end if board then @@ -6595,7 +6609,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. opsgroup:FullStop() - opsgroup:Loading() + opsgroup:__Loading(-5) end elseif opsgroup:IsTransporting() then @@ -8062,7 +8076,10 @@ function OPSGROUP:_AddElementByName(unitname) end -- Max cargo weight - element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + --element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) + + unit:SetCargoBayWeightLimit() + element.weightMaxCargo=unit.__.CargoBayWeightLimit -- FLIGHTGROUP specific. if self.isFlightgroup then From 084499fa0ebecff7018bcee3fbc2bd54bb4c9a8e Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Feb 2021 00:19:03 +0100 Subject: [PATCH 103/382] OPS Wait --- Moose Development/Moose/Ops/ArmyGroup.lua | 19 +++ Moose Development/Moose/Ops/FlightGroup.lua | 3 +- Moose Development/Moose/Ops/NavyGroup.lua | 19 +++ Moose Development/Moose/Ops/OpsGroup.lua | 136 ++++++++++++++++---- 4 files changed, 148 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 0e062ef69..70928e07e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -575,6 +575,21 @@ function ARMYGROUP:onafterSpawned(From, Event, To) end +--- On before "UpdateRoute" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number n Waypoint number. Default is next waypoint. +-- @param #number Speed Speed in knots. Default cruise speed. +-- @param #number Formation Formation of the group. +function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Formation) + if self:IsWaiting() then + return false + end + return true +end + --- On after "UpdateRoute" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -1028,6 +1043,10 @@ end -- @param #number Formation Formation. function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil + self:__UpdateRoute(-1, nil, Speed, Formation) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ba95760fc..cb03b0085 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -140,6 +140,7 @@ FLIGHTGROUP = { flaghold = nil, Tholding = nil, Tparking = nil, + Twaiting = nil, menu = nil, isHelo = nil, } @@ -251,8 +252,6 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. - self:AddTransition("*", "Wait", "*") -- Group is orbiting. - self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 38e66f59c..210e5d856 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -694,6 +694,21 @@ function NAVYGROUP:onafterSpawned(From, Event, To) end +--- On before "UpdateRoute" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number n Waypoint number. Default is next waypoint. +-- @param #number Speed Speed in knots to the next waypoint. +-- @param #number Depth Depth in meters to the next waypoint. +function NAVYGROUP:onbeforeUpdateRoute(From, Event, To, n, Speed, Depth) + if self:IsWaiting() then + return false + end + return true +end + --- On after "UpdateRoute" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -962,6 +977,10 @@ end -- @param #number Speed Speed in knots until next waypoint is reached. Default is speed set for waypoint. function NAVYGROUP:onafterCruise(From, Event, To, Speed) + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil + -- No set depth. self.depth=nil diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index fe5ea4e1e..8c10af305 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -41,6 +41,8 @@ -- @field Core.Zone#ZONE destzone The destination zone of the flight group. Set when final waypoint is in air. -- @field #number currentwp Current waypoint index. This is the index of the last passed waypoint. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. +-- @field #number Twaiting Abs. mission time stamp when the group was ordered to wait. +-- @field #number dTwait Time to wait in seconds. Default `nil` (for ever). -- @field #table taskqueue Queue of tasks. -- @field #number taskcounter Running number of task ids. -- @field #number taskcurrent ID of current task. If 0, there is no current task assigned. @@ -530,6 +532,8 @@ function OPSGROUP:New(group) self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. self:AddTransition("*", "PassingWaypoint", "*") -- Passing waypoint. + + self:AddTransition("*", "Wait", "*") -- Group will wait for further orders. self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle. self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set. @@ -1045,7 +1049,17 @@ end -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=self:GetVec3() or self.position + local vec3=nil --DCS#Vec3 + + -- TODO: get vec3 of carrier group. move this stuff to GetVec3 + if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD then + -- Get the carrier position. + vec3=self.carrier.unit:GetVec3() + else + self:GetVec3() + end + + vec3=vec3 or self.position if vec3 then @@ -3009,7 +3023,7 @@ end function OPSGROUP:_GetNextMission() -- Check if group is acting as carrier or cargo at the moment. - if self:IsTransporting() or self:IsPickingup() or self:IsLoading() or self:IsUnloading() or self:IsLoaded() then + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsUnloading() or self:IsLoaded() then return nil end @@ -3514,7 +3528,7 @@ function OPSGROUP:_QueueUpdate() -- Mission --- - -- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically. + -- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically. if self:IsExist() then local mission=self:_GetNextMission() @@ -3568,6 +3582,34 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "PassingWaypoint" event. +-- @param #OPSGROUP self +-- @param #boolean If true, group is currently waiting. +function OPSGROUP:IsWaiting() + if self.Twaiting then + return true + end + return false +end + +--- On after "Wait" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Duration Duration in seconds how long the group will be waiting. Default `nil` (for ever). +function OPSGROUP:onafterWait(From, Event, To, Duration) + + -- Order Group to hold. + self:FullStop() + + self.Twaiting=timer.getAbsTime() + + self.dTwait=Duration + +end + + --- On after "PassingWaypoint" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4574,7 +4616,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) @@ -4585,7 +4627,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Cargo queue debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#OPSGROUP.CargoTransport @@ -4623,7 +4665,7 @@ function OPSGROUP:_CheckCargoTransport() if self.cargoTransport then -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -5184,10 +5226,12 @@ function OPSGROUP:onafterPickup(From, Event, To) if ready4loading then - -- We are already in the pickup zone ==> initiate loading. + -- We are already in the pickup zone ==> wait and initiate loading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:FullStop() + self:Wait() end + + -- Start loading. self:__Loading(-5) else @@ -5239,12 +5283,16 @@ function OPSGROUP:onafterPickup(From, Event, To) local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + + self:__Cruise(-2) elseif self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:__Cruise(-2) + end end @@ -5294,14 +5342,15 @@ function OPSGROUP:onafterLoading(From, Event, To) end return nil end - for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup if not cargo.delivered then - - if cargo.opsgroup:IsNotCargo() then + + -- Check that group is not cargo already and not busy. + -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() + if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() or cargo.opsgroup:IsLoaded()) then -- Check if cargo is in pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) @@ -5344,6 +5393,16 @@ function OPSGROUP:onafterLoading(From, Event, To) end +--- Clear waypoints. +-- @param #OPSGROUP self +function OPSGROUP:ClearWaypoints() + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} +end + --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. -- @param #OPSGROUP self -- @param #string From From state. @@ -5380,10 +5439,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED -- Clear all waypoints. - for i=1,#CargoGroup.waypoints do - table.remove(CargoGroup.waypoints, i) - end - CargoGroup.waypoints={} + CargoGroup:ClearWaypoints() -- Set carrier (again). CargoGroup.carrier=carrier @@ -5463,9 +5519,14 @@ function OPSGROUP:onafterTransport(From, Event, To) if inzone then - -- We are already in deploy zone ==> initiate unloading. - self:Unloading() - + -- We are already in the pickup zone ==> wait and initiate unloading. + if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then + self:Wait() + end + + -- Start loading. + self:__UnLoading(-5) + else -- Get a random coordinate in the deploy zone and let the carrier go there. @@ -5515,11 +5576,17 @@ function OPSGROUP:onafterTransport(From, Event, To) local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + -- Give cruise command. + self:Cruise() + elseif self.isNavygroup then -- NAVYGROUP local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + + -- Give cruise command. + self:Cruise() end @@ -5591,8 +5658,10 @@ function OPSGROUP:onafterUnloading(From, Event, To) if not self.cargoTransport.inactiveUnload then + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + -- Random coordinate/heading in the zone. - local Coordinate=zone:GetRandomCoordinate() + local Coordinate=zoneCarrier:GetRandomCoordinate(20) local Heading=math.random(0,359) -- Unload. @@ -5839,18 +5908,24 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() + + -- Clear all waypoints. + self:ClearWaypoints() if self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:Cruise() else local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) waypoint.detour=true + self:Cruise() end else - env.info("FF board with direct load") + -- Debug info. + self:I(self.lid..string.format("FF board with direct load to carrier %s", self.carrierGroup:GetName())) -- Trigger Load event. self.carrierGroup:Load(self) @@ -6033,10 +6108,17 @@ function OPSGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, self._CheckGroupDone, self) else + -- Group is engaging something. if self:IsEngaging() then self:UpdateRoute() return end + + -- Group is waiting. We deny all updates. + if self:IsWaiting() then + -- If group is waiting, we assume that is the way it is meant to be. + return + end -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) @@ -6069,11 +6151,10 @@ function OPSGROUP:_CheckGroupDone(delay) -- Get positive speed to first waypoint. local speed=self:GetSpeedToWaypoint(i) - -- Start route at first waypoint. - --self:UpdateRoute(i, speed) - + -- Cruise. self:Cruise(speed) + -- Debug info. self:T(self.lid..string.format("Adinfinitum=TRUE ==> Goto WP index=%d at speed=%d knots", i, speed)) else @@ -6181,7 +6262,7 @@ function OPSGROUP:_CheckDamage() self.life=0 local damaged=false for _,_element in pairs(self.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Current life points. local life=element.unit:GetLife() @@ -6413,7 +6494,8 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) -- Switch to cruise mode. if self:IsHolding() then - self:Cruise() + -- Disable this for now. Cruise has to be commanded manually now. If group is ordered to hold, it will hold until told to move again. + --self:Cruise() end end @@ -6608,7 +6690,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. - opsgroup:FullStop() + opsgroup:Wait() opsgroup:__Loading(-5) end @@ -6623,7 +6705,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and loading. - opsgroup:FullStop() + opsgroup:Wait() opsgroup:Unloading() end From 5990ab1cc98c32ffddd331c7488632985991c895 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 15 Feb 2021 23:19:35 +0100 Subject: [PATCH 104/382] OPSTRANSPORT - New class. --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 112 +++++----- Moose Development/Moose/Ops/OpsTransport.lua | 209 +++++++++++++++++++ 4 files changed, 273 insertions(+), 51 deletions(-) create mode 100644 Moose Development/Moose/Ops/OpsTransport.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 95d211370..8a941f0db 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -83,6 +83,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 210e5d856..9f8f4ba9d 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -582,7 +582,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Recovery Windows --- - if self.verbose>=2 then + if self.verbose>=2 and #self.Qintowind>0 then -- Debug output: local text=string.format(self.lid.."Turn into wind time windows:") diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 8c10af305..d49774f96 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -375,7 +375,6 @@ OPSGROUP.TaskType={ --- Cargo Carrier status. -- @type OPSGROUP.CarrierStatus -- @field #string NOTCARRIER This group is not a carrier yet. --- @field #string EMPTY Carrier is empty and ready for cargo transport. -- @field #string PICKUP Carrier is on its way to pickup cargo. -- @field #string LOADING Carrier is loading cargo. -- @field #string LOADED Carrier has loaded cargo. @@ -383,7 +382,6 @@ OPSGROUP.TaskType={ -- @field #string UNLOADING Carrier is unloading cargo. OPSGROUP.CarrierStatus={ NOTCARRIER="not carrier", - EMPTY="empty", PICKUP="pickup", LOADING="loading", LOADED="loaded", @@ -394,18 +392,14 @@ OPSGROUP.CarrierStatus={ --- Cargo status. -- @type OPSGROUP.CargoStatus -- @field #string NOTCARGO This group is no cargo yet. --- @field #string WAITING Cargo is awaiting transporter. -- @field #string ASSIGNED Cargo is assigned to a carrier. -- @field #string BOARDING Cargo is boarding a carrier. -- @field #string LOADED Cargo is loaded into a carrier. --- @field #string DELIVERED Cargo was delivered at its destination. OPSGROUP.CargoStatus={ NOTCARGO="not cargo", - WAITING="waiting for carrier", ASSIGNED="assigned to carrier", BOARDING="boarding", LOADED="loaded", - DELIVERED="delivered", } --- Cargo transport status. @@ -1023,6 +1017,17 @@ end -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3(UnitName) + local vec3=nil --DCS#Vec3 + + -- First check if this group is loaded into a carrier + if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then + local unit=self.carrier.unit + if unit and unit:IsAlive()~=nil then + vec3=unit:GetVec3() + return vec3 + end + end + if self:IsExist() then local unit=nil --DCS#Unit @@ -1049,18 +1054,8 @@ end -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate(NewObject) - local vec3=nil --DCS#Vec3 + local vec3=self:GetVec3() or self.position --DCS#Vec3 - -- TODO: get vec3 of carrier group. move this stuff to GetVec3 - if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD then - -- Get the carrier position. - vec3=self.carrier.unit:GetVec3() - else - self:GetVec3() - end - - vec3=vec3 or self.position - if vec3 then self.coordinate=self.coordinate or COORDINATE:New(0,0,0) @@ -4646,16 +4641,7 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo queue:"..text) end end - - -- Loop over cargo queue and check if everything was delivered. - for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport - local delivered=self:_CheckDelivered(transport) - if delivered then - self:Delivered(transport) - end - end - + -- Check if there is anything in the queue. if not self.cargoTransport then self.cargoTransport=self:_GetNextCargoTransport() @@ -4696,12 +4682,12 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsLoading() then - -- Debug info. - self:I(self.lid.."Loading...") - -- Set loading time stamp. --TODO: Check max loading time. If exceeded ==> abort transport. - self.Tloading=self.Tloading or Time + self.Tloading=self.Tloading or Time + + -- Debug info. + self:I(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -4763,6 +4749,15 @@ function OPSGROUP:_CheckCargoTransport() end end + + -- Loop over cargo queue and check if everything was delivered. + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + local delivered=self:_CheckDelivered(transport) + if delivered then + self:Delivered(transport) + end + end return self end @@ -4773,6 +4768,8 @@ end -- @param #OPSGROUP.Element CarrierElement The element of the carrier. function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) + --TODO: Check group is not already in cargobay of this carrier or any other carrier. + -- Cargo weight. local weight=CargoGroup:GetWeightTotal() @@ -4789,6 +4786,7 @@ end -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. -- @param #OPSGROUP.Element CarrierElement The element of the carrier. +-- @return #boolean If `true`, cargo could be removed. function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) if self.cargoBay[CargoGroup.groupname] then @@ -4799,11 +4797,12 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) -- Reduce carrier weight. local weight=CargoGroup:GetWeightTotal() self:RedWeightCargo(CargoGroup.carrier.name, weight) - - else - env.error("ERROR: Group is not in cargo bay. Cannot remove it!") + + return true end + env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") + return false end --- Get cargo transport from cargo queue. @@ -4840,7 +4839,7 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) then + if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4887,11 +4886,11 @@ end -- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. -- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. -- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return #OPSGROUP.CargoTransport Cargo transport. +-- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) -- Create a new cargo transport assignment. - local cargotransport=self:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) + local cargotransport=OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) if cargotransport then @@ -4906,6 +4905,19 @@ function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Impo return cargotransport end +--- Create a cargo transport assignment. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. +function OPSGROUP:AddOpsTransport(OpsTransport) + + -- Set state to SCHEDULED. + OpsTransport.status=OPSTRANSPORT.Status.SCHEDULED + + --Add to cargo queue + table.insert(self.cargoqueue, OpsTransport) + +end + --- Delete a cargo transport assignment from the cargo queue -- @param #OPSGROUP self -- @param #OPSGROUP.CargoTransport CargoTransport Cargo transport do be deleted. @@ -5468,7 +5480,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) env.info("FF loaded") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5661,12 +5673,12 @@ function OPSGROUP:onafterUnloading(From, Event, To) local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) -- Random coordinate/heading in the zone. - local Coordinate=zoneCarrier:GetRandomCoordinate(20) + local Coordinate=zoneCarrier:GetRandomCoordinate(50) local Heading=math.random(0,359) -- Unload. env.info("FF unload cargo "..cargo.opsgroup:GetName()) - self:Unload(cargo.opsgroup, Coordinate, Heading) + self:Unload(cargo.opsgroup, Coordinate, nil, Heading) else env.info("FF unload cargo Inactive "..cargo.opsgroup:GetName()) self:Unload(cargo.opsgroup) @@ -5700,9 +5712,9 @@ end -- @param #string To To state. -- @param #OPSGROUP OpsGroup The OPSGROUP loaded as cargo. -- @param Core.Point#COORDINATE Coordinate Coordinate were the group is unloaded to. --- @param #number Heading Heading of group. -- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. -function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, Activated) +-- @param #number Heading (Optional) Heading of group in degrees. Default is random heading for each unit. +function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) -- Remove group from carrier bay. self:_DelCargobay(OpsGroup) @@ -5713,10 +5725,6 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - -- No carrier. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil - if Coordinate then --- @@ -5776,7 +5784,11 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Heading, end -- Trigger "Disembarked" event. - OpsGroup:Disembarked() + OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) + + -- No carrier any more. + OpsGroup.carrier=nil + OpsGroup.carrierGroup=nil end @@ -5790,7 +5802,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) self:I(self.lid.."Cargo unloaded..") -- Cancel landedAt task. - if self.isFlightgroup and self:IsLandedAt() then + if self:IsFlightgroup() and self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) end @@ -5801,13 +5813,13 @@ function OPSGROUP:onafterUnloaded(From, Event, To) if not delivered then -- Pickup the next batch. - self:I(self.lid.."Still cargo left ==> pickup") + self:I(self.lid.."Unloaded: Still cargo left ==> Pickup") self:Pickup(self.cargoTransport.pickupzone) else -- Everything delivered. - self:I(self.lid.."Still ALL unloaded ==> delivered") + self:I(self.lid.."Unloaded: ALL cargo unloaded ==> Delivered (current)") self:Delivered(self.cargoTransport) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua new file mode 100644 index 000000000..2c3f452c6 --- /dev/null +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -0,0 +1,209 @@ +--- **Ops** - Troop transport assignment of OPS groups. +-- +-- ## Main Features: +-- +-- * Patrol waypoints *ad infinitum* +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- == +-- +-- @module Ops.OpsTransport +-- @image OPS_OpsTransport.png + + +--- OPSTRANSPORT class. +-- @type OPSTRANSPORT +-- @field #string ClassName Name of the class. +-- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. +-- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #number importance Importance of this transport. Smaller=higher. +-- @field #number Tstart Start time in *abs.* seconds. +-- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. +-- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. +-- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. +-- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. +-- @extends Core.Fsm#FSM + +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +-- +-- === +-- +-- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) +-- +-- # The OPSTRANSPORT Concept +-- +-- This class enhances naval groups. +-- +-- @field #OPSTRANSPORT +OPSTRANSPORT = { + ClassName = "OPSTRANSPORT", + verbose = 1, + cargos = {}, +} + +--- Cargo transport status. +-- @type OPSTRANSPORT.Status +-- @field #string PLANNING Planning state. +-- @field #string SCHEDULED Transport is scheduled in the cargo queue. +-- @field #string EXECUTING Transport is being executed. +-- @field #string DELIVERED Transport was delivered. +OPSTRANSPORT.Status={ + PLANNING="planning", + SCHEDULED="scheduled", + EXECUTING="executing", + DELIVERED="delivered", +} + +_OPSTRANSPORTID=0 + +--- Army Group version. +-- @field #string version +OPSTRANSPORT.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. Essential input are the troops that should be transported and the zones where the troops are picked up and deployed. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! +-- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + + -- Set some string id for output to DCS.log file. + self.lid=string.format("OPSTRANSPORT %s --> %s | ", Pickupzone:GetName(), Deployzone:GetName()) + + _OPSTRANSPORTID=_OPSTRANSPORTID+1 + + self.uid=_OPSTRANSPORTID + self.status=OPSTRANSPORT.Status.PLANNING + self.pickupzone=Pickupzone + self.deployzone=Deployzone + self.embarkzone=Pickupzone + self.disembarkzone=Deployzone + self.prio=50 + self.importance=nil + self.Tstart=timer.getAbsTime() + self.carrierGroup=nil + self.cargos={} + + + -- Check type of GroupSet provided. + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + + -- We got a single GROUP or OPSGROUP objectg. + local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) + table.insert(self.cargos, cargo) + end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) then + table.insert(self.cargos, cargo) + end + + end + end + + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + self:I(self.lid..text) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new OPSTRANSPORT class object. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) + self.embarkzone=EmbarkZone or self.pickupzone + return self +end + + +--- Create a cargo group data structure. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP group The GROUP object. +-- @param Core.Zone#ZONE Pickupzone Pickup zone. +-- @param Core.Zone#ZONE Deployzone Deploy zone. +-- @return #OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + local opsgroup=nil + + if group:IsInstanceOf("OPSGROUP") then + opsgroup=group + else + + opsgroup=_DATABASE:GetOpsGroup(group) + + if not opsgroup then + if group:IsAir() then + opsgroup=FLIGHTGROUP:New(group) + elseif group:IsShip() then + opsgroup=NAVYGROUP:New(group) + else + opsgroup=ARMYGROUP:New(group) + end + else + --env.info("FF found opsgroup in createcargo") + end + + end + + local cargo={} --#OPSGROUP.CargoGroup + + cargo.opsgroup=opsgroup + cargo.delivered=false + cargo.status="Unknown" + cargo.pickupzone=Pickupzone + cargo.deployzone=Deployzone + + return cargo +end From dcf1a56756116aeb832fa760649d884e95ca2131 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 16 Feb 2021 00:15:22 +0100 Subject: [PATCH 105/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d49774f96..67b6f5ea1 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4839,7 +4839,7 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --#OPSGROUP.CargoTransport - if Time>=cargotransport.Tstart and cargotransport.status==OPSGROUP.TransportStatus.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + if Time>=cargotransport.Tstart and cargotransport.status~=OPSGROUP.TransportStatus.DELIVERED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport.status=OPSGROUP.TransportStatus.EXECUTING return cargotransport end @@ -4859,12 +4859,16 @@ function OPSGROUP:_CheckDelivered(CargoTransport) for _,_cargo in pairs(CargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if cargo.delivered then - -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then - -- This one is dead. - else - done=false --Someone is not done! + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + end end @@ -5168,13 +5172,17 @@ end -- @return #boolean If `true`, there is an element of the group that can load the whole cargo group. function OPSGROUP:CanCargo(CargoGroup) - local weight=CargoGroup:GetWeightTotal() - - for _,element in pairs(self.elements) do - local can=element.weightMaxCargo>=weight - if can then - return true + if CargoGroup then + + local weight=CargoGroup:GetWeightTotal() + + for _,element in pairs(self.elements) do + local can=element.weightMaxCargo>=weight + if can then + return true + end end + end return false @@ -5358,7 +5366,7 @@ function OPSGROUP:onafterLoading(From, Event, To) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if not cargo.delivered then + if self:CanCargo(cargo.opsgroup) and not cargo.delivered then -- Check that group is not cargo already and not busy. -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() From e8bfab515c87e673edca0c402f75c6107e324637 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:13:10 +0100 Subject: [PATCH 106/382] Integrate option for Shorad in Mantis Also, correct some documentation errors --- Moose Development/Moose/Functional/Mantis.lua | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a06b5ccc2..17e6b9524 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -24,14 +24,14 @@ ------------------------------------------------------------------------- --- **MANTIS** class, extends @{#Core.Base#BASE} --- @type MANTIS #MANTIS +-- @type MANTIS -- @field #string Classname -- @field #string name Name of this Mantis --- @field #string SAM_Templates_Prefix Prefix to build the #GROUP_SET for SAM sites --- @field @{#Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET --- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group --- @field @{#Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET --- @field @{#Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode +-- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites +-- @field @{#Core.Set#SET_GROUP} SAM_Group The SAM #SET_GROUP +-- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group +-- @field @{#Core.Set#SET_GROUP} EWR_Group The EWR #SET_GROUP +-- @field @{#Core.Set#SET_GROUP} Adv_EWR_Group The EWR #SET_GROUP used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object -- @field @{#Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites @@ -51,6 +51,9 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit +-- @field @{#Functional.Shorad#SHORAD} Shorad SHORAD Object, if available +-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled +-- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range -- @extends @{#Core.Base#BASE} @@ -163,7 +166,10 @@ MANTIS = { AWACS_Prefix = "", advAwacs = false, verbose = false, - awacsrange = 250000 + awacsrange = 250000, + Shorad = nil, + ShoradLink = false, + ShoradTime = 600, } ----------------------------------------------------------------------- @@ -230,6 +236,9 @@ do self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil self.awacsrange = 250000 --TODO: 250km, User Function to change + self.Shorad = nil + self.ShoradLink = false + self.ShoradTime = 600 if type(awacs) == "string" then self.advAwacs = true else @@ -709,6 +718,27 @@ do return self end + --- Function to link up #MANTIS with a #SHORAD installation + -- @param #MANTIS self + -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object + -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up + function MANTIS:AddShorad(Shorad,Shoradtime) + local Shorad = Shorad or nil + local ShoradTime = Shoradtime or 600 + local ShoradLink = true + if Shorad:IsInstanceOf("SHORAD") then + self.ShoradLink = ShoradLink + self.Shorad = Shorad --#SHORAD + self.ShoradTime = Shoradtime -- #number + end + end + + --- Function to unlink #MANTIS from a #SHORAD installation + -- @param #MANTIS self + function MANTIS:RemoveShorad() + self.ShoradLink = false + end + ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- @@ -743,8 +773,15 @@ do if samgroup:IsAlive() then -- switch off SAM samgroup:OptionAlarmStateRed() - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() + -- link in to SHORAD if available + -- TODO Test integration + if self.ShoradLink then + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(name, radius, ontime) + end + -- debug output local text = string.format("SAM %s switched to alarm state RED!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then env.info(self.lid..text) end From c0e58e9c929d6b623e471237a6df858574e8c930 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:14:53 +0100 Subject: [PATCH 107/382] Update Sead.lua Handle new shortened weapon names in DCS, make handling more graceful --- Moose Development/Moose/Functional/Sead.lua | 63 ++++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index f7e591897..2c5abb451 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,7 +17,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: Dec 2020 +-- Last Update: Feb 2021 -- -- === -- @@ -51,9 +51,44 @@ SEAD = { EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } + -- TODO Complete list? + --- Missile enumerators + -- @field Harms + SEAD.Harms = { + --[[ + ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired + ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired + ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired + ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired + ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired + ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired + ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired + ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired + ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired + ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired + ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired + ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired + ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired + ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired + --]] + ["AGM_88"] = "AGM_88", + ["AGM_45"] = "AGM_45", + ["AGM_122"] = "AGM_122", + ["AGM_84"] = "AGM_84", + ["AGM_45"] = "AGM_45", + ["ALARN"] = "ALARM", + ["LD-10"] = "LD-10", + ["X_58"] = "X_58", + ["X_28"] = "X_28", + ["X_25"] = "X_25", + ["X_31"] = "X_31", + ["Kh25"] = "Kh25", + } + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. +-- @param #SEAD self -- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. -- @return SEAD -- @usage @@ -74,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.2") + self:I("*** SEAD - Started Version 0.2.5") return self end @@ -112,7 +147,20 @@ function SEAD:SetEngagementRange(range) return self end ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. + --- Check if a known HARM was fired + -- @param #SEAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SEAD:_CheckHarms(WeaponName) + self:F( { WeaponName } ) + local hit = false + for _,_name in pairs (SEAD.Harms) do + if string.find(WeaponName,_name,1) then hit = true end + end + return hit + end + +--- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData @@ -127,7 +175,7 @@ function SEAD:OnEventShot( EventData ) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) self:T({ SEADWeapon }) - --check for SEAD missiles + --[[check for SEAD missiles if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired @@ -155,12 +203,13 @@ function SEAD:OnEventShot( EventData ) SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired - then - + --]] + if self:_CheckHarms(SEADWeaponName) then + local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) -- Unit name - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted grouo + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted group local _targetMimgroupName = _targetMimgroup:getName() -- group name local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill self:T( self.SEADGroupPrefixes ) From 154cc9fbebb6ef12eb352f6113d30cabcfe1a9aa Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:25:22 +0100 Subject: [PATCH 108/382] Shorad.lua - Initial release :) --- Moose Development/Moose/Functional/Shorad.lua | 457 ++++++++++++++++++ 1 file changed, 457 insertions(+) create mode 100644 Moose Development/Moose/Functional/Shorad.lua diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua new file mode 100644 index 000000000..098bf70c9 --- /dev/null +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -0,0 +1,457 @@ +--- **Functional** -- Short Range Air Defense System +-- +-- === +-- +-- **SHORAD** - Short Range Air Defense System +-- Controls a network of short range AAA groups. Uses events to detect a missile attack +-- +-- === +-- +-- ## Missions: +-- +-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/tbd) +-- +-- === +-- +-- ### Author : **applevangelist ** +-- +-- @module Functional.Shorad +-- @image Functional.Mantis.jpg +-- +-- Date: Feb 2021 + +------------------------------------------------------------------------- +--- **SHORAD** class, extends @{#Core.Base#BASE} +-- @type SHORAD +-- @field #string ClassName +-- @field #string name Name of this Shorad +-- @field #boolean debug Set the debug state +-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} +-- @field #number Radius Shorad defense radius in meters +-- @field @{#Core.Set#SET_GROUP} Groupset The set of Shorad groups +-- @field @{#Core.Set#SET_GROUP} Samset The set of SAM groups to defend +-- @field #string Coalition The coalition of this Shorad +-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds +-- @field #table ActiveGroups Table for the timer function +-- @field #string lid The log ID for the dcs.log +-- @field #boolean DefendHarms Default true, intercept incoming HARMS +-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles +-- @field #number DefenseLowProb Default 70, minimum detection limit +-- @field #number DefenseHighProb Default 90, maximim detection limit +-- @extends Core.Base#BASE + +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +-- +-- Simple Class for a more intelligent Short Range Air Defense System +-- +-- #SHORAD +-- Moose derived missile intercepting short range defense system +-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy +-- Integrate with @{Functional.Mantis#MANTIS} to complete the defensive system setup. +-- +-- ## Usage +-- +-- Set up a #SET_GROUP for the SAM sites to be protected: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` +-- +-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%. +-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. +-- +-- ### Start a new SHORAD system, parameters are: +-- +-- * Name: Name of this SHORAD. +-- * ShoradPrefix: Filter for the Shorad #SET_GROUP. +-- * Samset: The #SET_GROUP of SAM sites to defend. +-- * Radius: Defense radius in meters. +-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. +-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* +-- +-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` +-- +-- ## Customize options +-- +-- * SHORAD:SwitchDebug(debug) +-- * SHORAD:SwitchHARMDefense(onoff) +-- * SHORAD:SwitchAGMDefense(onoff) +-- * SHORAD:SetDefenseLimits(low,high) +-- * SHORAD:SetActiveTimer(seconds) +-- * SHORAD:SetDefenseRadius(meters) +-- * SHORAD:SetActiveTimer(seconds) +-- * SHORAD:SetDefenseRadius(meters) +-- +-- @field #SHORAD +SHORAD = { + ClassName = "SHORAD", + name = "MyShorad", + debug = false, + Prefixes = "", + Radius = 20000, + Groupset = nil, + Samset = nil, + Coalition = nil, + ActiveTimer = 600, --stay on 10 mins + ActiveGroups = {}, + lid = "", + DefendHarms = true, + DefendMavs = true, + DefenseLowProb = 70, + DefenseHighProb = 90, +} + +----------------------------------------------------------------------- +-- SHORAD System +----------------------------------------------------------------------- + +do + -- TODO Complete list? + --- Missile enumerators + -- @field Harms + SHORAD.Harms = { + --[[ + ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired + ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired + ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired + ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired + ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired + ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired + ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired + ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired + ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired + ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired + ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired + ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired + ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired + ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired + --]] + ["AGM_88"] = "AGM_88", + ["AGM_45"] = "AGM_45", + ["AGM_122"] = "AGM_122", + ["AGM_84"] = "AGM_84", + ["AGM_45"] = "AGM_45", + ["ALARN"] = "ALARM", + ["LD-10"] = "LD-10", + ["X_58"] = "X_58", + ["X_28"] = "X_28", + ["X_25"] = "X_25", + ["X_31"] = "X_31", + ["Kh25"] = "Kh25", + } + + --- TODO complete list? + -- @field Mavs + SHORAD.Mavs = { + ["AGM"] = "AGM", + ["C-701"] = "C-701", + ["Kh25"] = "Kh25", + ["Kh29"] = "Kh29", + ["Kh31"] = "Kh31", + ["Kh66"] = "Kh66", + } + + --- Instantiates a new SHORAD object + -- @param #SHORAD self + -- @param #string Name Name of this SHORAD + -- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP + -- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend + -- @param #number Radius Defense radius in meters, used to switch on groups + -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call + -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + local self = BASE:Inherit( self, BASE:New() ) + self:F({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + + local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() + + self.name = Name or "MyShorad" + self.Prefixes = ShoradPrefix or "SAM SHORAD" + self.Radius = Radius or 20000 + self.Coalition = Coalition or "blue" + self.Samset = Samset or GroupSet + self.ActiveTimer = ActiveTimer or 600 + self.ActiveGroups = {} + self.Groupset = GroupSet + self:HandleEvent( EVENTS.Shot ) + self.DefendHarms = true + self.DefendMavs = true + self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin + self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin + self:I("*** SHORAD - Started Version 0.0.1") + -- Set the string id for output to DCS.log file. + self.lid=string.format("SHORAD %s | ", self.name) + self:_InitState() + return self + end + + --- Initially set all groups to alarm state GREEN + -- @param #SHORAD self + function SHORAD:_InitState() + local table = {} + local set = self.Groupset + self:T({set = set}) + local aliveset = set:GetAliveSet() --#table + for _,_group in pairs (aliveset) do + _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP + end + -- gather entropy + for i=1,10 do + math.random() + end + end + + --- Switch debug state + -- @param #SHORAD self + -- @param #boolean debug Switch debug on (true) or off (false) + function SHORAD:SwitchDebug(debug) + self:F( { debug } ) + local onoff = debug or false + if debug then + self.debug = true + --tracing + BASE:TraceOn() + BASE:TraceClass("SHORAD") + else + self.debug = false + BASE:TraceOff() + end + end + + --- Switch defense for HARMs + -- @param #SHORAD self + -- @param #boolean onoff + function SHORAD:SwitchHARMDefense(onoff) + self:F( { onoff } ) + local onoff = onoff or true + self.DefendHarms = onoff + end + + --- Switch defense for AGMs + -- @param #SHORAD self + -- @param #boolean onoff + function SHORAD:SwitchAGMDefense(onoff) + self:F( { onoff } ) + local onoff = onoff or true + self.DefendMavs = onoff + end + + --- Set defense probability limits + -- @param #SHORAD self + -- @param #number low Minimum detection limit, integer 1-100 + -- @param #number high Maximum detection limit integer 1-100 + function SHORAD:SetDefenseLimits(low,high) + self:F( { low, high } ) + local low = low or 70 + local high = high or 90 + if (low < 0) or (low > 100) or (low > high) then + low = 70 + end + if (high < 0) or (high > 100) or (high < low ) then + high = 90 + end + self.DefenseLowProb = low + self.DefenseHighProb = high + end + + --- Set the number of seconds a SHORAD site will stay active + -- @param #SHORAD self + -- @param #number seconds Number of seconds systems stay active + function SHORAD:SetActiveTimer(seconds) + local timer = seconds or 600 + if timer < 0 then + timer = 600 + end + self.ActiveTimer = timer + end + + --- Set the number of meters for the SHORAD defense zone + -- @param #SHORAD self + -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active + function SHORAD:SetDefenseRadius(meters) + local radius = meters or 20000 + if radius < 0 then + radius = 20000 + end + self.Radius = radius + end + + --- Check if a HARM was fired + -- @param #SHORAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SHORAD:_CheckHarms(WeaponName) + self:F( { WeaponName } ) + local hit = false + if self.DefendHarms then + for _,_name in pairs (SHORAD.Harms) do + if string.find(WeaponName,_name,1) then hit = true end + end + end + return hit + end + + --- Check if a Maverick was fired + -- @param #SHORAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SHORAD:_CheckMavs(WeaponName) + self:F( { WeaponName } ) + local hit = false + if self.DefendMavs then + for _,_name in pairs (SHORAD.Mavs) do + if string.find(WeaponName,_name,1) then hit = true end + end + end + return hit + end + + --- Check the coalition of the attacker + -- @param #SHORAD self + -- @param #string Coalition name + -- @return #boolean Returns false for a match + function SHORAD:_CheckCoalition(Coalition) + local owncoalition = self.Coalition + local othercoalition = "" + if Coalition == 0 then + othercoalition = "neutral" + elseif Coalition == 1 then + othercoalition = "red" + else + othercoalition = "blue" + end + self:T({owncoalition = owncoalition, othercoalition = othercoalition}) + if owncoalition ~= othercoalition then + return true + else + return false + end + end + + --- Check if the missile is aimed at a SHORAD + -- @param #SHORAD self + -- @param #string TargetGroupName Name of the target group + -- @return #boolean Returns true for a match, else false + function SHORAD:_CheckShotAtShorad(TargetGroupName) + local tgtgrp = TargetGroupName + local shorad = self.Groupset + local shoradset = shorad:GetAliveSet() --#table + local returnname = false + for _,_groups in pairs (shoradset) do + local groupname = _groups:GetName() + if string.find(groupname, tgtgrp, 1) then + returnname = true + end + end + return returnname + end + + --- Check if the missile is aimed at a SAM site + -- @param #SHORAD self + -- @param #string TargetGroupName Name of the target group + -- @return #boolean Returns true for a match, else false + function SHORAD:_CheckShotAtSams(TargetGroupName) + local tgtgrp = TargetGroupName + local shorad = self.Samset + local shoradset = shorad:GetAliveSet() --#table + local returnname = false + for _,_groups in pairs (shoradset) do + local groupname = _groups:GetName() + if string.find(groupname, tgtgrp, 1) then + returnname = true + end + end + return returnname + end + + --- Calculate if the missile shot is detected + -- @param #SHORAD self + -- @return #boolean Returns true for a detection, else false + function SHORAD:_ShotIsDetected() + local IsDetected = false + local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value + local ActualDetection = math.random(1,100) -- value for this shot + if ActualDetection <= DetectionProb then + IsDetected = true + end + return IsDetected + end + + --- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds + -- @param #SHORAD self + -- @param #string TargetGroup Name of the target group used to build the #ZONE + -- @param #number Radius Radius of the #ZONE + -- @param #number ActiveTimer Number of seconds to stay active + function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) + self:F({TargetGroup, Radius, ActiveTimer}) + local targetgroup = GROUP:FindByName(TargetGroup) + local targetzone = ZONE_GROUP:New("Shorad",targetgroup,Radius) -- create a defense zone to check + local groupset = self.Groupset --Core.Set#SET_GROUP + local shoradset = groupset:GetAliveSet() --#table + -- local function to switch off shorad again + local function SleepShorad(group) + local groupname = group:GetName() + self.ActiveGroups[groupname] = nil + group:OptionAlarmStateGreen() + local text = string.format("Sleeping SHORAD %s", group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + end + -- go through set and find the one(s) to activate + for _,_group in pairs (shoradset) do + if _group:IsAnyInZone(targetzone) then + local text = string.format("Waking up SHORAD %s", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + _group:OptionAlarmStateRed() + local groupname = _group:GetName() + if self.ActiveGroups[groupname] == nil then -- no timer yet for this group + self.ActiveGroups[groupname] = { Timing = ActiveTimer } + local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit + timer.scheduleFunction(SleepShorad, _group, endtime) + end + end + end + end + + --- Main function - work on the EventData + -- @param #SHORAD self + function SHORAD:OnEventShot( EventData ) + self:F( { EventData } ) + + --local ShootingUnit = EventData.IniDCSUnit + --local ShootingUnitName = EventData.IniDCSUnitName + local ShootingWeapon = EventData.Weapon -- Identify the weapon fired + local ShootingWeaponName = EventData.WeaponName -- return weapon type + -- get detection probability + local IsDetected = self:_ShotIsDetected() + -- convert to text + local DetectedText = "false" + if IsDetected then + DetectedText = "true" + end + local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) + self:T( text ) + local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) + -- get firing coalition + local weaponcoalition = EventData.IniGroup:GetCoalition() + if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and self:_CheckCoalition(weaponcoalition) and IsDetected then + -- get target data + local targetdata = EventData.Weapon:getTarget() -- Identify target + local targetunitname = Unit.getName(targetdata) -- Unit name + local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group + local targetgroupname = targetgroup:getName() -- group name + -- check if we or a SAM site are the target + --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP + local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean + local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean + -- if being shot at, find closest SHORADs to activate + if shotatsams or shotatus then + self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + end + end + end +-- +end +----------------------------------------------------------------------- +-- SHORAD end +----------------------------------------------------------------------- From d4c8d5d91b7835c08eca384d5224b7aa0868261d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:26:25 +0100 Subject: [PATCH 109/382] Include Shorad.lua --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index c26eb8c33..62d483052 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -68,6 +68,7 @@ __Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Fox.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' ) +__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) From 688c57d91d1c703c2506f29df033fac3ff2714c6 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:29:13 +0100 Subject: [PATCH 110/382] Update Moose.files --- Moose Setup/Moose.files | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index b5a4d5031..3e43da98e 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -67,7 +67,8 @@ Functional/Artillery.lua Functional/Suppression.lua Functional/PseudoATC.lua Functional/Warehouse.lua -Functional/Fox.lua +Functional/Mantis.lua +Functional/Shorad.lua Ops/Airboss.lua Ops/RecoveryTanker.lua From 798c5b292a102c65a55185ba7e1a823ee03d3d7a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:30:46 +0100 Subject: [PATCH 111/382] Update Moose.files --- Moose Setup/Moose.files | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 3e43da98e..f64bb2ff3 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -67,6 +67,7 @@ Functional/Artillery.lua Functional/Suppression.lua Functional/PseudoATC.lua Functional/Warehouse.lua +Functional/Fox.lua Functional/Mantis.lua Functional/Shorad.lua From 2c92bb9d619fd294b096a36b52d0e65dec4076a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 17 Feb 2021 00:29:59 +0100 Subject: [PATCH 112/382] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 27 +-- Moose Development/Moose/Ops/OpsTransport.lua | 189 ++++++++++++++++++- 2 files changed, 196 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 67b6f5ea1..ee91b7867 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -438,7 +438,7 @@ OPSGROUP.TransportStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.1" +OPSGROUP.version="0.7.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -4625,8 +4625,8 @@ function OPSGROUP:_CheckCargoTransport() if self.verbose>=1 then local text="" for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport - text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport.status, transport.pickupzone:GetName(), transport.deployzone:GetName()) + local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT + text=text..string.format("\n[%d] UID=%d Status=%s: %s --> %s", i, transport.uid, transport:GetState(), transport.pickupzone:GetName(), transport.deployzone:GetName()) for j,_cargo in pairs(transport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup local state=cargo.opsgroup:GetState() @@ -4837,10 +4837,11 @@ function OPSGROUP:_GetNextCargoTransport() -- Find next transport assignment. for _,_cargotransport in pairs(self.cargoqueue) do - local cargotransport=_cargotransport --#OPSGROUP.CargoTransport + local cargotransport=_cargotransport --Ops.OpsTransport#OPSTRANSPORT - if Time>=cargotransport.Tstart and cargotransport.status~=OPSGROUP.TransportStatus.DELIVERED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then - cargotransport.status=OPSGROUP.TransportStatus.EXECUTING + if Time>=cargotransport.Tstart and cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + cargotransport:Executing() + cargotransport:SetCarrierTransportStatus(self, OPSTRANSPORT.Status.EXECUTING) return cargotransport end @@ -4851,7 +4852,7 @@ end --- Check if all cargo of this transport assignment was delivered. -- @param #OPSGROUP self --- @param #OPSGROUP.CargoTransport CargoTransport The next due cargo transport or `nil`. +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The next due cargo transport or `nil`. -- @return #boolean If true, all cargo was delivered. function OPSGROUP:_CheckDelivered(CargoTransport) @@ -4874,7 +4875,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) end -- Debug info. - self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport.status, tostring(done))) + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) return done end @@ -4914,8 +4915,9 @@ end -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. function OPSGROUP:AddOpsTransport(OpsTransport) - -- Set state to SCHEDULED. - OpsTransport.status=OPSTRANSPORT.Status.SCHEDULED + -- Add this group as carrier for the transport. + OpsTransport:_AddCarrier(self) + --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) @@ -5868,7 +5870,10 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) if self:IsFlightgroup() then if self:IsUncontrolled() then self:StartUncontrolled() - end + elseif self:IsLandedAt() then + local Task=self:GetTaskCurrent() + self:TaskCancel(Task) + end end -- Check group done. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 2c3f452c6..bc361e92a 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -23,7 +23,11 @@ --- OPSTRANSPORT class. -- @type OPSTRANSPORT -- @field #string ClassName Name of the class. +-- @field #string lid Log ID. +-- @field #number uid Unique ID of the transport. +-- @field #number verbose Verbosity level. -- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #table carriers Carriers assigned for this transport. -- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. @@ -50,6 +54,8 @@ OPSTRANSPORT = { ClassName = "OPSTRANSPORT", verbose = 1, cargos = {}, + carriers = {}, + carrierTransportStatus = {}, } --- Cargo transport status. @@ -59,17 +65,18 @@ OPSTRANSPORT = { -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. OPSTRANSPORT.Status={ - PLANNING="planning", + PLANNED="planned", SCHEDULED="scheduled", EXECUTING="executing", DELIVERED="delivered", } +--- Transport ID. _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.1" +OPSTRANSPORT.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -92,10 +99,10 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + _OPSTRANSPORTID=_OPSTRANSPORTID+1 + -- Set some string id for output to DCS.log file. - self.lid=string.format("OPSTRANSPORT %s --> %s | ", Pickupzone:GetName(), Deployzone:GetName()) - - _OPSTRANSPORTID=_OPSTRANSPORTID+1 + self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) self.uid=_OPSTRANSPORTID self.status=OPSTRANSPORT.Status.PLANNING @@ -107,14 +114,16 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.importance=nil self.Tstart=timer.getAbsTime() self.carrierGroup=nil - self.cargos={} + self.cargos={} + self.carriers={} + -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - -- We got a single GROUP or OPSGROUP objectg. - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + -- We got a single GROUP or OPSGROUP object. + local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) @@ -149,6 +158,22 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) self:I(self.lid..text) end + + + -- FMS start state is PLANNED. + self:SetStartState(OPSTRANSPORT.Status.PLANNED) + + -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED + self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. + self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. + self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. + self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") + + -- Call status update + self:__Status(-1) return self end @@ -157,7 +182,7 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new OPSTRANSPORT class object. +--- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. -- @return #OPSTRANSPORT self @@ -166,6 +191,42 @@ function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) return self end +--- Add a carrier assigned for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:_AddCarrier(CarrierGroup) + + self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + + self:Scheduled() + + table.insert(self.carriers, CarrierGroup) + + return self +end + +--- Add a carrier assigned for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @param #string Status Carrier Status. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetCarrierTransportStatus(CarrierGroup, Status) + + self.carrierTransportStatus[CarrierGroup.groupname]=Status + + return self +end + +--- Get carrier transport status. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #string Carrier status. +function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) + return self.carrierTransportStatus[CarrierGroup.groupname] +end + + --- Create a cargo group data structure. -- @param #OPSTRANSPORT self @@ -207,3 +268,113 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status Update +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Status" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterStatus(From, Event, To) + + local fsmstate=self:GetState() + + local text=string.format("State=%s", fsmstate) + + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + end + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier + end + + self:I(self.lid..text) + + -- Check if all cargo was delivered (or is dead). + self:_CheckDelivered() + + self:__Status(-30) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "Planned" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterPlanned(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.PLANNED)) +end + +--- On after "Scheduled" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterScheduled(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.SCHEDULED)) +end + +--- On after "Executing" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterExecuting(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.EXECUTING)) +end + +--- On after "Delivered" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterDelivered(From, Event, To) + self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) + + -- TODO: Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then + carrier:Delivered(self) + end + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSTRANSPORT self +function OPSTRANSPORT:_CheckDelivered() + + local done=true + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + else + done=false --Someone is not done! + end + + end + + if done then + self:Delivered() + end + +end From 9491b18ff40b7c64ac03a508e5f6164c4824e56a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:13:35 +0100 Subject: [PATCH 113/382] Update Shorad.lua Own picture and quote, cleanup docs, link missions, less overhead by avoiding checks on friendly fire. --- Moose Development/Moose/Functional/Shorad.lua | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 098bf70c9..7ed638f43 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -3,33 +3,33 @@ -- === -- -- **SHORAD** - Short Range Air Defense System --- Controls a network of short range AAA groups. Uses events to detect a missile attack +-- Controls a network of short range air/missile defense groups. -- -- === -- -- ## Missions: -- --- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/tbd) +-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense) -- -- === -- -- ### Author : **applevangelist ** -- -- @module Functional.Shorad --- @image Functional.Mantis.jpg +-- @image Functional.Shorad.jpg -- -- Date: Feb 2021 ------------------------------------------------------------------------- ---- **SHORAD** class, extends @{#Core.Base#BASE} +--- **SHORAD** class, extends Core.Base#BASE -- @type SHORAD -- @field #string ClassName -- @field #string name Name of this Shorad -- @field #boolean debug Set the debug state -- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} -- @field #number Radius Shorad defense radius in meters --- @field @{#Core.Set#SET_GROUP} Groupset The set of Shorad groups --- @field @{#Core.Set#SET_GROUP} Samset The set of SAM groups to defend +-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups +-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend -- @field #string Coalition The coalition of this Shorad -- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds -- @field #table ActiveGroups Table for the timer function @@ -40,14 +40,14 @@ -- @field #number DefenseHighProb Default 90, maximim detection limit -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) -- -- Simple Class for a more intelligent Short Range Air Defense System -- -- #SHORAD --- Moose derived missile intercepting short range defense system --- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy --- Integrate with @{Functional.Mantis#MANTIS} to complete the defensive system setup. +-- Moose derived missile intercepting short range defense system. +-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy. +-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup. -- -- ## Usage -- @@ -77,8 +77,6 @@ -- * SHORAD:SetDefenseLimits(low,high) -- * SHORAD:SetActiveTimer(seconds) -- * SHORAD:SetDefenseRadius(meters) --- * SHORAD:SetActiveTimer(seconds) --- * SHORAD:SetDefenseRadius(meters) -- -- @field #SHORAD SHORAD = { @@ -176,7 +174,7 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self:I("*** SHORAD - Started Version 0.0.1") + self:I("*** SHORAD - Started Version 0.0.2") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -289,7 +287,7 @@ do return hit end - --- Check if a Maverick was fired + --- Check if an AGM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match @@ -380,6 +378,7 @@ do -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active + -- @usage Use this function to integrate with other systems. function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) self:F({TargetGroup, Radius, ActiveTimer}) local targetgroup = GROUP:FindByName(TargetGroup) @@ -414,6 +413,7 @@ do --- Main function - work on the EventData -- @param #SHORAD self + -- @param Core.Event#EVENTDATA EventData The event details table data set function SHORAD:OnEventShot( EventData ) self:F( { EventData } ) @@ -421,32 +421,35 @@ do --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired local ShootingWeaponName = EventData.WeaponName -- return weapon type - -- get detection probability - local IsDetected = self:_ShotIsDetected() - -- convert to text - local DetectedText = "false" - if IsDetected then - DetectedText = "true" - end - local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) - self:T( text ) - local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) -- get firing coalition local weaponcoalition = EventData.IniGroup:GetCoalition() - if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and self:_CheckCoalition(weaponcoalition) and IsDetected then - -- get target data - local targetdata = EventData.Weapon:getTarget() -- Identify target - local targetunitname = Unit.getName(targetdata) -- Unit name - local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group - local targetgroupname = targetgroup:getName() -- group name - -- check if we or a SAM site are the target - --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP - local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean - local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean - -- if being shot at, find closest SHORADs to activate - if shotatsams or shotatus then - self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + -- get detection probability + if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire + local IsDetected = self:_ShotIsDetected() + -- convert to text + local DetectedText = "false" + if IsDetected then + DetectedText = "true" + end + local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) + self:T( text ) + local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) + -- + if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then + -- get target data + local targetdata = EventData.Weapon:getTarget() -- Identify target + local targetunitname = Unit.getName(targetdata) -- Unit name + local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group + local targetgroupname = targetgroup:getName() -- group name + -- check if we or a SAM site are the target + --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP + local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean + local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean + -- if being shot at, find closest SHORADs to activate + if shotatsams or shotatus then + self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + end end end end From 37c061bcbdbafca629526d2b61252a4e2d435a2a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 17 Feb 2021 09:14:56 +0100 Subject: [PATCH 114/382] Update Mantis.lua Nicefy doc output, Shorad link doc --- Moose Development/Moose/Functional/Mantis.lua | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 17e6b9524..bc043506b 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -23,21 +23,21 @@ -- Date: Jan 2021 ------------------------------------------------------------------------- ---- **MANTIS** class, extends @{#Core.Base#BASE} +--- **MANTIS** class, extends #Core.Base#BASE -- @type MANTIS -- @field #string Classname -- @field #string name Name of this Mantis -- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites --- @field @{#Core.Set#SET_GROUP} SAM_Group The SAM #SET_GROUP +-- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP -- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group --- @field @{#Core.Set#SET_GROUP} EWR_Group The EWR #SET_GROUP --- @field @{#Core.Set#SET_GROUP} Adv_EWR_Group The EWR #SET_GROUP used for advanced mode +-- @field Core.Set#SET_GROUP EWR_Group The EWR #SET_GROUP +-- @field #Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object --- @field @{#Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ +-- @field Wrapper.Group#GROUP HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging --- @field @{#Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR --- @field @{#Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS +-- @field Functional.Detection#DETECTION_AREAS Detection The #DETECTION_AREAS object for EWR +-- @field Functional.Detection#DETECTION_AREAS AWACS_Detection The #DETECTION_AREAS object for AWACS -- @field #boolean debug Switch on extra messages -- @field #boolean verbose Switch on extra logging -- @field #number checkradius Radius of the SAM sites @@ -51,10 +51,10 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit --- @field @{#Functional.Shorad#SHORAD} Shorad SHORAD Object, if available +-- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range --- @extends @{#Core.Base#BASE} +-- @extends Core.Base#BASE --- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat @@ -382,7 +382,7 @@ do --- Function to set the HQ object for further use -- @param #MANTIS self - -- @param Wrapper.GROUP#GROUP The HQ #GROUP object to be set as HQ + -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ function MANTIS:SetCommandCenter(group) local group = group or nil if group ~= nil then @@ -774,7 +774,7 @@ do -- switch off SAM samgroup:OptionAlarmStateRed() -- link in to SHORAD if available - -- TODO Test integration + -- TODO Test integration fully if self.ShoradLink then local Shorad = self.Shorad local radius = self.checkradius From fb66d74ab7ef491b589f6cd449a3e3802b425fbe Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 17 Feb 2021 10:26:13 +0100 Subject: [PATCH 115/382] ENUMS - ENUMS: Added Phonetic and ISOLang by A101Wayz - Fixed CALLSIGN.JTAC.Warrior (was CALLSIGN.JTAC.Warrier) --- Moose Development/Moose/Utilities/Enums.lua | 51 ++++++++++++++++++++- Moose Development/Moose/Utilities/Utils.lua | 2 +- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 84712dc7a..757b67ea1 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -310,4 +310,53 @@ ENUMS.Morse.N7="- - * * *" ENUMS.Morse.N8="- - - * *" ENUMS.Morse.N9="- - - - *" ENUMS.Morse.N0="- - - - -" -ENUMS.Morse[" "]=" " \ No newline at end of file +ENUMS.Morse[" "]=" " + +--- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). +-- +-- @type ENUMS.ISOLang +ENUMS.ISOLang = +{ + Arabic = 'AR', + Chinese = 'ZH', + English = 'EN', + French = 'FR', + German = 'DE', + Russian = 'RU', + Spanish = 'ES', + Japanese = 'JA', + Italian = 'IT', +} + +--- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet). +-- +-- @type ENUMS.Phonetic +ENUMS.Phonetic = +{ + A = 'Alpha', + B = 'Bravo', + C = 'Charlie', + D = 'Delta', + E = 'Echo', + F = 'Foxtrot', + G = 'Golf', + H = 'Hotel', + I = 'India', + J = 'Juliett', + K = 'Kilo', + L = 'Lima', + M = 'Mike', + N = 'November', + O = 'Oscar', + P = 'Papa', + Q = 'Quebec', + R = 'Romeo', + S = 'Sierra', + T = 'Tango', + U = 'Uniform', + V = 'Victor', + W = 'Whiskey', + X = 'Xray', + Y = 'Yankee', + Z = 'Zulu', +} \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ff42f0a47..fc2acdd9d 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -97,7 +97,7 @@ CALLSIGN={ JTAC={ Axeman=1, Darknight=2, - Warrier=3, + Warrior=3, Pointer=4, Eyeball=5, Moonbeam=6, From 6791e4d4ba6ca52bc43ce6fcb9d3187d53f3f75f Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 17 Feb 2021 13:11:18 +0100 Subject: [PATCH 116/382] Update Set.lua - Improved docs for `:FilterPrefixes()` --- Moose Development/Moose/Core/Set.lua | 66 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 87bcf9e4a..c8cb5dd05 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -880,8 +880,8 @@ do -- SET_GROUP -- -- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). -- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). - -- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). - -- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). + -- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the groups belonging to the country(ies). + -- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*. -- * @{#SET_GROUP.FilterActive}: Builds the SET_GROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! -- -- For the Category Filter, extra methods have been added: @@ -1241,10 +1241,10 @@ do -- SET_GROUP end - --- Builds a set of groups of defined GROUP prefixes. - -- All the groups starting with the given prefixes will be included within the set. + --- Builds a set of groups that contain the given string in their group name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self - -- @param #string Prefixes The prefix of which the group name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_GROUP self function SET_GROUP:FilterPrefixes( Prefixes ) if not self.Filter.GroupPrefixes then @@ -1843,7 +1843,7 @@ do -- SET_UNIT -- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). -- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). - -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). + -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units sharing the same string(s) in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_UNIT.FilterActive}: Builds the SET_UNIT with the units that are only active. Units that are inactive (late activation) won't be included in the set! -- -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: @@ -2105,10 +2105,10 @@ do -- SET_UNIT end - --- Builds a set of units of defined unit prefixes. - -- All the units starting with the given prefixes will be included within the set. + --- Builds a set of UNITs that contain a given string in their unit name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self - -- @param #string Prefixes The prefix of which the unit name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit name. Can also be passed as a `#table` of strings. -- @return #SET_UNIT self function SET_UNIT:FilterPrefixes( Prefixes ) if not self.Filter.UnitPrefixes then @@ -2963,7 +2963,7 @@ do -- SET_STATIC -- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies). -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s). -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). - -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s). + -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units containing the same string(s) in their name. **ATTENTION** bad naming convention as this *does not** only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: -- @@ -3176,10 +3176,10 @@ do -- SET_STATIC end - --- Builds a set of units of defined unit prefixes. - -- All the units starting with the given prefixes will be included within the set. + --- Builds a set of STATICs that contain the given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self - -- @param #string Prefixes The prefix of which the unit name starts with. + -- @param #string Prefixes The string pattern(s) that need to be contained in the static name. Can also be passed as a `#table` of strings. -- @return #SET_STATIC self function SET_STATIC:FilterPrefixes( Prefixes ) if not self.Filter.StaticPrefixes then @@ -3675,7 +3675,7 @@ do -- SET_CLIENT -- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). -- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). -- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). - -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). + -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients containing the same string(s) in their unit/pilot name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_CLIENT.FilterActive}: Builds the SET_CLIENT with the units that are only active. Units that are inactive (late activation) won't be included in the set! -- -- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: @@ -3858,10 +3858,10 @@ do -- SET_CLIENT end - --- Builds a set of clients of defined client prefixes. - -- All the clients starting with the given prefixes will be included within the set. + --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self - -- @param #string Prefixes The prefix of which the client name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings. -- @return #SET_CLIENT self function SET_CLIENT:FilterPrefixes( Prefixes ) if not self.Filter.ClientPrefixes then @@ -4114,7 +4114,7 @@ do -- SET_PLAYER -- * @{#SET_PLAYER.FilterCategories}: Builds the SET_PLAYER with the clients belonging to the category(ies). -- * @{#SET_PLAYER.FilterTypes}: Builds the SET_PLAYER with the clients belonging to the client type(s). -- * @{#SET_PLAYER.FilterCountries}: Builds the SET_PLAYER with the clients belonging to the country(ies). - -- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients starting with the same prefix string(s). + -- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients sharing the same string(s) in their unit/pilot name. **ATTENTION** Bad naming convention as this *does not* only filter prefixes. -- -- Once the filter criteria have been set for the SET_PLAYER, you can start filtering using: -- @@ -4293,10 +4293,10 @@ do -- SET_PLAYER end - --- Builds a set of clients of defined client prefixes. - -- All the clients starting with the given prefixes will be included within the set. + --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self - -- @param #string Prefixes The prefix of which the client name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings. -- @return #SET_PLAYER self function SET_PLAYER:FilterPrefixes( Prefixes ) if not self.Filter.ClientPrefixes then @@ -4874,7 +4874,7 @@ do -- SET_CARGO -- Filter criteria are defined by: -- -- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s). - -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s). + -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the same string(s). **ATTENTION** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s). -- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies). -- @@ -5036,10 +5036,10 @@ do -- SET_CARGO end - --- (R2.1) Builds a set of cargos of defined cargo prefixes. - -- All the cargos starting with the given prefixes will be included within the set. + --- Builds a set of CARGOs that contain a given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self - -- @param #string Prefixes The prefix of which the cargo name starts with. + -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 if not self.Filter.CargoPrefixes then @@ -5315,7 +5315,7 @@ do -- SET_ZONE -- You can set filter criteria to build the collection of zones in SET_ZONE. -- Filter criteria are defined by: -- - -- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix. + -- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_ZONE, you can start filtering using: -- @@ -5450,10 +5450,10 @@ do -- SET_ZONE - --- Builds a set of zones of defined zone prefixes. - -- All the zones starting with the given prefixes will be included within the set. + --- Builds a set of ZONEs that contain the given string in their name. + -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self - -- @param #string Prefixes The prefix of which the zone name starts with. + -- @param #string Prefixes The string pattern(s) that need to be contained in the zone name. Can also be passed as a `#table` of strings. -- @return #SET_ZONE self function SET_ZONE:FilterPrefixes( Prefixes ) if not self.Filter.Prefixes then @@ -5652,7 +5652,7 @@ do -- SET_ZONE_GOAL -- You can set filter criteria to build the collection of zones in SET_ZONE_GOAL. -- Filter criteria are defined by: -- - -- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern of prefix. + -- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_ZONE_GOAL, you can start filtering using: -- @@ -5768,10 +5768,10 @@ do -- SET_ZONE_GOAL - --- Builds a set of zones of defined zone prefixes. - -- All the zones starting with the given prefixes will be included within the set. + --- Builds a set of ZONE_GOALs that contain the given string in their name. + -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self - -- @param #string Prefixes The prefix of which the zone name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings. -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:FilterPrefixes( Prefixes ) if not self.Filter.Prefixes then From fccf58cd6c768daab40bd53e1128ce751fa081c8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:02:37 +0100 Subject: [PATCH 117/382] Update Mantis.lua added code examples --- Moose Development/Moose/Functional/Mantis.lua | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bc043506b..29b360e74 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -31,7 +31,7 @@ -- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP -- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group -- @field Core.Set#SET_GROUP EWR_Group The EWR #SET_GROUP --- @field #Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode +-- @field Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object -- @field Wrapper.Group#GROUP HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites @@ -135,8 +135,26 @@ -- # 4. Advanced Mode -- -- Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Awacs is counted as one EWR unit. It will set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. --- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- +-- E.g. `mymantis:SetAdvancedMode( true, 90 )` +-- -- Use this option if you want to make use of or allow advanced SEAD tactics. +-- +-- # 5. Integrate SHORAD +-- +-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs. When SHORAD detects a missile fired at one of MANTIS' SAM sites, it will activate SHORAD systems in +-- the given defense checkradius around that SAM site. Create a SHORAD object first, then integrate with MANTIS like so: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart()` +-- `myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue")` +-- `-- now set up MANTIS` +-- `mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` +-- `mymantis:AddShorad(myshorad,720)` +-- `mymantis:Start()` +-- +-- and (optionally) remove the link later on with +-- +-- `mymantis:RemoveShorad()` -- -- @field #MANTIS MANTIS = { From 95dc7ad2b43d90f83a612b474dcc383f5920f135 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:04:59 +0100 Subject: [PATCH 118/382] Update Shorad.lua code example added --- Moose Development/Moose/Functional/Shorad.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 7ed638f43..7ecf59d18 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -378,7 +378,14 @@ do -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active - -- @usage Use this function to integrate with other systems. + -- @usage Use this function to integrate with other systems, example + -- + -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() + -- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") + -- myshorad:SwitchDebug(true) + -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") + -- mymantis:AddShorad(myshorad,720) + -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) self:F({TargetGroup, Radius, ActiveTimer}) local targetgroup = GROUP:FindByName(TargetGroup) From c0401447dda855554847da18982bf55def39edab Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 18 Feb 2021 00:10:17 +0100 Subject: [PATCH 119/382] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 52 ++++--- Moose Development/Moose/Ops/OpsTransport.lua | 153 +++++++++++++------ 2 files changed, 132 insertions(+), 73 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ee91b7867..7dbeabb77 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -108,7 +108,7 @@ -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. --- @field #OPSGROUP.CargoTransport cargoTransport Current cargo transport assignment. +-- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. -- @field #number cargocounter Running number of cargo UIDs. @@ -2999,7 +2999,7 @@ function OPSGROUP:CountRemainingTransports() -- Loop over mission queue. for _,_transport in pairs(self.cargoqueue) do - local transport=_transport --#OPSGROUP.CargoTransport + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT -- Count not delivered (executing or scheduled) assignments. if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then @@ -4752,7 +4752,7 @@ function OPSGROUP:_CheckCargoTransport() -- Loop over cargo queue and check if everything was delivered. for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --#OPSGROUP.CargoTransport + local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT local delivered=self:_CheckDelivered(transport) if delivered then self:Delivered(transport) @@ -4807,7 +4807,7 @@ end --- Get cargo transport from cargo queue. -- @param #OPSGROUP self --- @return #OPSGROUP.CargoTransport The next due cargo transport or `nil`. +-- @return Ops.OpsTransport#OPSTRANSPORT The next due cargo transport or `nil`. function OPSGROUP:_GetNextCargoTransport() -- Abs. mission time in seconds. @@ -4818,8 +4818,8 @@ function OPSGROUP:_GetNextCargoTransport() -- Sort results table wrt prio and distance to pickup zone. local function _sort(a, b) - local transportA=a --#OPSGROUP.CargoTransport - local transportB=b --#OPSGROUP.CargoTransport + local transportA=a --Ops.OpsTransport#OPSTRANSPORT + local transportB=b --Ops.OpsTransport#OPSTRANSPORT local distA=transportA.pickupzone:GetCoordinate():Get2DDistance(coord) local distB=transportB.pickupzone:GetCoordinate():Get2DDistance(coord) return (transportA.prio %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) @@ -5841,7 +5845,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #OPSGROUP.CargoTransport CargoTransport +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Set cargo status. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index bc361e92a..56f28b878 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -2,13 +2,13 @@ -- -- ## Main Features: -- --- * Patrol waypoints *ad infinitum* +-- * Transport troops from A to B. -- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Transport). -- -- === -- @@ -32,22 +32,25 @@ -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. +-- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. +-- @field disembarkActivation Activation setting when group is disembared from carrier. +-- @field disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. -- @extends Core.Fsm#FSM ---- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +--- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill -- -- === -- --- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) +-- ![Banner Image](..\Presentations\OPS\Transport\_Main.png) -- -- # The OPSTRANSPORT Concept -- --- This class enhances naval groups. +-- Transport OPSGROUPS using carriers such as APCs, helicopters or airplanes. -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -99,11 +102,13 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT + -- Increase ID counter. _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) + -- Defaults. self.uid=_OPSTRANSPORTID self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone @@ -112,53 +117,15 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.disembarkzone=Deployzone self.prio=50 self.importance=nil - self.Tstart=timer.getAbsTime() + self.Tstart=timer.getAbsTime()+5 self.carrierGroup=nil self.cargos={} self.carriers={} - - - -- Check type of GroupSet provided. - if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - - -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - - if cargo then --and self:CanCargo(cargo.opsgroup) - table.insert(self.cargos, cargo) - end - - else - - -- We got a SET_GROUP object. - - for _,group in pairs(GroupSet.Set) do - - local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) - - if cargo then --and self:CanCargo(cargo.opsgroup) then - table.insert(self.cargos, cargo) - end - - end + if GroupSet then + self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) end - -- Debug info. - if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) - local Weight=0 - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() - Weight=Weight+weight - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) - end - text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) - self:I(self.lid..text) - end - -- FMS start state is PLANNED. self:SetStartState(OPSTRANSPORT.Status.PLANNED) @@ -182,6 +149,56 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Add cargo groups to be transported. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) + + -- Check type of GroupSet provided. + if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then + + -- We got a single GROUP or OPSGROUP object. + local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + + if cargo then --and self:CanCargo(cargo.opsgroup) + table.insert(self.cargos, cargo) + end + + else + + -- We got a SET_GROUP object. + + for _,group in pairs(GroupSet.Set) do + + local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + + if cargo then + table.insert(self.cargos, cargo) + end + + end + end + + -- Debug info. + if self.verbose>=0 then + local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", + self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + local Weight=0 + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --#OPSGROUP.CargoGroup + local weight=cargo.opsgroup:GetWeightTotal() + Weight=Weight+weight + text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) + end + text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #self.cargos, Weight) + self:I(self.lid..text) + end + + + return self +end + --- Set embark zone. -- @param #OPSTRANSPORT self -- @param Core.Zone#ZONE EmbarkZone Zone where the troops are embarked. @@ -191,6 +208,43 @@ function OPSTRANSPORT:SetEmbarkZone(EmbarkZone) return self end +--- Set disembark zone. +-- @param #OPSTRANSPORT self +-- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkZone(DisembarkZone) + self.disembarkzone=DisembarkZone or self.deployzone + return self +end + +--- Set activation status of group when disembarked from transport carrier. +-- @param #OPSTRANSPORT self +-- @param #boolean Active If `true` or `nil`, group is activated when disembarked. If `false`, group is late activated and needs to be activated manually. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkActivation(Active) + if Active==true or Active==nil then + self.disembarkActivation=true + else + self.disembarkActivation=false + end + return self +end + + +--- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. +-- @param #OPSTRANSPORT self +-- @param #boolean InUtero If `true` or `nil`, group remains *in utero* after disembarkment. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkInUtero(InUtero) + if InUtero==true or InUtero==nil then + self.disembarkInUtero=true + else + self.disembarkInUtero=false + end + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. @@ -240,7 +294,7 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) if group:IsInstanceOf("OPSGROUP") then opsgroup=group - else + elseif group:IsInstanceOf("GROUP") then opsgroup=_DATABASE:GetOpsGroup(group) @@ -252,10 +306,11 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) else opsgroup=ARMYGROUP:New(group) end - else - --env.info("FF found opsgroup in createcargo") end + else + self:E(self.lid.."ERROR: Cargo must be a GROUP or OPSGROUP object!") + return nil end local cargo={} --#OPSGROUP.CargoGroup From cdb491bb5f3945636088a7c8a8f6cccb85ebe764 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 18 Feb 2021 17:05:36 +0100 Subject: [PATCH 120/382] Update AI_Cargo_Helicopter.lua Added docu on Pseudo Function, make Home() function clear and Height useful --- .../Moose/AI/AI_Cargo_Helicopter.lua | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index a9bae01c3..90a39bdb4 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -33,7 +33,7 @@ -- -- ## Infantry health. -- --- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. +-- When infantry is unboarded from the helicopters, 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. @@ -67,18 +67,6 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Unloaded", "Pickup", "*" ) self:AddTransition( "Loaded", "Deploy", "*" ) - --[[ - self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) - self:AddTransition( "Boarding", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Loaded", "Loaded" ) - self:AddTransition( "Boarding", "PickedUp", "Loaded" ) - self:AddTransition( "Boarding", "Deploy", "Loaded" ) - self:AddTransition( "Loaded", "Unload", "Unboarding" ) - self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) - self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) - self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) - self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) - --]] self:AddTransition( "*", "Loaded", "Loaded" ) self:AddTransition( "Unboarding", "Pickup", "Unloaded" ) self:AddTransition( "Unloaded", "Unboard", "Unloaded" ) @@ -109,6 +97,24 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + --- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp + -- @param #AI_CARGO_HELICOPTER self + -- @param Wrapper.Group#GROUP Helicopter The helicopter #GROUP object + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object + + --- Unloaded Handler OnAfter for AI_CARGO_HELICOPTER - Cargo unloaded, carrier is empty + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterUnloaded + -- @param #AI_CARGO_HELICOPTER self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param #string Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. + -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object + --- Pickup Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Pickup -- @param #AI_CARGO_HELICOPTER self @@ -141,6 +147,13 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + --- Deployed Handler OnAfter for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed + -- @param #AI_CARGO_HELICOPTER self + -- @param #string From + -- @param #string Event + -- @param #string To + --- Deploy Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Deploy -- @param #AI_CARGO_HELICOPTER self @@ -154,6 +167,20 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @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. + --- Home Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] Home + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. + -- @param #number Speed (optional) Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Height (optional) Height the Helicopter should by flying at. + + --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER + -- @function [parent=#AI_CARGO_HELICOPTER] __Home + -- @param #number Delay Delay in seconds. + -- @param #AI_CARGO_HELICOPTER self + -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. + -- @param #number Speed (optional) Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Height (optional) Height the Helicopter should by flying at. -- We need to capture the Crash events for the helicopters. -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. @@ -235,7 +262,7 @@ end -- @param Event -- @param To function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) - + self:F({From, Event, To}) Helicopter:F( { Name = Helicopter:GetName() } ) if Helicopter and Helicopter:IsAlive() then @@ -276,7 +303,7 @@ end -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) - + self:F({From, Event, To, Coordinate, Speed, DeployZone}) local HelicopterInZone = false if Helicopter and Helicopter:IsAlive() == true then @@ -359,7 +386,8 @@ end -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate ) - + self:F({From, Event, To, Coordinate}) + if Helicopter and Helicopter:IsAlive() then local Route = {} @@ -394,7 +422,7 @@ end -- @param #boolean Deployed Cargo is deployed. -- @return #boolean True if all cargo has been unloaded. function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) - self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } ) + self:F( { From, Event, To, DeployZone = DeployZone } ) self:Orbit( Helicopter:GetCoordinate(), 50 ) @@ -488,7 +516,7 @@ end -- @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 Height Height in meters to move to the deploy coordinate. function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) - + self:F({From, Event, To, Coordinate, Speed, Height, DeployZone}) if Helicopter and Helicopter:IsAlive() ~= nil then self.RouteDeploy = true @@ -554,7 +582,8 @@ end -- @param #number Height Height in meters to move to 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_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) - + self:F({From, Event, To, Coordinate, Speed, Height}) + if Helicopter and Helicopter:IsAlive() ~= nil then self.RouteHome = true @@ -563,7 +592,8 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Calculate the target route point. - Coordinate.y = Height + --Coordinate.y = Height + Height = Height or 50 Speed = Speed or Helicopter:GetSpeedMax()*0.5 @@ -576,7 +606,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Create a route point of type air. local CoordinateTo = Coordinate local landheight = CoordinateTo:GetLandHeight() -- get target height - CoordinateTo.y = landheight + 50 -- flight height should be 50m above ground + CoordinateTo.y = landheight + Height -- flight height should be 50m above ground local WaypointTo = CoordinateTo:WaypointAir("RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, Speed, true) @@ -589,12 +619,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) Route[#Route].task = Helicopter:TaskCombo( Tasks ) - + Route[#Route+1] = WaypointTo -- Now route the helicopter Helicopter:Route(Route, 0) - end end From c50f6b72e7c62f8d802dbf9daddd7580135a4216 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 19 Feb 2021 00:11:55 +0100 Subject: [PATCH 121/382] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 5 + Moose Development/Moose/Ops/OpsGroup.lua | 96 +++++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 94 ++++++++++++++++--- 3 files changed, 149 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 70928e07e..f758ff079 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1082,10 +1082,15 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) + env.info("FF Current waypoint index="..self.currentwp) + env.info("FF Add waypoint after index="..(AfterWaypointWithID or "nil")) + local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) + + env.info("FF Add waypoint index="..wpnumber) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7dbeabb77..3f3dc427b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5303,15 +5303,17 @@ function OPSGROUP:onafterPickup(From, Event, To) end elseif self.isNavygroup then - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Navy Group + + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true self:__Cruise(-2) elseif self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- Army Group + + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true self:__Cruise(-2) @@ -5496,7 +5498,7 @@ function OPSGROUP:onafterLoaded(From, Event, To) end -- Order group to transport. - self:__Transport(1, self.cargoTransport.deployzone) + self:__Transport(1) end @@ -5551,8 +5553,16 @@ function OPSGROUP:onafterTransport(From, Event, To) else - -- Get a random coordinate in the deploy zone and let the carrier go there. - local Coordinate=Zone:GetRandomCoordinate() + -- Coord where the carrier goes to unload. + local Coordinate=nil --Core.Point#COORDINATE + + if self.cargoTransport.carrierGroup and self.cargoTransport.carrierGroup:IsLoading() then + -- Coordinate of the new carrier. + Coordinate=self.cargoTransport.carrierGroup:GetCoordinate() + else + -- Get a random coordinate in the deploy zone and let the carrier go there. + Coordinate=Zone:GetRandomCoordinate() + end -- Add waypoint. if self.isFlightgroup then @@ -5594,9 +5604,8 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then - -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + -- ARMYGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5604,8 +5613,7 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isNavygroup then -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5629,7 +5637,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING -- Deploy zone. - local zone=self.cargoTransport.disembarkzone --Core.Zone#ZONE + local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -5663,7 +5671,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) env.info("ERROR: No element of the group can take this cargo!") end - elseif zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then + elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then --- -- Delivered to a ship via helo or VTOL @@ -5684,20 +5692,37 @@ function OPSGROUP:onafterUnloading(From, Event, To) self:Unload(cargo.opsgroup) else + + local Coordinate=nil - -- TODO: honor disembark zone! + if self.cargoTransport.disembarkzone then + + -- Random coordinate in disembark zone. + Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + + else - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - -- Random coordinate/heading in the zone. - local Coordinate=zoneCarrier:GetRandomCoordinate(50) + -- TODO: Optimize with random Vec2 + + -- Create a zone around the carrier. + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + -- Random coordinate/heading in the zone. + Coordinate=zoneCarrier:GetRandomCoordinate(50) + + end + + -- Random heading of the group. local Heading=math.random(0,359) - -- Unload. + -- Unload to Coordinate. self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) end - + + -- Trigger "Unloaded" event for current cargo transport + self.cargoTransport:Unloaded(cargo.opsgroup) + end end @@ -5799,7 +5824,7 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Trigger "Disembarked" event. OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) - + -- No carrier any more. OpsGroup.carrier=nil OpsGroup.carrierGroup=nil @@ -5882,7 +5907,8 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check group done. self:I(self.lid.."All cargo delivered ==> check group done") - self:_CheckGroupDone(0.1) + self:__Cruise(0.1) + self:_CheckGroupDone(0.2) -- No current transport any more. self.cargoTransport=nil @@ -6718,7 +6744,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else - -- Stop and loading. + -- Wait and load cargo. opsgroup:Wait() opsgroup:__Loading(-5) end @@ -6733,8 +6759,8 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) else - -- Stop and loading. - opsgroup:Wait() + -- Stop and unload. + opsgroup:Wait() opsgroup:Unloading() end @@ -8101,15 +8127,19 @@ end -- @return Core.Point#COORDINATE The coordinate of the object. function OPSGROUP:_CoordinateFromObject(Object) - if Object:IsInstanceOf("COORDINATE") then - return Object - else - if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then - self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") - return Object:GetCoordinate() + if Object then + if Object:IsInstanceOf("COORDINATE") then + return Object else - self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") + if Object:IsInstanceOf("POSITIONABLE") or Object:IsInstanceOf("ZONE_BASE") then + self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate") + return Object:GetCoordinate() + else + self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!") + end end + else + self:E(self.lid.."ERROR: Object passed is nil!") end return nil diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 56f28b878..c354ff996 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -1,4 +1,4 @@ ---- **Ops** - Troop transport assignment of OPS groups. +--- **Ops** - Troop transport assignment for OPS groups. -- -- ## Main Features: -- @@ -106,22 +106,24 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. - self.lid=string.format("OPSTRANSPORT [UID=%d] %s --> %s | ", _OPSTRANSPORTID, Pickupzone:GetName(), Deployzone:GetName()) + self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) -- Defaults. self.uid=_OPSTRANSPORTID - self.status=OPSTRANSPORT.Status.PLANNING + self.status=OPSTRANSPORT.Status.PLANNING + self.pickupzone=Pickupzone self.deployzone=Deployzone self.embarkzone=Pickupzone - self.disembarkzone=Deployzone - self.prio=50 - self.importance=nil - self.Tstart=timer.getAbsTime()+5 self.carrierGroup=nil self.cargos={} self.carriers={} + + self:SetPriority() + self:SetTime() + + -- Add cargo groups. if GroupSet then self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) end @@ -139,6 +141,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Unloaded", "*") + + -- Call status update self:__Status(-1) @@ -183,7 +188,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) -- Debug info. if self.verbose>=0 then local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone:GetName(), self.deployzone:GetName(), self.disembarkzone:GetName()) + self.uid, self.pickupzone:GetName(), self.embarkzone and self.embarkzone:GetName() or "none", self.deployzone:GetName(), self.disembarkzone and self.disembarkzone:GetName() or "none") local Weight=0 for _,_cargo in pairs(self.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup @@ -213,7 +218,7 @@ end -- @param Core.Zone#ZONE DisembarkZone Zone where the troops are disembarked. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkZone(DisembarkZone) - self.disembarkzone=DisembarkZone or self.deployzone + self.disembarkzone=DisembarkZone return self end @@ -260,6 +265,56 @@ function OPSTRANSPORT:_AddCarrier(CarrierGroup) return self end +--- Set transport start and stop time. +-- @param #OPSTRANSPORT self +-- @param #string ClockStart Time the transport is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. +-- @param #string ClockStop (Optional) Time the transport is stopped, e.g. "13:00" for 1 pm. If mission could not be started at that time, it will be removed from the queue. If specified as a #number it will be relative (in seconds) to the current mission time. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetTime(ClockStart, ClockStop) + + -- Current mission time. + local Tnow=timer.getAbsTime() + + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Set stop time. Default nil. + local Tstop=nil + if ClockStop and type(ClockStop)=="number" then + Tstop=Tnow+ClockStop + elseif ClockStop and type(ClockStop)=="string" then + Tstop=UTILS.ClockToSeconds(ClockStop) + end + + self.Tstart=Tstart + self.Tstop=Tstop + + if Tstop then + self.duration=self.Tstop-self.Tstart + end + + return self +end + +--- Set mission priority and (optional) urgency. Urgent missions can cancel other running missions. +-- @param #OPSTRANSPORT self +-- @param #number Prio Priority 1=high, 100=low. Default 50. +-- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. Default is `nil`. +-- @param #boolean Urgent If *true*, another running mission might be cancelled if it has a lower priority. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) + self.prio=Prio or 50 + self.urgent=Urgent + self.importance=Importance + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. @@ -337,14 +392,18 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local fsmstate=self:GetState() - local text=string.format("State=%s", fsmstate) + local text=string.format("State=%s: %s --> %s", fsmstate, self.pickupzone:GetName(), self.deployzone:GetName()) + text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + text=text..string.format("\n- %s: %s %s, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup.cargoStatus, cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none") end + text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do - local carrier=_carrier + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + text=text..string.format("\n- %s: %s %s, cargo=%d kg", carrier:GetName(), carrier:GetState(), carrier.carrierStatus, carrier:GetWeightCargo()) end self:I(self.lid..text) @@ -394,8 +453,7 @@ end function OPSTRANSPORT:onafterDelivered(From, Event, To) self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) - -- TODO: Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. - + -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP if self:GetCarrierTransportStatus(carrier)~=OPSTRANSPORT.Status.DELIVERED then @@ -405,6 +463,16 @@ function OPSTRANSPORT:onafterDelivered(From, Event, To) end +--- On after "Unloaded" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was unloaded from a carrier. +function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 4deca4f816b6081608aa3e4cf684526f35f5b9d5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 19 Feb 2021 09:59:12 +0100 Subject: [PATCH 122/382] Update AI_Cargo_Helicopter.lua Helicopters don't drive, they fly... ;) --- .../Moose/AI/AI_Cargo_Helicopter.lua | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 90a39bdb4..8e33b4113 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -95,7 +95,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate - -- @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 fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- PickedUp Handler OnAfter for AI_CARGO_HELICOPTER - Cargo set has been picked up, ready to deploy -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickedUp @@ -112,14 +112,14 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string From -- @param #string Event -- @param #string To - -- @param #string Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. + -- @param Cargo.CargoGroup#CARGO_GROUP Cargo The #CARGO_GROUP object. -- @param Wrapper.Unit#UNIT Unit The helicopter #UNIT object --- Pickup Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Pickup -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate - -- @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 fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup @@ -135,7 +135,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate Place at which 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. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @return #boolean --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER @@ -145,7 +145,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate - -- @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 fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deployed Handler OnAfter for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeployed @@ -158,20 +158,20 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @function [parent=#AI_CARGO_HELICOPTER] Deploy -- @param #AI_CARGO_HELICOPTER self -- @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. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy -- @param #number Delay Delay in seconds. -- @param #AI_CARGO_HELICOPTER self -- @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. + -- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Home Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Home -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height (optional) Height the Helicopter should by flying at. --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER @@ -179,7 +179,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #number Delay Delay in seconds. -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. - -- @param #number Speed (optional) Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height (optional) Height the Helicopter should by flying at. -- We need to capture the Crash events for the helicopters. @@ -444,7 +444,7 @@ end -- @param Event -- @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. +-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to the pickup coordinate. This parameter is ignored for APCs. -- @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, Height, PickupZone ) @@ -513,7 +513,7 @@ end -- @param Event -- @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. +-- @param #number Speed Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to the deploy coordinate. function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, Height, DeployZone ) self:F({From, Event, To, Coordinate, Speed, Height, DeployZone}) @@ -578,7 +578,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 fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @param #number Height Height in meters to move to 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_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, Height, HomeZone ) From f18c818b8e3d87a749f418770e1c8d389f199c43 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 19 Feb 2021 10:04:30 +0100 Subject: [PATCH 123/382] Update AI_Cargo_Helicopter.lua --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 8e33b4113..79f664a12 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -172,7 +172,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should by flying at. + -- @param #number Height (optional) Height the Helicopter should be flying at. --- Home Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Home @@ -180,7 +180,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate Place to which the helicopter will go. -- @param #number Speed (optional) Speed in km/h to fly to the pickup coordinate. Default is 50% of max possible speed the unit can go. - -- @param #number Height (optional) Height the Helicopter should by flying at. + -- @param #number Height (optional) Height the Helicopter should be flying at. -- We need to capture the Crash events for the helicopters. -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. From 302018feff2455ad460bba3d0dbb48df49b0beef Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 20 Feb 2021 00:03:45 +0100 Subject: [PATCH 124/382] OPS Cargo --- Moose Development/Moose/Ops/ArmyGroup.lua | 4 ++ Moose Development/Moose/Ops/FlightGroup.lua | 13 +++- Moose Development/Moose/Ops/NavyGroup.lua | 4 ++ Moose Development/Moose/Ops/OpsGroup.lua | 54 +++++++++++++--- Moose Development/Moose/Ops/OpsTransport.lua | 65 +++++++++++++++++--- 5 files changed, 119 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f758ff079..7660edc0f 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -540,6 +540,10 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index cb03b0085..8472b6ad0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1529,6 +1529,10 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then @@ -2067,12 +2071,15 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Number of cargo transports remaining. local nTransports=self:CountRemainingTransports() + + -- Debug info. + self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d", tostring(self.passedfinalwp), nMissions, nTasks, nTransports)) -- Final waypoint passed? if self.passedfinalwp then -- Got current mission or task? - if self.currentmission==nil and self.taskcurrent==0 then + if self.currentmission==nil and self.taskcurrent==0 and self.cargoTransport==nil then -- Number of remaining tasks/missions? if nTasks==0 and nMissions==0 and nTransports==0 then @@ -2082,10 +2089,10 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Send flight to destination. if destbase then - self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") self:__RTB(-3, destbase) elseif destzone then - self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:__RTZ(-3, destzone) else self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9f8f4ba9d..8c78e0ede 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -660,6 +660,10 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() + + -- Not dead or destroyed yet. + self.isDead=false + self.isDestroyed=false if self.isAI then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3f3dc427b..3dacfb5f3 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -32,6 +32,8 @@ -- @field #boolean isAircraft If true, group is airplane or helicopter. -- @field #boolean isNaval If true, group is ships or submarine. -- @field #boolean isGround If true, group is some ground unit. +-- @field #boolean isDestroyed If true, the whole group was destroyed. +-- @field #boolean isDead If true, the whole group is dead. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. -- @field Wrapper.Airbase#AIRBASE homebase The home base of the flight group. @@ -1537,7 +1539,18 @@ end -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() - return self:Is("Dead") + if self.isDead then + return true + else + return self:Is("Dead") + end +end + +--- Check if group was destroyed. +-- @param #OPSGROUP self +-- @return #boolean If true, all units/elements of the group were destroyed. +function OPSGROUP:IsDestroyed() + return self.isDestroyed end --- Check if FSM is stopped. @@ -2994,6 +3007,7 @@ end -- @param #OPSGROUP self -- @return #number Number of unfinished transports in the queue. function OPSGROUP:CountRemainingTransports() + env.info("FF Count remaining transports="..#self.cargoqueue) local N=0 @@ -3001,14 +3015,17 @@ function OPSGROUP:CountRemainingTransports() for _,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + self:I(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) + -- Count not delivered (executing or scheduled) assignments. - if transport and transport.status~=OPSGROUP.TransportStatus.DELIVERED then + if transport and transport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and transport:GetState()~=OPSTRANSPORT.Status.DELIVERED then N=N+1 end end + env.info("FF Count remaining transports="..N) return N end @@ -4532,6 +4549,16 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) return self end +--- On after "Destroyed" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterDestroyed(From, Event, To) + self:T(self.lid..string.format("Group destroyed at t=%.3f", timer.getTime())) + self.isDestroyed=true +end + --- On before "Dead" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4550,10 +4577,9 @@ end -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false + + -- Is dead now. + self.isDead=true -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do @@ -4565,6 +4591,10 @@ function OPSGROUP:onafterDead(From, Event, To) mission:GroupDead(self) end + + -- Delete waypoints so they are re-initialized at the next spawn. + self.waypoints=nil + self.groupinitialized=false -- Stop in a sec. self:__Stop(-5) @@ -4922,6 +4952,7 @@ function OPSGROUP:AddOpsTransport(OpsTransport) --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) + self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) end --- Delete a cargo transport assignment from the cargo queue @@ -5654,6 +5685,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Cargo was delivered (somehow). cargo.delivered=true + -- Increase number of delivered cargos. + self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 + if carrierGroup then --- @@ -5902,12 +5936,14 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) elseif self:IsLandedAt() then local Task=self:GetTaskCurrent() self:TaskCancel(Task) - end + end + else + -- Army & Navy: give Cruise command to "wake up" from waiting status. + self:__Cruise(0.1) end -- Check group done. self:I(self.lid.."All cargo delivered ==> check group done") - self:__Cruise(0.1) self:_CheckGroupDone(0.2) -- No current transport any more. @@ -7504,7 +7540,7 @@ end -- @return #OPSGROUP self function OPSGROUP:_UpdatePosition() - if self:IsAlive()~=nil then + if self:IsExist() then -- Backup last state to monitor differences. self.positionLast=self.position or self:GetVec3() diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index c354ff996..6f098e30d 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -38,8 +38,11 @@ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. --- @field disembarkActivation Activation setting when group is disembared from carrier. --- @field disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. +-- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #number Ncargo Total number of cargo groups. +-- @field #number Ncarrier Total number of assigned carriers. +-- @field #number Ndelivered Total number of cargo groups delivered. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -118,6 +121,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self.carrierGroup=nil self.cargos={} self.carriers={} + self.Ncargo=0 + self.Ncarrier=0 + self.Ndelivered=0 self:SetPriority() @@ -168,6 +174,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) + self.Ncargo=self.Ncargo+1 end else @@ -180,6 +187,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) if cargo then table.insert(self.cargos, cargo) + self.Ncargo=self.Ncargo+1 end end @@ -249,6 +257,21 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) return self end +--- Check if an OPS group is assigned as carrier for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. +-- @return #boolean If true, group is an assigned carrier. +function OPSTRANSPORT:IsCarrier(CarrierGroup) + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + return true + end + end + + return false +end --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -256,11 +279,19 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:_AddCarrier(CarrierGroup) - self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + if not self:IsCarrier(CarrierGroup) then - self:Scheduled() - - table.insert(self.carriers, CarrierGroup) + -- Increase carrier count. + self.Ncarrier=self.Ncarrier+1 + + -- Set trans + self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + + self:Scheduled() + + table.insert(self.carriers, CarrierGroup) + + end return self end @@ -390,9 +421,11 @@ end -- @param #string To To state. function OPSTRANSPORT:onafterStatus(From, Event, To) + -- Current FSM state. local fsmstate=self:GetState() + - local text=string.format("State=%s: %s --> %s", fsmstate, self.pickupzone:GetName(), self.deployzone:GetName()) + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do @@ -483,20 +516,34 @@ end function OPSTRANSPORT:_CheckDelivered() local done=true + local dead=true for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if cargo.delivered then -- This one is delivered. - elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + dead=false + elseif cargo.opsgroup==nil then + -- This one is nil?! + dead=false + elseif cargo.opsgroup:IsDestroyed() then + -- This one was destroyed. + elseif cargo.opsgroup:IsDead() then -- This one is dead. + dead=false + elseif cargo.opsgroup:IsStopped() then + -- This one is stopped. + dead=false else done=false --Someone is not done! + dead=false end end - if done then + if dead then + --self:CargoDead() + elseif done then self:Delivered() end From c4084156acd7ae6029acecad2ddbabd2a9ec0cda Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Feb 2021 00:19:32 +0100 Subject: [PATCH 125/382] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 135 +++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 28 ++-- 2 files changed, 123 insertions(+), 40 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 3dacfb5f3..c0879f357 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4641,7 +4641,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - if self.verbose>=1 then + if self.verbose>=3 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) @@ -4652,7 +4652,7 @@ function OPSGROUP:_CheckCargoTransport() end -- Cargo queue debug info. - if self.verbose>=1 then + if self.verbose>=3 then local text="" for i,_transport in pairs(self.cargoqueue) do local transport=_transport --#Ops.OpsTransport#OPSTRANSPORT @@ -4681,7 +4681,7 @@ function OPSGROUP:_CheckCargoTransport() if self.cargoTransport then -- Debug info. - if self.verbose>=1 then + if self.verbose>=2 then local text=string.format("Carrier [%s]: %s --> %s", self.carrierStatus, self.cargoTransport.pickupzone:GetName(), self.cargoTransport.deployzone:GetName()) for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup @@ -4785,7 +4785,7 @@ function OPSGROUP:_CheckCargoTransport() local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT local delivered=self:_CheckDelivered(transport) if delivered then - self:Delivered(transport) + --self:Delivered(transport) end end @@ -5141,6 +5141,36 @@ function OPSGROUP:GetFreeCargobay(UnitName) return Free end +--- Get max weight of cargo (group) this group can load. This is the largest free cargo bay of any (not dead) element of the group. +-- Optionally, you can calculate the current max weight possible, which accounts for currently loaded cargo. +-- @param #OPSGROUP self +-- @param #boolean Currently If true, calculate the max weight currently possible in case there is already cargo loaded. +-- @return #number Max weight in kg. +function OPSGROUP:GetFreeCargobayMax(Currently) + + local maxweight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.status~=OPSGROUP.ElementStatus.DEAD then + + local weight=element.weightMaxCargo + + if Currently then + weight=weight-element.weightCargo + end + + -- Check if this element can load more. + if weight>maxweight then + maxweight=weight + end + + end + end + + return maxweight +end + --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self @@ -5163,6 +5193,26 @@ function OPSGROUP:GetWeightCargo(UnitName) return weight end +--- Get max weight of the internal cargo the group can carry. +-- @param #OPSGROUP self +-- @return #number Cargo weight in kg. +function OPSGROUP:GetWeightCargoMax() + + local weight=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + if element.status~=OPSGROUP.ElementStatus.DEAD then + + weight=weight+element.weightMaxCargo + + end + + end + + return weight +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. @@ -5246,7 +5296,7 @@ end -- @param #string To To state. function OPSGROUP:onafterPickup(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP @@ -5370,7 +5420,7 @@ end -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.LOADING @@ -5398,23 +5448,29 @@ function OPSGROUP:onafterLoading(From, Event, To) return nil end + --TODO: sort cargos wrt weight. + + -- Loop over all cargos. for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --#OPSGROUP.CargoGroup - if self:CanCargo(cargo.opsgroup) and not cargo.delivered then + -- Check that cargo weight is + if self:CanCargo(cargo.opsgroup) and not (cargo.delivered or cargo.opsgroup:IsDead()) then -- Check that group is not cargo already and not busy. -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading() or cargo.opsgroup:IsLoaded()) then - -- Check if cargo is in pickup zone. + -- Check if cargo is in embark/pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) - -- First check if cargo is not delivered yet. + -- Cargo MUST be inside zone or it will not be loaded! if inzone then + -- Weight of cargo. local weight=cargo.opsgroup:GetWeightTotal() + -- Find a carrier that has enough free cargo bay for this group. local carrier=_findCarrier(weight) if carrier then @@ -5429,19 +5485,20 @@ function OPSGROUP:onafterLoading(From, Event, To) cargo.opsgroup:Board(self, carrier) else - - env.info("FF cannot board carrier") - + -- Debug info. + env.info("FF cannot board carrier") end else + -- Debug info. env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end end - else - env.info("FF cargo already delivered") + else + -- Debug info. + self:T3(self.lid.."Cargo already delivered, is dead or carrier cannot") end end @@ -5451,11 +5508,11 @@ end --- Clear waypoints. -- @param #OPSGROUP self function OPSGROUP:ClearWaypoints() - -- Clear all waypoints. - for i=1,#self.waypoints do - table.remove(self.waypoints, i) - end - self.waypoints={} + -- Clear all waypoints. + for i=1,#self.waypoints do + table.remove(self.waypoints, i) + end + self.waypoints={} end --- On after "Load" event. Carrier loads a cargo group into ints cargo bay. @@ -5488,7 +5545,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) --- -- Debug info. - CargoGroup:I(CargoGroup.lid..string.format("New cargo status %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) + CargoGroup:I(CargoGroup.lid..string.format("New cargo status: %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) -- Set cargo status. CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED @@ -5540,7 +5597,7 @@ end -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) -- Set carrier status. self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING @@ -5662,7 +5719,7 @@ end -- @param #string To To state. function OPSGROUP:onafterUnloading(From, Event, To) -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING @@ -5793,7 +5850,7 @@ end function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) -- Debug info. - OpsGroup:I(OpsGroup.lid..string.format("New cargo status %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) + OpsGroup:I(OpsGroup.lid..string.format("New cargo status: %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO @@ -5914,13 +5971,15 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then -- Debug info. - self:I(self.lid..string.format("New carrier status %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - + -- Checks if self:IsPickingup() then -- Delete pickup waypoint? + local wpindex=self:GetWaypointIndexNext(false) + if wpindex then + self:RemoveWaypoint(wpindex) + end elseif self:IsLoading() then -- Nothing to do? elseif self:IsTransporting() then @@ -5929,6 +5988,9 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Nothing to do? end + -- This is not a carrier anymore. + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then if self:IsUncontrolled() then @@ -5960,6 +6022,23 @@ end -- Cargo Group Functions --- +--- On before "Board" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP CarrierGroup The carrier group. +-- @param #OPSGROUP.Element Carrier The OPSGROUP element +function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) + + if self:IsDead() then + self:I(self.lid.."Group DEAD ==> Deny Board transition!") + return false + end + + return true +end + --- On after "Board" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5969,7 +6048,7 @@ end -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Debug info. - self:I(self.lid..string.format("New cargo status %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) + self:I(self.lid..string.format("New cargo status: %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 6f098e30d..323dd6017 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -37,6 +37,7 @@ -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. +-- @field Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. -- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. @@ -82,13 +83,14 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.2" +OPSTRANSPORT.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add start conditions. +-- TODO: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -143,14 +145,12 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Unloaded", "*") + - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Stop", "*") - - self:AddTransition("*", "Unloaded", "*") - - - -- Call status update + -- Call status update. self:__Status(-1) return self @@ -430,13 +430,15 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - text=text..string.format("\n- %s: %s %s, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), cargo.opsgroup.cargoStatus, cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none") + local name=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name) end text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s %s, cargo=%d kg", carrier:GetName(), carrier:GetState(), carrier.carrierStatus, carrier:GetWeightCargo()) + text=text..string.format("\n- %s: %s [%s], cargo=%d/%d kg, free cargo bay %d/%d kg", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), carrier:GetFreeCargobayMax(true), carrier:GetFreeCargobayMax()) end self:I(self.lid..text) @@ -510,7 +512,6 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Check if all cargo of this transport assignment was delivered. -- @param #OPSTRANSPORT self function OPSTRANSPORT:_CheckDelivered() @@ -543,7 +544,10 @@ function OPSTRANSPORT:_CheckDelivered() if dead then --self:CargoDead() + self:I(self.lid.."All cargo DEAD!") + self:Delivered() elseif done then + self:I(self.lid.."All cargo delivered") self:Delivered() end From 5cc023d1fef7790e6ff6144af09170eec703958a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Feb 2021 11:53:52 +0100 Subject: [PATCH 126/382] OPS Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 7 +- Moose Development/Moose/Ops/OpsTransport.lua | 88 +++++++++++++++++++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c0879f357..20f482f2b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4840,9 +4840,6 @@ end -- @return Ops.OpsTransport#OPSTRANSPORT The next due cargo transport or `nil`. function OPSGROUP:_GetNextCargoTransport() - -- Abs. mission time in seconds. - local Time=timer.getAbsTime() - -- Current position. local coord=self:GetCoordinate() @@ -4869,7 +4866,9 @@ function OPSGROUP:_GetNextCargoTransport() for _,_cargotransport in pairs(self.cargoqueue) do local cargotransport=_cargotransport --Ops.OpsTransport#OPSTRANSPORT - if Time>=cargotransport.Tstart and cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then + local carrierstatusScheduled=cargotransport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED + + if cargotransport:IsReadyToGo() and carrierstatusScheduled and (cargotransport.importance==nil or cargotransport.importance<=vip) and not self:_CheckDelivered(cargotransport) then cargotransport:Executing() cargotransport:SetCarrierTransportStatus(self, OPSTRANSPORT.Status.EXECUTING) return cargotransport diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 323dd6017..6bf39a147 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -33,6 +33,7 @@ -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). +-- @field #table conditionStart Start conditions. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. @@ -62,7 +63,8 @@ OPSTRANSPORT = { verbose = 1, cargos = {}, carriers = {}, - carrierTransportStatus = {}, + carrierTransportStatus = {}, + conditionStart = {}, } --- Cargo transport status. @@ -78,6 +80,11 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } +--- Generic mission condition. +-- @type OPSTRANSPORT.Condition +-- @field #function func Callback function to check for a condition. Should return a #boolean. +-- @field #table arg Optional arguments passed to the condition callback function. + --- Transport ID. _OPSTRANSPORTID=0 @@ -89,7 +96,7 @@ OPSTRANSPORT.version="0.0.3" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add start conditions. +-- DONE: Add start conditions. -- TODO: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -345,6 +352,26 @@ function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) return self end +--- Add start condition. +-- @param #OPSTRANSPORT self +-- @param #function ConditionFunction Function that needs to be true before the transport can be started. Must return a #boolean. +-- @param ... Condition function arguments if any. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) + + local condition={} --#OPSTRANSPORT.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStart, condition) + + return self +end + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -367,7 +394,6 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) end - --- Create a cargo group data structure. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP group The GROUP object. @@ -410,6 +436,38 @@ function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) return cargo end +--- Check if transport is ready to be started. +-- * Start time passed. +-- * Stop time did not pass already. +-- * All start conditions are true. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, mission can be started. +function OPSTRANSPORT:IsReadyToGo() + + local Tnow=timer.getAbsTime() + + -- Start time did not pass yet. + if self.Tstart and Tnowself.Tstop or false then + return false + end + + -- All start conditions true? + local startme=self:EvalConditionsAll(self.conditionStart) + + if not startme then + return false + end + + + -- We're good to go! + return true +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -552,3 +610,27 @@ function OPSTRANSPORT:_CheckDelivered() end end + +--- Check if all given condition are true. +-- @param #OPSTRANSPORT self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false. +function OPSTRANSPORT:EvalConditionsAll(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --#OPSTRANSPORT.Condition + + -- Call function. + local istrue=condition.func(unpack(condition.arg)) + + -- Any false will return false. + if not istrue then + return false + end + + end + + -- All conditions were true. + return true +end From 11cdae098ba373ebfd469a5880a9abd9455e6f91 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 21 Feb 2021 19:55:48 +0100 Subject: [PATCH 127/382] Update Intelligence.lua Honor settings for clustermarkers --- Moose Development/Moose/Ops/Intelligence.lua | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index edff50801..fec438144 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -810,16 +810,17 @@ function INTEL:PaintPicture() -- Update F10 marker text if cluster has changed. - for _,_cluster in pairs(self.Clusters) do - local cluster=_cluster --#INTEL.Cluster - - local coordinate=self:GetClusterCoordinate(cluster) - - - -- Update F10 marker. - self:UpdateClusterMarker(cluster) - end + if self.clustermarkers then --honor markersettings + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + local coordinate=self:GetClusterCoordinate(cluster) + + + -- Update F10 marker. + self:UpdateClusterMarker(cluster) + end + end end --- Create a new cluster. From 832abd7860de118666e8d1fc4e44a12fe048b453 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 23 Feb 2021 10:34:55 +0100 Subject: [PATCH 128/382] Error in NewFromLLDD - altitude isn't set if given Thanks to tomekldc --- Moose Development/Moose/Core/Point.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 31669d146..0c722f9af 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -355,9 +355,9 @@ do -- COORDINATE -- Adjust height if altitude==nil then - _coord.y=altitude - else _coord.y=self:GetLandHeight() + else + _coord.y=altitude end return _coord From c72c3fd0917a9a764ca205c7a93bf7e8d41e9542 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 24 Feb 2021 12:55:54 +0100 Subject: [PATCH 129/382] Update Mantis.lua Added a smarter switch-on distance for SHORAD, added user function to set AWACS detection range, change default SAM firing range to 95% --- Moose Development/Moose/Functional/Mantis.lua | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 29b360e74..c4564fb94 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Jan 2021 +-- Date: Feb 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -54,6 +54,7 @@ -- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range +-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. -- @extends Core.Base#BASE @@ -127,7 +128,7 @@ -- * grouping = 5000 (meters) - Detection (EWR) will group enemy flights to areas of 5km for tracking - `MANTIS:SetEWRGrouping(radius)` -- * acceptrange = 80000 (meters) - Detection (EWR) will on consider flights inside a 80km radius - `MANTIS:SetEWRRange(radius)` -- * detectinterval = 30 (seconds) - MANTIS will decide every 30 seconds which SAM to activate - `MANTIS:SetDetectInterval(interval)` --- * engagerange = 75 (percent) - SAMs will only fire if flights are inside of a 75% radius of their max firerange - `MANTIS:SetSAMRange(range)` +-- * engagerange = 85 (percent) - SAMs will only fire if flights are inside of a 85% radius of their max firerange - `MANTIS:SetSAMRange(range)` -- * dynamic = false - Group filtering is set to once, i.e. newly added groups will not be part of the setup by default - `MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic)` -- * autorelocate = false - HQ and (mobile) EWR system will not relocate in random intervals between 30mins and 1 hour - `MANTIS:SetAutoRelocate(hq, ewr)` -- * debug = false - Debugging reports on screen are set to off - `MANTIS:Debug(onoff)` @@ -187,7 +188,8 @@ MANTIS = { awacsrange = 250000, Shorad = nil, ShoradLink = false, - ShoradTime = 600, + ShoradTime = 600, + ShoradActDistance = 15000, } ----------------------------------------------------------------------- @@ -253,19 +255,20 @@ do self.verbose = false self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil - self.awacsrange = 250000 --TODO: 250km, User Function to change + self.awacsrange = 250000 --DONE: 250km, User Function to change self.Shorad = nil self.ShoradLink = false - self.ShoradTime = 600 + self.ShoradTime = 600 + self.ShoradActDistance = 15000 + if type(awacs) == "string" then self.advAwacs = true else self.advAwacs = false end - -- @field #string version - self.version="0.3.6" - env.info(string.format("***** Starting MANTIS Version %s *****", self.version)) + -- Inherit everything from BASE class. + local self = BASE:Inherit(self, BASE:New()) -- #MANTIS -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -294,8 +297,10 @@ do if self.HQ_Template_CC then self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end - -- Inherit everything from BASE class. - local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + + -- @field #string version + self.version="0.3.7" + self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self end @@ -397,6 +402,14 @@ do end end end + + --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! + -- @param #MANTIS self + -- @param #number range Detection range of the AWACS group + function MANTIS:SetAwacsRange(range) + local range = range or 250000 + self.awacsrange = range + end --- Function to set the HQ object for further use -- @param #MANTIS self @@ -587,6 +600,7 @@ do -- @param #table dectset Table of coordinates of detected items -- @param samcoordinate Core.Point#COORDINATE Coordinate object. -- @return #boolean True if in any zone, else false + -- @return #number Distance Target distance in meters or zero when no object is in zone function MANTIS:CheckObjectInZone(dectset, samcoordinate) self:F(self.lid.."CheckObjectInZone Called") -- check if non of the coordinate is in the given defense zone @@ -603,10 +617,10 @@ do if self.verbose then env.info(self.lid..text) end -- end output to cross-check if targetdistance <= radius then - return true + return true, targetdistance end end - return false + return false, 0 end --- (Internal) Function to start the detection via EWR groups @@ -676,7 +690,7 @@ do -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() - -- TODO: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 + -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 self:F(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group @@ -787,13 +801,14 @@ do local samcoordinate = _data[2] local name = _data[1] local samgroup = GROUP:FindByName(name) - if self:CheckObjectInZone(detset, samcoordinate) then --check any target in zone + local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + if IsInZone then --check any target in zone if samgroup:IsAlive() then -- switch off SAM samgroup:OptionAlarmStateRed() -- link in to SHORAD if available - -- TODO Test integration fully - if self.ShoradLink then + -- DONE: Test integration fully + if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime From cdd829e7bea070818abb57525675bc21041ed174 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 24 Feb 2021 16:16:28 +0100 Subject: [PATCH 130/382] Update AirWing.lua Ensure correct type of tanker is going on mission --- Moose Development/Moose/Ops/AirWing.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 8ffd7e6bf..2f3870403 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -966,7 +966,7 @@ function AIRWING:CheckTANKER() local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) mission.patroldata=patrol @@ -984,7 +984,7 @@ function AIRWING:CheckTANKER() local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) mission.patroldata=patrol From 7751d4ee8801115ad6ddcb6175e205582725a464 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 24 Feb 2021 16:22:13 +0100 Subject: [PATCH 131/382] Update Intelligence.lua Added function to set verbosity --- Moose Development/Moose/Ops/Intelligence.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index fec438144..3c6993761 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -351,6 +351,15 @@ function INTEL:SetClusterAnalysis(Switch, Markers) return self end +--- Set verbosity level for debugging. +-- @param #INTEL self +-- @param #number Verbosity The higher, the noisier, e.g. 0=off, 2=debug +-- @return #INTEL self +function INTEL:SetVerbosity(Verbosity) + self.verbose=Verbosity or 2 + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From a6faadea488bde47cb51a6dc852b97411635688b Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Thu, 25 Feb 2021 12:52:50 -0800 Subject: [PATCH 132/382] T-45C Goshawk Parameters Addition of parameters for the VNAO T-45C Goshawk. --- Moose Development/Moose/Ops/Airboss.lua | 41 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 07ee32445..6748709ef 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1271,6 +1271,7 @@ AIRBOSS.AircraftCarrier={ F14B="F-14B", F14A_AI="F-14A", FA18C="F/A-18C", + T45C="T-45", S3B="S-3B", S3BTANKER="S-3B Tanker", E2D="E-2C", @@ -5471,6 +5472,7 @@ function AIRBOSS:_GetAircraftAoA(playerData) -- Get AC type. local hornet=playerData.actype==AIRBOSS.AircraftCarrier.HORNET + local goshawk=playerData.actype==AIRBOSS.AircraftCarrier.T45C local skyhawk=playerData.actype==AIRBOSS.AircraftCarrier.A4EC local harrier=playerData.actype==AIRBOSS.AircraftCarrier.AV8B local tomcat=playerData.actype==AIRBOSS.AircraftCarrier.F14A or playerData.actype==AIRBOSS.AircraftCarrier.F14B @@ -5497,6 +5499,15 @@ function AIRBOSS:_GetAircraftAoA(playerData) aoa.OnSpeedMin = self:_AoAUnit2Deg(playerData, 14.5) --14.17 --14.5 units aoa.Fast = self:_AoAUnit2Deg(playerData, 14.0) --13.33 --14.0 units aoa.FAST = self:_AoAUnit2Deg(playerData, 13.0) --11.67 --13.0 units + elseif goshawk then + -- T-45C Goshawk parameters. + aoa.SLOW = 8.00 --19 + aoa.Slow = 7.75 --18 + aoa.OnSpeedMax = 7.25 --17.5 + aoa.OnSpeed = 7.00 --17 + aoa.OnSpeedMin = 6.75 --16.5 + aoa.Fast = 6.25 --16 + aoa.FAST = 6.00 --15 elseif skyhawk then -- A-4E-C Skyhawk parameters from https://forums.eagle.ru/showpost.php?p=3703467&postcount=390 -- Note that these are arbitrary UNITS and not degrees. We need a conversion formula! @@ -5681,6 +5692,9 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.BREAKENTRY then @@ -5691,11 +5705,14 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif skyhawk then alt=UTILS.FeetToMeters(600) speed=UTILS.KnotsToMps(250) + elseif goshawk then + alt=UTILS.FeetToMeters(800) + speed=UTILS.KnotsToMps(300) end elseif step==AIRBOSS.PatternStep.EARLYBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5703,7 +5720,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.LATEBREAK then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(800) elseif skyhawk then alt=UTILS.FeetToMeters(600) @@ -5711,7 +5728,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.ABEAM then - if hornet or tomcat or harrier then + if hornet or tomcat or harrier or goshawk then alt=UTILS.FeetToMeters(600) elseif skyhawk then alt=UTILS.FeetToMeters(500) @@ -5725,11 +5742,20 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) else dist=UTILS.NMToMeters(1.2) end + + if goshawk then + -- 0.9 to 1.1 NM per natops ch.4 page 48 + dist=UTILS.NMToMeters(0.9) + else + dist=UTILS.NMToMeters(1.1) + end elseif step==AIRBOSS.PatternStep.NINETY then if hornet or tomcat then alt=UTILS.FeetToMeters(500) + elseif goshawk then + alt=UTILS.FeetToMeters(450) elseif skyhawk then alt=UTILS.FeetToMeters(500) elseif harrier then @@ -5740,7 +5766,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.WAKE then - if hornet then + if hornet or goshawk then alt=UTILS.FeetToMeters(370) elseif tomcat then alt=UTILS.FeetToMeters(430) -- Tomcat should be a bit higher as it intercepts the GS a bit higher. @@ -5753,7 +5779,7 @@ function AIRBOSS:_GetAircraftParameters(playerData, step) elseif step==AIRBOSS.PatternStep.FINAL then - if hornet then + if hornet or goshawk then alt=UTILS.FeetToMeters(300) elseif tomcat then alt=UTILS.FeetToMeters(360) @@ -10491,6 +10517,9 @@ function AIRBOSS:_Trapped(playerData) elseif playerData.actype==AIRBOSS.AircraftCarrier.A4EC then -- A-4E gets slowed down much faster the the F/A-18C! dcorr=56 + elseif playerData.actype==AIRBOSS.AircraftCarrier.T45C then + -- T-45 also gets slowed down much faster the the F/A-18C. + dcorr=56 end -- Get wire. @@ -14080,6 +14109,8 @@ function AIRBOSS:_GetACNickname(actype) local nickname="unknown" if actype==AIRBOSS.AircraftCarrier.A4EC then nickname="Skyhawk" + elseif actype==AIRBOSS.AircraftCarrier.T45C then + nickname="Goshawk" elseif actype==AIRBOSS.AircraftCarrier.AV8B then nickname="Harrier" elseif actype==AIRBOSS.AircraftCarrier.E2D then From 4a6377c20445cd399c0d39bf9ff959789bca5fac Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 27 Feb 2021 21:17:58 +0100 Subject: [PATCH 133/382] Ops Cargo --- Moose Development/Moose/Core/Database.lua | 16 + Moose Development/Moose/Core/Set.lua | 602 +++++++++++++++- Moose Development/Moose/Ops/ArmyGroup.lua | 5 - Moose Development/Moose/Ops/FlightGroup.lua | 5 +- Moose Development/Moose/Ops/NavyGroup.lua | 29 +- Moose Development/Moose/Ops/OpsGroup.lua | 684 ++++++++++--------- Moose Development/Moose/Ops/OpsTransport.lua | 400 ++++++++--- 7 files changed, 1276 insertions(+), 465 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 8ae42a151..35077c05b 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1444,6 +1444,22 @@ function DATABASE:GetOpsGroup(groupname) return self.FLIGHTGROUPS[groupname] end +--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base. +-- @param #DATABASE self +-- @param #string groupname Group name of the group. Can also be passed as GROUP object. +-- @return Ops.OpsGroup#OPSGROUP OPS group object. +function DATABASE:FindOpsGroup(groupname) + + -- Get group and group name. + if type(groupname)=="string" then + else + groupname=groupname:GetName() + end + + env.info("Getting OPSGROUP "..tostring(groupname)) + return self.FLIGHTGROUPS[groupname] +end + --- Add a flight control to the data base. -- @param #DATABASE self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c8cb5dd05..5fb677d9e 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -233,7 +233,7 @@ do -- SET_BASE -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:F2( { ObjectName = ObjectName, Object = Object } ) + self:I( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then @@ -5948,3 +5948,603 @@ do -- SET_ZONE_GOAL end end + + + +do -- SET_OPSGROUP + + --- @type SET_OPSGROUP + -- @extends Core.Set#SET_BASE + + --- Mission designers can use the @{Core.Set#SET_OPSGROUP} class to build sets of OPS groups belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Contain a certain string pattern + -- + -- ## SET_OPSGROUP constructor + -- + -- Create a new SET_OPSGROUP object with the @{#SET_OPSGROUP.New} method: + -- + -- * @{#SET_OPSGROUP.New}: Creates a new SET_OPSGROUP object. + -- + -- ## Add or Remove GROUP(s) from SET_OPSGROUP + -- + -- GROUPS can be added and removed using the @{Core.Set#SET_OPSGROUP.AddGroupsByName} and @{Core.Set#SET_OPSGROUP.RemoveGroupsByName} respectively. + -- These methods take a single GROUP name or an array of GROUP names to be added or removed from SET_OPSGROUP. + -- + -- ## SET_OPSGROUP filter criteria + -- + -- You can set filter criteria to define the set of groups within the SET_OPSGROUP. + -- Filter criteria are defined by: + -- + -- * @{#SET_OPSGROUP.FilterCoalitions}: Builds the SET_OPSGROUP with the groups belonging to the coalition(s). + -- * @{#SET_OPSGROUP.FilterCategories}: Builds the SET_OPSGROUP with the groups belonging to the category(ies). + -- * @{#SET_OPSGROUP.FilterCountries}: Builds the SET_OPSGROUP with the groups belonging to the country(ies). + -- * @{#SET_OPSGROUP.FilterPrefixes}: Builds the SET_OPSGROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*. + -- * @{#SET_OPSGROUP.FilterActive}: Builds the SET_OPSGROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! + -- + -- For the Category Filter, extra methods have been added: + -- + -- * @{#SET_OPSGROUP.FilterCategoryAirplane}: Builds the SET_OPSGROUP from airplanes. + -- * @{#SET_OPSGROUP.FilterCategoryHelicopter}: Builds the SET_OPSGROUP from helicopters. + -- * @{#SET_OPSGROUP.FilterCategoryGround}: Builds the SET_OPSGROUP from ground vehicles or infantry. + -- * @{#SET_OPSGROUP.FilterCategoryShip}: Builds the SET_OPSGROUP from ships. + -- + -- + -- Once the filter criteria have been set for the SET_OPSGROUP, you can start filtering using: + -- + -- * @{#SET_OPSGROUP.FilterStart}: Starts the filtering of the groups within the SET_OPSGROUP and add or remove GROUP objects **dynamically**. + -- * @{#SET_OPSGROUP.FilterOnce}: Filters of the groups **once**. + -- + -- + -- ## SET_OPSGROUP iterators + -- + -- Once the filters have been defined and the SET_OPSGROUP has been built, you can iterate the SET_OPSGROUP with the available iterator methods. + -- The iterator methods will walk the SET_OPSGROUP set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET_OPSGROUP: + -- + -- * @{#SET_OPSGROUP.ForEachGroup}: Calls a function for each alive group it finds within the SET_OPSGROUP. + -- + -- ## SET_OPSGROUP trigger events on the GROUP objects. + -- + -- The SET is derived from the FSM class, which provides extra capabilities to track the contents of the GROUP objects in the SET_OPSGROUP. + -- + -- ### When a GROUP object crashes or is dead, the SET_OPSGROUP will trigger a **Dead** event. + -- + -- You can handle the event using the OnBefore and OnAfter event handlers. + -- The event handlers need to have the paramters From, Event, To, GroupObject. + -- The GroupObject is the GROUP object that is dead and within the SET_OPSGROUP, and is passed as a parameter to the event handler. + -- See the following example: + -- + -- -- Create the SetCarrier SET_OPSGROUP collection. + -- + -- local SetHelicopter = SET_OPSGROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() + -- + -- -- Put a Dead event handler on SetCarrier, to ensure that when a carrier is destroyed, that all internal parameters are reset. + -- + -- function SetHelicopter:OnAfterDead( From, Event, To, GroupObject ) + -- self:F( { GroupObject = GroupObject:GetName() } ) + -- end + -- + -- + -- === + -- + -- @field #SET_OPSGROUP SET_OPSGROUP + -- + SET_OPSGROUP = { + ClassName = "SET_OPSGROUP", + Filter = { + Coalitions = nil, + Categories = nil, + Countries = nil, + GroupPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + Categories = { + plane = Group.Category.AIRPLANE, + helicopter = Group.Category.HELICOPTER, + ground = Group.Category.GROUND, + ship = Group.Category.SHIP, + }, + }, -- FilterMeta + } + + + --- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP + function SET_OPSGROUP:New() + + -- Inherit SET_BASE. + local self = BASE:Inherit(self, SET_BASE:New(_DATABASE.GROUPS)) -- #SET_OPSGROUP + + -- Include non activated + self:FilterActive( false ) + + return self + end + + --- Gets the Set. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:GetAliveSet() + + local AliveSet = SET_OPSGROUP:New() + + -- Clean the Set before returning with only the alive Groups. + for GroupName, GroupObject in pairs(self.Set) do + local GroupObject=GroupObject --Wrapper.Group#GROUP + + if GroupObject and GroupObject:IsAlive() then + AliveSet:Add(GroupName, GroupObject) + end + end + + return AliveSet.Set or {} + end + + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using a given ObjectName as the index. + -- @param #SET_BASE self + -- @param #string ObjectName The name of the object. + -- @param Core.Base#BASE Object The object itself. + -- @return Core.Base#BASE The added BASE Object. + function SET_OPSGROUP:Add(ObjectName, Object) + self:I( { ObjectName = ObjectName, Object = Object } ) + + -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set + if self.Set[ObjectName] then + self:Remove(ObjectName, true) + end + + local object=nil --Ops.OpsGroup#OPSGROUP + if Object:IsInstanceOf("GROUP") then + + --- + -- GROUP Object + --- + + -- Fist, look up in the DATABASE if an OPSGROUP already exists. + object=_DATABASE:FindOpsGroup(ObjectName) + + if not object then + + if Object:IsShip() then + object=NAVYGROUP:New(Object) + elseif Object:IsGround() then + object=ARMYGROUP:New(Object) + elseif Object:IsAir() then + object=FLIGHTGROUP:New(Object) + else + env.error("ERROR: Unknown category of group object!") + end + end + + elseif Object:IsInstanceOf("OPSGROUP") then + -- We already have an OPSGROUP. + object=Object + else + env.error("ERROR: Object must be a GROUP or OPSGROUP!") + end + + -- Add object to set. + self.Set[ObjectName]=object + + -- Add Object name to Index. + table.insert(self.Index, ObjectName) + + -- Trigger Added event. + self:Added(ObjectName, object) + end + + --- Add a GROUP or OPSGROUP object to the set. + -- **NOTE** that an OPSGROUP is automatically created from the GROUP if it does not exist already. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP group The GROUP which should be added to the set. Can also be given as an #OPSGROUP object. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroup(group) + + local groupname=group:GetName() + + self:Add(groupname, group ) + + return self + end + + --- Add GROUP(s) or OPSGROUP(s) to the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param #string AddGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:AddGroupsByName( AddGroupNames ) + + local AddGroupNamesArray = ( type( AddGroupNames ) == "table" ) and AddGroupNames or { AddGroupNames } + + for AddGroupID, AddGroupName in pairs( AddGroupNamesArray ) do + self:Add(AddGroupName, GROUP:FindByName(AddGroupName)) + end + + return self + end + + --- Remove GROUP(s) or OPSGROUP(s) from the set. + -- @param Core.Set#SET_OPSGROUP self + -- @param Wrapper.Group#GROUP RemoveGroupNames A single name or an array of GROUP names. + -- @return Core.Set#SET_OPSGROUP self + function SET_OPSGROUP:RemoveGroupsByName( RemoveGroupNames ) + + local RemoveGroupNamesArray = ( type( RemoveGroupNames ) == "table" ) and RemoveGroupNames or { RemoveGroupNames } + + for RemoveGroupID, RemoveGroupName in pairs( RemoveGroupNamesArray ) do + self:Remove( RemoveGroupName ) + end + + return self + end + + --- Finds an OPSGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.OpsGroup#OPSGROUP The found OPSGROUP (FLIGHTGROUP, ARMYGROUP or NAVYGROUP) or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindGroup(GroupName) + local GroupFound = self.Set[GroupName] + return GroupFound + end + + --- Finds a FLIGHTGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.FlightGroup#FLIGHTGROUP The found FLIGHTGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindFlightGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Finds a ARMYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.ArmyGroup#ARMYGROUP The found ARMYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindArmyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + + --- Finds a NAVYGROUP based on the group name. + -- @param #SET_OPSGROUP self + -- @param #string GroupName Name of the group. + -- @return Ops.NavyGroup#NAVYGROUP The found NAVYGROUP or `#nil` if the group is not in the set. + function SET_OPSGROUP:FindNavyGroup(GroupName) + local GroupFound = self:FindGroup(GroupName) + return GroupFound + end + + --- Builds a set of groups of coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_OPSGROUP self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCoalitions(Coalitions) + + -- Create an empty set. + if not self.Filter.Coalitions then + self.Filter.Coalitions={} + end + + -- Ensure we got a table. + if type(Coalitions)~="table" then + Coalitions = {Coalitions} + end + + -- Set filter. + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + + return self + end + + + --- Builds a set of groups out of categories. + -- + -- Possible current categories are: + -- + -- * "plane" for fixed wing groups + -- * "helicopter" for rotary wing groups + -- * "ground" for ground groups + -- * "ship" for naval groups + -- + -- @param #SET_OPSGROUP self + -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategories( Categories ) + + if not self.Filter.Categories then + self.Filter.Categories={} + end + + if type(Categories)~="table" then + Categories={Categories} + end + + for CategoryID, Category in pairs( Categories ) do + self.Filter.Categories[Category] = Category + end + + return self + end + + --- Builds a set of groups out of ground category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryGround() + self:FilterCategories("ground") + return self + end + + --- Builds a set of groups out of airplane category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAirplane() + self:FilterCategories("plane") + return self + end + + --- Builds a set of groups out of aicraft category (planes and helicopters). + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryAircraft() + self:FilterCategories({"plane", "helicopter"}) + return self + end + + --- Builds a set of groups out of helicopter category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryHelicopter() + self:FilterCategories("helicopter") + return self + end + + --- Builds a set of groups out of ship category. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCategoryShip() + self:FilterCategories("ship") + return self + end + + --- Builds a set of groups of defined countries. + -- @param #SET_OPSGROUP self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterCountries(Countries) + + -- Create empty table if necessary. + if not self.Filter.Countries then + self.Filter.Countries = {} + end + + -- Ensure input is a table. + if type(Countries)~="table" then + Countries={Countries} + end + + -- Set filter. + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + + return self + end + + + --- Builds a set of groups that contain the given string in their group name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. + -- @param #SET_OPSGROUP self + -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterPrefixes(Prefixes) + + -- Create emtpy table if necessary. + if not self.Filter.GroupPrefixes then + self.Filter.GroupPrefixes={} + end + + -- Ensure we have a table. + if type(Prefixes)~="table" then + Prefixes={Prefixes} + end + + -- Set group prefixes. + for PrefixID, Prefix in pairs(Prefixes) do + self.Filter.GroupPrefixes[Prefix]=Prefix + end + + return self + end + + --- Builds a set of groups that are only active. + -- Only the groups that are active will be included within the set. + -- @param #SET_OPSGROUP self + -- @param #boolean Active (optional) Include only active groups to the set. + -- Include inactive groups if you provide false. + -- @return #SET_OPSGROUP self + -- @usage + -- + -- -- Include only active groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterStart() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- + -- -- Include only active groups to the set of the blue coalition, and filter one time. + -- -- Later, reset to include back inactive groups to the set. + -- GroupSet = SET_OPSGROUP:New():FilterActive():FilterCoalition( "blue" ):FilterOnce() + -- ... logic ... + -- GroupSet = SET_OPSGROUP:New():FilterActive( false ):FilterCoalition( "blue" ):FilterOnce() + -- + function SET_OPSGROUP:FilterActive( Active ) + Active = Active or not ( Active == false ) + self.Filter.Active = Active + return self + end + + + --- Starts the filtering. + -- @param #SET_OPSGROUP self + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:FilterStart() + + if _DATABASE then + self:_FilterStart() + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) + self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + end + + return self + end + + --- Handles the OnDead or OnCrash event for alive groups set. + -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + if Event.IniDCSGroup:getSize() == 1 then -- Only remove if the last unit of the group was destroyed. + self:Remove( ObjectName ) + end + 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! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:AddInDatabase( Event ) + + if Event.IniObjectCategory==1 then + + if not self.Database[Event.IniDCSGroupName] then + self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) + end + + end + + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Handles the Database to check on any event that Object exists in the Database. + -- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! + -- @param #SET_OPSGROUP self + -- @param Core.Event#EVENTDATA Event Event data table. + -- @return #string The name of the GROUP + -- @return #table The GROUP + function SET_OPSGROUP:FindInDatabase(Event) + return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] + end + + --- Iterate the set and call an iterator function for each OPSGROUP object. + -- @param #SET_OPSGROUP self + -- @param #function IteratorFunction The function that will be called for all OPSGROUPs in the set. **NOTE** that the function must have the OPSGROUP as first parameter! + -- @param ... (Optional) arguments passed to the `IteratorFunction`. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:ForEachGroup( IteratorFunction, ... ) + + self:ForEach(IteratorFunction, arg, self:GetSet()) + + return self + end + + --- Check include object. + -- @param #SET_OPSGROUP self + -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:IsIncludeObject(MGroup) + + -- Assume it is and check later if not. + local MGroupInclude=true + + -- Filter active. + if self.Filter.Active~=nil then + + local MGroupActive = false + + if self.Filter.Active==false or (self.Filter.Active==true and MGroup:IsActive()==true) then + MGroupActive = true + end + + MGroupInclude = MGroupInclude and MGroupActive + end + + -- Filter coalitions. + if self.Filter.Coalitions then + + local MGroupCoalition = false + + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==MGroup:GetCoalition() then + MGroupCoalition = true + end + end + + MGroupInclude = MGroupInclude and MGroupCoalition + end + + -- Filter categories. + if self.Filter.Categories then + + local MGroupCategory = false + + for CategoryID, CategoryName in pairs( self.Filter.Categories ) do + if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName]==MGroup:GetCategory() then + MGroupCategory = true + end + end + + MGroupInclude = MGroupInclude and MGroupCategory + end + + -- Filter countries. + if self.Filter.Countries then + local MGroupCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + if country.id[CountryName] == MGroup:GetCountry() then + MGroupCountry = true + end + end + MGroupInclude = MGroupInclude and MGroupCountry + end + + -- Filter "prefixes". + if self.Filter.GroupPrefixes then + + local MGroupPrefix = false + + for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do + if string.find( MGroup:GetName(), GroupPrefix:gsub ("-", "%%-"), 1 ) then --Not sure why "-" is replaced by "%-" ?! + MGroupPrefix = true + end + end + + MGroupInclude = MGroupInclude and MGroupPrefix + end + + return MGroupInclude + end + +end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 7660edc0f..fc82983ac 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1086,15 +1086,10 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - env.info("FF Current waypoint index="..self.currentwp) - env.info("FF Add waypoint after index="..(AfterWaypointWithID or "nil")) - local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - - env.info("FF Add waypoint index="..wpnumber) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 8472b6ad0..ad8146281 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1682,13 +1682,11 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.isAI then if self:IsTransporting() then if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF transporting land at airbase ") local airbase=self.cargoTransport.deployzone:GetAirbase() self:LandAtAirbase(airbase) end elseif self:IsPickingup() then if self.cargoTransport and self.cargoTransport.pickupzone and self.cargoTransport.pickupzone:IsInstanceOf("ZONE_AIRBASE") then - env.info("FF pickingup land at airbase ") local airbase=self.cargoTransport.pickupzone:GetAirbase() self:LandAtAirbase(airbase) end @@ -2959,6 +2957,9 @@ function FLIGHTGROUP:_InitGroup() text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) self:I(self.lid..text) end + + env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 8c78e0ede..4f233faac 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -498,7 +498,8 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group started or stopped turning. self:_CheckTurning() - local freepath=UTILS.NMToMeters(10) + local disttoWP=math.min(self:GetDistanceToWaypoint(), UTILS.NMToMeters(10)) + local freepath=disttoWP -- Only check if not currently turning. if not self:IsTurning() then @@ -506,7 +507,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check free path ahead. freepath=self:_CheckFreePath(freepath, 100) - if freepath<5000 then + if disttoWP>1 and freepath= 5 meters. + if dist<5 then + return + end + local boxwidth=dist*2 local spacex=dist*0.1 local delta=dist/10 -- Create a grid of nodes. We only want nodes of surface type water. - astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, self.Debug) + astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta, self.verbose>10) -- Valid neighbour nodes need to have line of sight. astar:SetValidNeighbourLoS(self.pathCorridor) @@ -1539,7 +1550,9 @@ function NAVYGROUP:_FindPathToNextWaypoint() uid=wp.uid -- Debug: smoke and mark path. - --node.coordinate:MarkToAll(string.format("Path node #%d", i)) + if self.verbose>=10 then + node.coordinate:MarkToAll(string.format("Path node #%d", i)) + end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 20f482f2b..047912831 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -108,6 +108,7 @@ -- -- @field #OPSGROUP.Element carrier Carrier the group is loaded into as cargo. -- @field #OPSGROUP carrierGroup Carrier group transporting this group as cargo. +-- @field #OPSGROUP.MyCarrier mycarrier Carrier group for this group. -- @field #table cargoqueue Table containing cargo groups to be transported. -- @field #table cargoBay Table containing OPSGROUP loaded into this group. -- @field Ops.OpsTransport#OPSTRANSPORT cargoTransport Current cargo transport assignment. @@ -172,6 +173,7 @@ OPSGROUP = { cargoqueue = {}, cargoBay = {}, cargocounter = 1, + mycarrier = {}, } @@ -418,25 +420,18 @@ OPSGROUP.TransportStatus={ } --- Cargo transport data. --- @type OPSGROUP.CargoTransport --- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. --- @field #string status Status of the transport. See @{#OPSGROUP.TransportStatus}. --- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). --- @field #number importance Importance of this transport. Smaller=higher. --- @field #number Tstart Start time in *abs.* seconds. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. --- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. --- @field #OPSGROUP carrierGroup The new carrier group. +-- @type OPSGROUP.MyCarrier +-- @field #OPSGROUP group The carrier group. +-- @field #OPSGROUP.Element element The carrier element. +-- @field #boolean reserved If `true`, the carrier has caro space reserved for me. --- Cargo group data. -- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. --- @field #string status Status of the cargo group. See @{#OPSGROUP.CargoStatus}. --- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. --- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. --- @field #boolean delivered If true, group was delivered. +-- @field #boolean delivered If `true`, group was delivered. +-- @field #OPSGROUP disembarkCarrierGroup Carrier group where the cargo group is directly loaded to. +-- @field #OPSGROUP disembarkCarrierElement Carrier element to which the cargo group is directly loaded to. +-- @field #string status Status of the cargo group. Not used yet. --- OpsGroup version. -- @field #string version @@ -1022,8 +1017,9 @@ function OPSGROUP:GetVec3(UnitName) local vec3=nil --DCS#Vec3 -- First check if this group is loaded into a carrier - if self.carrier and self.carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then - local unit=self.carrier.unit + local carrier=self:_GetMyCarrierElement() + if carrier and carrier.status~=OPSGROUP.ElementStatus.DEAD and self:IsLoaded() then + local unit=carrier.unit if unit and unit:IsAlive()~=nil then vec3=unit:GetVec3() return vec3 @@ -1219,8 +1215,10 @@ end -- @return #OPSGROUP self function OPSGROUP:DespawnUnit(UnitName, Delay, NoEventRemoveUnit) - env.info("FF despawn element .."..tostring(UnitName)) + -- Debug info. + self:T(self.lid.."Despawn element "..tostring(UnitName)) + -- Get element. local element=self:GetElementByName(UnitName) if element then @@ -1652,7 +1650,8 @@ end -- @return #boolean If true, group is boarding. function OPSGROUP:IsBoarding(CarrierGroupName) if CarrierGroupName then - if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname~=CarrierGroupName then return false end end @@ -1665,7 +1664,8 @@ end -- @return #boolean If true, group is loaded. function OPSGROUP:IsLoaded(CarrierGroupName) if CarrierGroupName then - if self.carrierGroup and self.carrierGroup.groupname~=CarrierGroupName then + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname~=CarrierGroupName then return false end end @@ -3007,7 +3007,6 @@ end -- @param #OPSGROUP self -- @return #number Number of unfinished transports in the queue. function OPSGROUP:CountRemainingTransports() - env.info("FF Count remaining transports="..#self.cargoqueue) local N=0 @@ -3015,17 +3014,15 @@ function OPSGROUP:CountRemainingTransports() for _,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT - self:I(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) + -- Debug info. + self:T(self.lid..string.format("Transport status=%s [%s]", transport:GetCarrierTransportStatus(self), transport:GetState())) -- Count not delivered (executing or scheduled) assignments. if transport and transport:GetCarrierTransportStatus(self)==OPSTRANSPORT.Status.SCHEDULED and transport:GetState()~=OPSTRANSPORT.Status.DELIVERED then - - N=N+1 - + N=N+1 end end - env.info("FF Count remaining transports="..N) return N end @@ -4114,8 +4111,6 @@ end -- @param #string To To state. function OPSGROUP:onafterLaserLostLOS(From, Event, To) - --env.info("FF lost LOS") - -- No of sight. self.spot.LOS=false @@ -4123,8 +4118,6 @@ function OPSGROUP:onafterLaserLostLOS(From, Event, To) self.spot.lostLOS=true if self.spot.On then - - --env.info("FF lost LOS ==> pause laser") -- Switch laser off. self:LaserPause() @@ -4142,19 +4135,14 @@ function OPSGROUP:onafterLaserGotLOS(From, Event, To) -- Has line of sight. self.spot.LOS=true - - --env.info("FF Laser Got LOS") if self.spot.lostLOS then -- Did not loose LOS anymore. self.spot.lostLOS=false - - --env.info("FF had lost LOS and regained it") -- Resume laser if currently paused. if self.spot.Paused then - --env.info("FF laser was paused ==> resume") self:LaserResume() end @@ -4205,8 +4193,6 @@ function OPSGROUP:SetLaserTarget(Target) else self.spot.offsetTarget={x=0, 2, z=0} end - - --env.info(string.format("Target offset %.3f", y)) else self:E("WARNING: LASER target is not alive!") @@ -4310,8 +4296,6 @@ function OPSGROUP:_UpdateLaser() -- Check current LOS. local los=self:HasLoS(self.spot.Coordinate, self.spot.element, self.spot.offset) - --env.info(string.format("FF check LOS current=%s previous=%s", tostring(los), tostring(self.spot.LOS))) - if los then -- Got LOS if self.spot.lostLOS then @@ -4425,8 +4409,13 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) local opsgroup=_DATABASE:GetOpsGroup(groupname) if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then for _,element in pairs(opsgroup.elements) do - env.info("FF cargo element dead "..element.name) + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..element.name) + + -- Trigger dead event. opsgroup:ElementDead(element) + end end end @@ -4662,9 +4651,10 @@ function OPSGROUP:_CheckCargoTransport() local state=cargo.opsgroup:GetState() local status=cargo.opsgroup.cargoStatus local name=cargo.opsgroup.groupname - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "N/A" - local carrierelement=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "N/A" - text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carriergroup, carrierelement, tostring(cargo.delivered)) + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n (%d) %s [%s]: %s, carrier=%s(%s), delivered=%s", j, name, state, status, carrierGroupname, carrierElementname, tostring(cargo.delivered)) end end if text~="" then @@ -4689,9 +4679,10 @@ function OPSGROUP:_CheckCargoTransport() local gstatus=cargo.opsgroup:GetState() local cstatus=cargo.opsgroup.cargoStatus local weight=cargo.opsgroup:GetWeightTotal() - local carriername=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" - local carriergroup=cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup.groupname or "none" - text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carriername, carriergroup, tostring(cargo.delivered)) + local carriergroup, carrierelement, reserved=cargo.opsgroup:_GetMyCarrier() + local carrierGroupname=carriergroup and carriergroup.groupname or "none" + local carrierElementname=carrierelement and carrierelement.name or "none" + text=text..string.format("\n- %s (%.1f kg) [%s]: %s, carrier=%s (%s), delivered=%s", name, weight, gstatus, cstatus, carrierElementname, carrierGroupname, tostring(cargo.delivered)) end self:I(self.lid..text) end @@ -4761,7 +4752,10 @@ function OPSGROUP:_CheckCargoTransport() for _,_cargo in pairs(self.cargoTransport.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - if (cargo.opsgroup.carrierGroup and cargo.opsgroup.carrierGroup:GetName()==self:GetName()) and not cargo.delivered then + local carrierGroup=cargo.opsgroup:_GetMyCarrierGroup() + + -- Check that this group is + if (carrierGroup and carrierGroup:GetName()==self:GetName()) and not cargo.delivered then delivered=false break end @@ -4780,15 +4774,6 @@ function OPSGROUP:_CheckCargoTransport() end - -- Loop over cargo queue and check if everything was delivered. - for i=#self.cargoqueue,1,-1 do - local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT - local delivered=self:_CheckDelivered(transport) - if delivered then - --self:Delivered(transport) - end - end - return self end @@ -4825,8 +4810,14 @@ function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) self.cargoBay[CargoGroup.groupname]=nil -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - self:RedWeightCargo(CargoGroup.carrier.name, weight) + local weight=CargoGroup:GetWeightTotal() + + -- Get carrier of group. + local carrier=CargoGroup:_GetMyCarrierElement() + + if carrier then + self:RedWeightCargo(carrier.name, weight) + end return true end @@ -4909,36 +4900,6 @@ function OPSGROUP:_CheckDelivered(CargoTransport) return done end ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! --- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. --- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). --- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. --- @param #string ClockStart Start time in format "HH:MM(:SS)(+D)", e.g. "13:05:30" or "08:30+1". Can also be given as a `#number`, in which case it is interpreted as relative amount in seconds from now on. --- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. --- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. -function OPSGROUP:AddCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) - - -- Create a new cargo transport assignment. - local cargotransport=OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) - - if cargotransport then - - -- Set state to SCHEDULED. - cargotransport.status=OPSGROUP.TransportStatus.SCHEDULED - - --Add to cargo queue - table.insert(self.cargoqueue, cargotransport) - - end - - return cargotransport -end - --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. @@ -4971,134 +4932,6 @@ function OPSGROUP:DelCargoTransport(CargoTransport) return self end ---- Create a cargo transport assignment. --- @param #OPSGROUP self --- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be a single @{Wrapper.Group#GROUP} or @{Ops.OpsGroup#OPSGROUP} object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. This is the zone, where the carrier is going to pickup the cargo. **Important**: only cargo is considered, if it is in this zone when the carrier starts loading! --- @param Core.Zone#ZONE Deployzone Deploy zone. This is the zone, where the carrier is going to drop off the cargo. --- @param #number Prio Priority of this transport assignment. Should be a number between 1 (high prio) and 100 (low prio). --- @param #number Importance Importance of this transport assignment (lower=more important). A transport is only considered, if all more important (if any) transports are done. Default `nil`. --- @param #string ClockStart Start time in format "HH:MM:SS+D", e.g. "13:05:30" or "08:30+1". Can also be passed as a `#number` in which case it is interpreted as relative amount in seconds from now on. --- @param Core.Zone#ZONE Embarkzone (Optional) Zone where the cargo is going to be embarked into the transport. By default is goes to the assigned carrier unit. --- @param Core.Zone#ZONE Disembarkzone (Optional) Zone where the cargo disembarks to (is spawned after unloaded). Default is anywhere in the deploy zone. --- @param #OPSGROUP DisembarkCarrierGroup (Optional) The OPSGROUP where the cargo is directly loaded into. --- @return Ops.OpsTransport#OPSTRANSPORT Cargo transport. -function OPSGROUP:CreateCargoTransport(GroupSet, Pickupzone, Deployzone, Prio, Importance, ClockStart, Embarkzone, Disembarkzone, DisembarkCarrierGroup) - - -- Current mission time. - local Tnow=timer.getAbsTime() - - -- Set start time. Default in 5 sec. - local Tstart=Tnow+5 - if ClockStart and type(ClockStart)=="number" then - Tstart=Tnow+ClockStart - elseif ClockStart and type(ClockStart)=="string" then - Tstart=UTILS.ClockToSeconds(ClockStart) - end - - -- Data structure. - local transport={} --Ops.OpsTransport#OPSTRANSPORT - transport.uid=self.cargocounter - transport.status=OPSGROUP.TransportStatus.PLANNING - transport.pickupzone=Pickupzone - transport.deployzone=Deployzone - transport.embarkzone=Embarkzone or Pickupzone - transport.disembarkzone=Disembarkzone or Deployzone - transport.prio=Prio or 50 - transport.importance=Importance - transport.Tstart=Tstart - transport.carrierGroup=DisembarkCarrierGroup - transport.cargos={} - - -- Check type of GroupSet provided. - if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then - - -- We got a single GROUP or OPSGROUP objectg. - local cargo=self:CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) - - if cargo and self:CanCargo(cargo.opsgroup) then - table.insert(transport.cargos, cargo) - end - - else - - -- We got a SET_GROUP object. - - for _,group in pairs(GroupSet.Set) do - - local cargo=self:CreateCargoGroupData(group, Pickupzone, Deployzone) - - if cargo and self:CanCargo(cargo.opsgroup) then - table.insert(transport.cargos, cargo) - end - - end - end - - -- Debug info. - if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - transport.uid, transport.pickupzone:GetName(), transport.embarkzone:GetName(), transport.deployzone:GetName(), transport.disembarkzone:GetName()) - local Weight=0 - for _,_cargo in pairs(transport.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup - local weight=cargo.opsgroup:GetWeightTotal() - Weight=Weight+weight - text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) - end - text=text..string.format("\nTOTAL: Ncargo=%d, Weight=%.1f kg", #transport.cargos, Weight) - self:I(self.lid..text) - end - - if #transport.cargos>0 then - self.cargocounter=self.cargocounter+1 - return transport - else - self:E(self.lid.."ERROR: Cargo too heavy for this carrier group!") - return nil - end -end - ---- Create a cargo group data structure. --- @param #OPSGROUP self --- @param Wrapper.Group#GROUP group The GROUP object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.CargoGroup Cargo group data. -function OPSGROUP:CreateCargoGroupData(group, Pickupzone, Deployzone) - - local opsgroup=nil - - if group:IsInstanceOf("OPSGROUP") then - opsgroup=group - else - - opsgroup=_DATABASE:GetOpsGroup(group) - - if not opsgroup then - if group:IsAir() then - opsgroup=FLIGHTGROUP:New(group) - elseif group:IsShip() then - opsgroup=NAVYGROUP:New(group) - else - opsgroup=ARMYGROUP:New(group) - end - else - --env.info("FF found opsgroup in createcargo") - end - - end - - local cargo={} --#OPSGROUP.CargoGroup - - cargo.opsgroup=opsgroup - cargo.delivered=false - cargo.status="Unknown" - cargo.pickupzone=Pickupzone - cargo.deployzone=Deployzone - - return cargo -end --- Get total weight of the group including cargo. -- @param #OPSGROUP self @@ -5124,7 +4957,7 @@ end --- Get free cargo bay weight. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. --- @return #number Total weight in kg. +-- @return #number Free cargo bay in kg. function OPSGROUP:GetFreeCargobay(UnitName) local Free=0 @@ -5133,10 +4966,17 @@ function OPSGROUP:GetFreeCargobay(UnitName) if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then local free=element.weightMaxCargo-element.weightCargo + --[[ + for _,_opsgroup in pairs(element.reservedCargos or {}) do + local opsgroup=_opsgroup --#OPSGROUP + free=free-opsgroup:GetWeightTotal() + end + ]] Free=Free+free end end + self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) return Free end @@ -5188,6 +5028,21 @@ function OPSGROUP:GetWeightCargo(UnitName) end end + + local gewicht=0 + for groupname, carriername in pairs(self.cargoBay) do + local element=self:GetElementByName(carriername) + if (UnitName==nil or UnitName==carriername) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then + local opsgroup=_DATABASE:FindOpsGroup(groupname) + if opsgroup then + gewicht=gewicht+opsgroup:GetWeightTotal() + end + end + end + + if gewicht~=weight then + self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) + end return weight end @@ -5247,7 +5102,7 @@ function OPSGROUP:RedWeightCargo(UnitName, Weight) return self end ---- Check if the group can be carrier of a cargo group. +--- Check if the group can *in principle* be carrier of a cargo group. This checks the max cargo capacity of the group but *not* how much cargo is already loaded (if any). -- **Note** that the cargo group *cannot* be split into units, i.e. the largest cargo bay of any element of the group must be able to load the whole cargo group in one piece. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. @@ -5258,11 +5113,14 @@ function OPSGROUP:CanCargo(CargoGroup) local weight=CargoGroup:GetWeightTotal() - for _,element in pairs(self.elements) do - local can=element.weightMaxCargo>=weight - if can then + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + + -- Check that element is not dead and has + if element and element.status~=OPSGROUP.ElementStatus.DEAD and element.weightMaxCargo>=weight then return true end + end end @@ -5278,16 +5136,104 @@ function OPSGROUP:FindCarrierForCargo(CargoGroup) local weight=CargoGroup:GetWeightTotal() - for _,element in pairs(self.elements) do + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + local free=self:GetFreeCargobay(element.name) + if free>=weight then return element + else + self:T3(self.lid..string.format("%s: Weight %d>%d free cargo bay", element.name, weight, free)) end + end return nil end +--- Reserve cargo space for a cargo group. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. +-- @return #OPSGROUP.Element Carrier able to transport the cargo. +function OPSGROUP:ReserveCargoSpace(CargoGroup) + + local element=self:FindCarrierForCargo(CargoGroup) + + if element then + element.reservedCargo=element.reservedCargo or {} + table.insert(element.reservedCargo, CargoGroup) + end + + return nil +end + +--- Set my carrier. +-- @param #OPSGROUP self +-- @param #OPSGROUP CarrierGroup Carrier group. +-- @param #OPSGROUP.Element CarrierElement Carrier element. +-- @param #boolean Reserved If `true`, reserve space for me. +function OPSGROUP:_SetMyCarrier(CarrierGroup, CarrierElement, Reserved) + + self.mycarrier.group=CarrierGroup + self.mycarrier.element=CarrierElement + self.mycarrier.reserved=Reserved + +end + +--- Get my carrier group. +-- @param #OPSGROUP self +-- @return #OPSGROUP Carrier group. +function OPSGROUP:_GetMyCarrierGroup() + if self.mycarrier and self.mycarrier.group then + return self.mycarrier.group + end + return nil +end + +--- Get my carrier element. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Element Carrier element. +function OPSGROUP:_GetMyCarrierElement() + if self.mycarrier and self.mycarrier.element then + return self.mycarrier.element + end + return nil +end + +--- Is my carrier reserved. +-- @param #OPSGROUP self +-- @return #boolean If `true`, space for me was reserved. +function OPSGROUP:_IsMyCarrierReserved() + if self.mycarrier then + return self.mycarrier.reserved + end + return nil +end + + + +--- Get my carrier. +-- @param #OPSGROUP self +-- @return #OPSGROUP Carrier group. +-- @return #OPSGROUP.Element Carrier element. +-- @return #boolean If `true`, space is reserved for me +function OPSGROUP:_GetMyCarrier() + return self.mycarrier.group, self.mycarrier.element, self.mycarrier.reserved +end + + +--- Remove my carrier. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:_RemoveMyCarrier() + self.mycarrier.group=nil + self.mycarrier.element=nil + self.mycarrier.reserved=nil + self.mycarrier={} + return self +end + --- On after "Pickup" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -5403,14 +5349,6 @@ function OPSGROUP:onafterPickup(From, Event, To) end ---- On before "Loading" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function OPSGROUP:onbeforeLoading(From, Event, To) - -end --- On after "Loading" event. -- @param #OPSGROUP self @@ -5485,12 +5423,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - env.info("FF cannot board carrier") + self:T(self.lid.."Cannot board carrier!") end else -- Debug info. - env.info("FF cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) + self:T(self.lid.."Cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) end end @@ -5526,7 +5464,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) -- Carrier element. - local carrier=Carrier or CargoGroup.carrier --#OPSGROUP.Element + local carrier=Carrier or CargoGroup:_GetMyCarrierElement() --#OPSGROUP.Element -- No carrier provided. if not carrier then @@ -5553,8 +5491,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup:ClearWaypoints() -- Set carrier (again). - CargoGroup.carrier=carrier - CargoGroup.carrierGroup=self + CargoGroup:_SetMyCarrier(self, carrier, false) -- Despawn this group. if CargoGroup:IsAlive() then @@ -5576,7 +5513,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoaded(From, Event, To) - env.info("FF loaded") + + -- Debug info. + self:T(self.lid.."Carrier Loaded ==> Transport") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -5691,6 +5630,17 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then + local path=self.cargoTransport:_GetPathTransport() + if path then + + for _,_zone in pairs(path.zones:GetSet()) do + local zone=_zone --Core.Zone#ZONE + local coordinate=zone:GetRandomCoordinate(nil, nil, nil) --TODO: surface type land + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.temp=true + end + + end + -- ARMYGROUP local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true @@ -5699,8 +5649,24 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isNavygroup then + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathTransport() + + if path then + -- Loop over zones + for i,coordinate in pairs(path) do + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) + waypoint.temp=true + uid=waypoint.uid + coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true -- Give cruise command. self:Cruise() @@ -5731,93 +5697,107 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Check that cargo is loaded into this group. -- NOTE: Could be that the element carriing this cargo group is DEAD, which would mean that the cargo group is also DEAD. - if cargo.opsgroup:IsLoaded(self.groupname) and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then + if cargo.opsgroup:IsLoaded(self.groupname) and not cargo.opsgroup:IsDead() then - env.info("FF deploy cargo "..cargo.opsgroup:GetName()) - - -- New carrier group. - local carrierGroup=self.cargoTransport.carrierGroup + -- Disembark to carrier. + local needscarrier=false + local carrier=nil + local carrierGroup=nil - -- Cargo was delivered (somehow). - cargo.delivered=true - - -- Increase number of delivered cargos. - self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 - - if carrierGroup then - - --- - -- Delivered to another carrier group. - --- - - -- Get new carrier. - local carrier=carrierGroup:FindCarrierForCargo(cargo.opsgroup) - - if carrier then - -- Unload from this and directly load into the other carrier. - self:Unload(cargo.opsgroup) - carrierGroup:Load(cargo.opsgroup, carrier) - else - env.info("ERROR: No element of the group can take this cargo!") - end - - elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then - - --- - -- Delivered to a ship via helo or VTOL - --- - - -- - env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") - - else - - --- - -- Delivered to deploy zone - --- - - if self.cargoTransport.disembarkInUtero then - - -- Unload but keep "in utero" (no coordinate provided). - self:Unload(cargo.opsgroup) - - else - - local Coordinate=nil - - if self.cargoTransport.disembarkzone then - -- Random coordinate in disembark zone. - Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() - - else + if self.cargoTransport.disembarkCarriers and #self.cargoTransport.disembarkCarriers>0 then + + needscarrier=true - -- TODO: Optimize with random Vec2 - - -- Create a zone around the carrier. - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - -- Random coordinate/heading in the zone. - Coordinate=zoneCarrier:GetRandomCoordinate(50) - - end - - -- Random heading of the group. - local Heading=math.random(0,359) - - -- Unload to Coordinate. - self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) - - end + carrier, carrierGroup=self.cargoTransport:FindTransferCarrierForCargo(cargo.opsgroup, zone) - -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) - + --TODO: max unloading time if transfer carrier does not arrive in the zone. + end - end + if needscarrier==false or (needscarrier and carrier and carrierGroup) then + + -- Cargo was delivered (somehow). + cargo.delivered=true + + -- Increase number of delivered cargos. + self.cargoTransport.Ndelivered=self.cargoTransport.Ndelivered+1 + + if carrier and carrierGroup then + + --- + -- Delivered to another carrier group. + --- + + -- Unload from this and directly load into the other carrier. + self:Unload(cargo.opsgroup) + carrierGroup:Load(cargo.opsgroup, carrier) + + elseif zone and zone:IsInstanceOf("ZONE_AIRBASE") and zone:GetAirbase():IsShip() then - end + --- + -- Delivered to a ship via helo or VTOL + --- + + -- Issue warning. + env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + --TODO: Dumb into sea. + + else + + --- + -- Delivered to deploy zone + --- + + if self.cargoTransport.disembarkInUtero then + + -- Unload but keep "in utero" (no coordinate provided). + self:Unload(cargo.opsgroup) + + else + + local Coordinate=nil + + if self.cargoTransport.disembarkzone then + + -- Random coordinate in disembark zone. + Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() + + else + + -- TODO: Optimize with random Vec2 + + -- Create a zone around the carrier. + local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + -- Random coordinate/heading in the zone. + Coordinate=zoneCarrier:GetRandomCoordinate(50) + + end + + -- Random heading of the group. + local Heading=math.random(0,359) + + -- Unload to Coordinate. + self:Unload(cargo.opsgroup, Coordinate, self.cargoTransport.disembarkActivation, Heading) + + end + + -- Trigger "Unloaded" event for current cargo transport + self.cargoTransport:Unloaded(cargo.opsgroup) + + + end + + else + self:T(self.lid.."Cargo needs carrier but no carrier is avaiable (yet)!") + end + + else + -- Not loaded or dead + end + + end -- loop over cargos end @@ -5854,6 +5834,8 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Set cargo status. OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + --TODO: Unload flightgroup. Find parking spot etc. + if Coordinate then --- @@ -5899,6 +5881,14 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Respawn group. OpsGroup:_Respawn(0, Template) + + if self:IsNavygroup() then + self.currentwp=1 + NAVYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) + elseif self:IsArmygroup() then + self.currentwp=1 + ARMYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) + end else @@ -6033,6 +6023,14 @@ function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) if self:IsDead() then self:I(self.lid.."Group DEAD ==> Deny Board transition!") return false + elseif CarrierGroup:IsDead() then + self:I(self.lid.."Carrier Group DEAD ==> Deny Board transition!") + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + return false + elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then + self:I(self.lid.."Carrier Element DEAD ==> Deny Board transition!") + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + return false end return true @@ -6052,21 +6050,21 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Set cargo status. self.cargoStatus=OPSGROUP.CargoStatus.BOARDING - -- Set carrier. - self.carrier=Carrier - self.carrierGroup=CarrierGroup + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. + self:_SetMyCarrier(CarrierGroup, Carrier, true) - -- Army or navy group. - local isArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() + -- Army or Navy group. + local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() + local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() -- Check that carrier is standing still. - if isArmyOrNavy and CarrierGroup:IsHolding() or CarrierGroup:IsParking() or CarrierGroup:IsLandedAt() then + if (CarrierIsArmyOrNavy and CarrierGroup:IsHolding()) or (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt()) then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. - local board=self.speedMax>0 and self:IsAlive() and isArmyOrNavy and self.carrierGroup:IsAlive() + local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive() and CarrierGroup:IsAlive() -- Armygroup cannot board ship ==> Load directly. - if self:IsArmygroup() and self.carrierGroup:IsNavygroup() then + if self:IsArmygroup() and CarrierGroup:IsNavygroup() then board=false end @@ -6093,16 +6091,20 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) else + --- + -- Direct load into carrier. + --- + -- Debug info. - self:I(self.lid..string.format("FF board with direct load to carrier %s", self.carrierGroup:GetName())) + self:I(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) -- Trigger Load event. - self.carrierGroup:Load(self) + CarrierGroup:Load(self) end else - env.info("FF Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") + self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") self:__Board(-10, CarrierGroup, Carrier) end @@ -6827,7 +6829,17 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:I(opsgroup.lid..text) -- Trigger PassingWaypoint event. - if waypoint.astar then + if waypoint.temp then + + -- Remove temp waypoint. + opsgroup:RemoveWaypointByID(uid) + + if opsgroup:IsNavygroup() or opsgroup:IsArmygroup() then + --TODO: not sure if this works with FLIGHTGROUPS + opsgroup:Cruise() + end + + elseif waypoint.astar then -- Remove Astar waypoint. opsgroup:RemoveWaypointByID(uid) @@ -6866,8 +6878,6 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsTransporting() then if opsgroup.isFlightgroup then - - env.info("FF passing waypointg in state istransporting ==> land at") -- Land at current pos and wait for 60 min max. opsgroup:LandAt(opsgroup:GetCoordinate(), 60*60) @@ -6880,12 +6890,15 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) elseif opsgroup:IsBoarding() then - if opsgroup.carrierGroup and opsgroup.carrierGroup:IsAlive() then + local carrierGroup=opsgroup:_GetMyCarrierGroup() + local carrier=opsgroup:_GetMyCarrierElement() + + if carrierGroup and carrierGroup:IsAlive() then - if opsgroup.carrier and opsgroup.carrier.unit and opsgroup.carrier.unit:IsAlive() then + if carrier and carrier.unit and carrier.unit:IsAlive() then -- Load group into the carrier. - opsgroup.carrierGroup:Load(opsgroup) + carrierGroup:Load(opsgroup) else opsgroup:E(opsgroup.lid.."ERROR: Group cannot board assigned carrier UNIT as it is NOT alive!") @@ -7651,10 +7664,7 @@ function OPSGROUP:_UpdatePosition() -- Add up travelled distance. self.traveldist=self.traveldist+self.travelds - - -- Debug info. - --env.info(string.format("FF Traveled %.1f m", self.traveldist)) - + end return self diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 6bf39a147..ef21c495a 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -26,9 +26,8 @@ -- @field #string lid Log ID. -- @field #number uid Unique ID of the transport. -- @field #number verbose Verbosity level. --- @field #table cargos Cargos. Each element is a @{#OPSGROUP.Cargo}. +-- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. --- @field #string status Status of the transport. See @{#OPSTRANSPORT.Status}. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. @@ -39,12 +38,13 @@ -- @field Core.Zone#ZONE embarkzone (Optional) Zone where the cargo is supposed to embark. Default is the pickup zone. -- @field Core.Zone#ZONE disembarkzone (Optional) Zone where the cargo is disembarked. Default is the deploy zone. -- @field Core.Zone#ZONE unboardzone (Optional) Zone where the cargo is going to after disembarkment. --- @field Ops.OpsGroup#OPSGROUP carrierGroup The new carrier group. -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. +-- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. +-- @field #table pathsTransport Paths of `#OPSGROUP.Path`. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -64,12 +64,13 @@ OPSTRANSPORT = { cargos = {}, carriers = {}, carrierTransportStatus = {}, - conditionStart = {}, + conditionStart = {}, + pathsTransport = {}, } --- Cargo transport status. -- @type OPSTRANSPORT.Status --- @field #string PLANNING Planning state. +-- @field #string PLANNED Planning state. -- @field #string SCHEDULED Transport is scheduled in the cargo queue. -- @field #string EXECUTING Transport is being executed. -- @field #string DELIVERED Transport was delivered. @@ -80,6 +81,12 @@ OPSTRANSPORT.Status={ DELIVERED="delivered", } +--- Path. +-- @type OPSTRANSPORT.Path +-- @field #table coords Table of coordinates. +-- @field #number radius Radomization radius in meters. Default 0 m. +-- @field #number altitude Altitude in feet AGL. Only for aircraft. + --- Generic mission condition. -- @type OPSTRANSPORT.Condition -- @field #function func Callback function to check for a condition. Should return a #boolean. @@ -115,32 +122,30 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) local self=BASE:Inherit(self, FSM:New()) -- #OPSTRANSPORT -- Increase ID counter. - _OPSTRANSPORTID=_OPSTRANSPORTID+1 + _OPSTRANSPORTID=_OPSTRANSPORTID+1 -- Set some string id for output to DCS.log file. self.lid=string.format("OPSTRANSPORT [UID=%d] | ", _OPSTRANSPORTID) -- Defaults. self.uid=_OPSTRANSPORTID - self.status=OPSTRANSPORT.Status.PLANNING + --self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone self.deployzone=Deployzone self.embarkzone=Pickupzone - self.carrierGroup=nil self.cargos={} self.carriers={} self.Ncargo=0 self.Ncarrier=0 self.Ndelivered=0 - self:SetPriority() self:SetTime() - -- Add cargo groups. + -- Add cargo groups (could also be added later). if GroupSet then - self:AddCargoGroups(GroupSet, Pickupzone, Deployzone) + self:AddCargoGroups(GroupSet) end @@ -152,8 +157,11 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") + + self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") @@ -171,13 +179,13 @@ end -- @param #OPSTRANSPORT self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be transported. Can also be passed as a single GROUP or OPSGROUP object. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) +function OPSTRANSPORT:AddCargoGroups(GroupSet) -- Check type of GroupSet provided. if GroupSet:IsInstanceOf("GROUP") or GroupSet:IsInstanceOf("OPSGROUP") then -- We got a single GROUP or OPSGROUP object. - local cargo=self:_CreateCargoGroupData(GroupSet, Pickupzone, Deployzone) + local cargo=self:_CreateCargoGroupData(GroupSet) if cargo then --and self:CanCargo(cargo.opsgroup) table.insert(self.cargos, cargo) @@ -190,7 +198,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) for _,group in pairs(GroupSet.Set) do - local cargo=self:_CreateCargoGroupData(group, Pickupzone, Deployzone) + local cargo=self:_CreateCargoGroupData(group) if cargo then table.insert(self.cargos, cargo) @@ -202,11 +210,10 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet, Pickupzone, Deployzone) -- Debug info. if self.verbose>=0 then - local text=string.format("Created Cargo Transport (UID=%d) from %s(%s) --> %s(%s)", - self.uid, self.pickupzone:GetName(), self.embarkzone and self.embarkzone:GetName() or "none", self.deployzone:GetName(), self.disembarkzone and self.disembarkzone:GetName() or "none") + local text=string.format("Added cargo groups:") local Weight=0 for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --#OPSGROUP.CargoGroup + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup local weight=cargo.opsgroup:GetWeightTotal() Weight=Weight+weight text=text..string.format("\n- %s [%s] weight=%.1f kg", cargo.opsgroup:GetName(), cargo.opsgroup:GetState(), weight) @@ -250,6 +257,40 @@ function OPSTRANSPORT:SetDisembarkActivation(Active) return self end +--- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP Carriers Carrier set. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetDisembarkCarriers(Carriers) + + self:I(self.lid.."Setting transfer carriers!") + + -- Create table. + self.disembarkCarriers=self.disembarkCarriers or {} + + if Carriers:IsInstanceOf("GROUP") or Carriers:IsInstanceOf("OPSGROUP") then + + local carrier=self:_GetOpsGroupFromObject(Carriers) + if carrier then + table.insert(self.disembarkCarriers, carrier) + end + + elseif Carriers:IsInstanceOf("SET_GROUP") or Carriers:IsInstanceOf("SET_OPSGROUP") then + + for _,object in pairs(Carriers:GetSet()) do + local carrier=self:_GetOpsGroupFromObject(object) + if carrier then + table.insert(self.disembarkCarriers, carrier) + end + end + + else + self:E(self.lid.."ERROR: Carriers must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") + end + + return self +end + --- Set if group remains *in utero* after disembarkment from carrier. Can be used to directly load the group into another carrier. Similar to disembark in late activated state. -- @param #OPSTRANSPORT self @@ -264,21 +305,7 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) return self end ---- Check if an OPS group is assigned as carrier for this transport. --- @param #OPSTRANSPORT self --- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. --- @return #boolean If true, group is an assigned carrier. -function OPSTRANSPORT:IsCarrier(CarrierGroup) - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP - if carrier.groupname==CarrierGroup.groupname then - return true - end - end - - return false -end --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -358,20 +385,76 @@ end -- @param ... Condition function arguments if any. -- @return #OPSTRANSPORT self function OPSTRANSPORT:AddConditionStart(ConditionFunction, ...) + + if ConditionFunction then - local condition={} --#OPSTRANSPORT.Condition - - condition.func=ConditionFunction - condition.arg={} - if arg then - condition.arg=arg + local condition={} --#OPSTRANSPORT.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStart, condition) + end - table.insert(self.conditionStart, condition) - return self end +--- Add path used for transportation from the pickup to the deploy zone. If multiple paths are defined, a random one is chosen. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #number Radius Randomization radius in meters. Default 0 m. +-- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) + + local path={} --#OPSTRANSPORT.Path + path.coords={} + path.radius=Radius or 0 + path.altitude=Altitude + + -- Get route points. + local waypoints=PathGroup:GetTaskRoute() + + for _,wp in pairs(waypoints) do + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + + -- Add path. + table.insert(self.pathsTransport, path) + + return self +end + +--- Get a path for transportation. +-- @param #OPSTRANSPORT self +-- @return #table The path of COORDINATEs. +function OPSTRANSPORT:_GetPathTransport() + + if self.pathsTransport and #self.pathsTransport>0 then + + -- Get a random path for transport. + local path=self.pathsTransport[math.random(#self.pathsTransport)] --#OPSTRANSPORT.Path + + + local coordinates={} + for _,coord in ipairs(path.coords) do + + -- TODO: Add randomization. + + table.insert(coordinates, coord) + end + + return coordinates + end + + return nil +end + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -394,46 +477,22 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) end ---- Create a cargo group data structure. --- @param #OPSTRANSPORT self --- @param Wrapper.Group#GROUP group The GROUP object. --- @param Core.Zone#ZONE Pickupzone Pickup zone. --- @param Core.Zone#ZONE Deployzone Deploy zone. --- @return #OPSGROUP.CargoGroup Cargo group data. -function OPSTRANSPORT:_CreateCargoGroupData(group, Pickupzone, Deployzone) - local opsgroup=nil - - if group:IsInstanceOf("OPSGROUP") then - opsgroup=group - elseif group:IsInstanceOf("GROUP") then - - opsgroup=_DATABASE:GetOpsGroup(group) - - if not opsgroup then - if group:IsAir() then - opsgroup=FLIGHTGROUP:New(group) - elseif group:IsShip() then - opsgroup=NAVYGROUP:New(group) - else - opsgroup=ARMYGROUP:New(group) - end + +--- Check if an OPS group is assigned as carrier for this transport. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Potential carrier OPSGROUP. +-- @return #boolean If true, group is an assigned carrier. +function OPSTRANSPORT:IsCarrier(CarrierGroup) + + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + return true end - - else - self:E(self.lid.."ERROR: Cargo must be a GROUP or OPSGROUP object!") - return nil end - local cargo={} --#OPSGROUP.CargoGroup - - cargo.opsgroup=opsgroup - cargo.delivered=false - cargo.status="Unknown" - cargo.pickupzone=Pickupzone - cargo.deployzone=Deployzone - - return cargo + return false end --- Check if transport is ready to be started. @@ -443,31 +502,49 @@ end -- @param #OPSTRANSPORT self -- @return #boolean If true, mission can be started. function OPSTRANSPORT:IsReadyToGo() + + -- Debug text. + local text=self.lid.."Is ReadyToGo? " + -- Current abs time. local Tnow=timer.getAbsTime() -- Start time did not pass yet. if self.Tstart and Tnowself.Tstop or false then + text=text.."Nope, stop time already passed!" + self:T(text) return false end -- All start conditions true? local startme=self:EvalConditionsAll(self.conditionStart) + -- Nope, not yet. if not startme then + text=text..("No way, at least one start condition is not true!") + self:I(text) return false end - -- We're good to go! + text=text.."Yes!" + self:T(text) return true end +--- Check if all cargo was delivered (or is dead). +-- @param #OPSTRANSPORT self +-- @return #boolean If true, all possible cargo was delivered. +function OPSTRANSPORT:IsDelivered() + return self:is(OPSTRANSPORT.Status.DELIVERED) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Update ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -482,14 +559,16 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() - + -- Info text. local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local name=cargo.opsgroup.carrier and cargo.opsgroup.carrier.name or "none" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name) + local carrier=cargo.opsgroup:_GetMyCarrierElement() + local name=carrier and carrier.name or "none" + local cstate=carrier and carrier.status or "N/A" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s]", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate) end text=text..string.format("\nCarriers:") @@ -504,7 +583,10 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Check if all cargo was delivered (or is dead). self:_CheckDelivered() - self:__Status(-30) + -- Update status again. + if not self:IsDelivered() then + self:__Status(-30) + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -574,39 +656,44 @@ end -- @param #OPSTRANSPORT self function OPSTRANSPORT:_CheckDelivered() - local done=true - local dead=true - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - - if cargo.delivered then - -- This one is delivered. - dead=false - elseif cargo.opsgroup==nil then - -- This one is nil?! - dead=false - elseif cargo.opsgroup:IsDestroyed() then - -- This one was destroyed. - elseif cargo.opsgroup:IsDead() then - -- This one is dead. - dead=false - elseif cargo.opsgroup:IsStopped() then - -- This one is stopped. - dead=false - else - done=false --Someone is not done! - dead=false + -- First check that at least one cargo was added (as we allow to do that later). + if self.Ncargo>0 then + + local done=true + local dead=true + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if cargo.delivered then + -- This one is delivered. + dead=false + elseif cargo.opsgroup==nil then + -- This one is nil?! + dead=false + elseif cargo.opsgroup:IsDestroyed() then + -- This one was destroyed. + elseif cargo.opsgroup:IsDead() then + -- This one is dead. + dead=false + elseif cargo.opsgroup:IsStopped() then + -- This one is stopped. + dead=false + else + done=false --Someone is not done! + dead=false + end + end - - end - - if dead then - --self:CargoDead() - self:I(self.lid.."All cargo DEAD!") - self:Delivered() - elseif done then - self:I(self.lid.."All cargo delivered") - self:Delivered() + + if dead then + --self:CargoDead() + self:I(self.lid.."All cargo DEAD!") + self:Delivered() + elseif done then + self:I(self.lid.."All cargo delivered") + self:Delivered() + end + end end @@ -634,3 +721,92 @@ function OPSTRANSPORT:EvalConditionsAll(Conditions) -- All conditions were true. return true end + + + +--- Find transfer carrier element for cargo group. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CargoGroup The cargo group that needs to be loaded into a carrier unit/element of the carrier group. +-- @param Core.Zone#ZONE Zone (Optional) Zone where the carrier must be in. +-- @return Ops.OpsGroup#OPSGROUP.Element New carrier element for cargo or nil. +-- @return Ops.OpsGroup#OPSGROUP New carrier group for cargo or nil. +function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) + + local carrier=nil --Ops.OpsGroup#OPSGROUP.Element + local carrierGroup=nil --Ops.OpsGroup#OPSGROUP + + --TODO: maybe sort the carriers wrt to largest free cargo bay. Or better smallest free cargo bay that can take the cargo group weight. + + for _,_carrier in pairs(self.disembarkCarriers or {}) do + local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP + + -- Find an element of the group that has enough free space. + carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + + if carrier then + if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then + return carrier, carrierGroup + else + self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + end + else + self:T3(self.lid.."No transfer carrier available!") + end + end + + return nil, nil +end + +--- Create a cargo group data structure. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP group The GROUP or OPSGROUP object. +-- @return Ops.OpsGroup#OPSGROUP.CargoGroup Cargo group data. +function OPSTRANSPORT:_CreateCargoGroupData(group) + + local opsgroup=self:_GetOpsGroupFromObject(group) + + local cargo={} --Ops.OpsGroup#OPSGROUP.CargoGroup + + cargo.opsgroup=opsgroup + cargo.delivered=false + cargo.status="Unknown" + cargo.disembarkCarrierElement=nil + cargo.disembarkCarrierGroup=nil + + return cargo +end + +--- Get an OPSGROUIP +-- @param #OPSTRANSPORT self +-- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP. +-- @return Ops.OpsGroup#OPSGROUP Ops Group. +function OPSTRANSPORT:_GetOpsGroupFromObject(Object) + + local opsgroup=nil + + if Object:IsInstanceOf("OPSGROUP") then + -- We already have an OPSGROUP + opsgroup=Object + elseif Object:IsInstanceOf("GROUP") then + + -- Look into DB and try to find an existing OPSGROUP. + opsgroup=_DATABASE:GetOpsGroup(Object) + + if not opsgroup then + if Object:IsAir() then + opsgroup=FLIGHTGROUP:New(Object) + elseif Object:IsShip() then + opsgroup=NAVYGROUP:New(Object) + else + opsgroup=ARMYGROUP:New(Object) + end + end + + else + self:E(self.lid.."ERROR: Object must be a GROUP or OPSGROUP object!") + return nil + end + + return opsgroup +end + From 884ea866e2d115f39d67670114d1b6ac76b92899 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 28 Feb 2021 23:25:32 +0100 Subject: [PATCH 134/382] AIRBOSS v1.1.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adjusted and (hopefully improved) parameters. - Angled deck 9.0° --> 9.1359° (from DCS config file) USS Stennis: - Deck height 19.00 --> 19.06 meters (from DCS config file) Supercarriers: - Deck height 20.0 -->20.1394 meters (from DCS config file) - stern coordinate 1.0 meters more on starboard side (LUR calls). --- Moose Development/Moose/Ops/Airboss.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 418962fc8..d8e631019 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1705,7 +1705,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.1.5" +AIRBOSS.version="1.1.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -4160,7 +4160,7 @@ function AIRBOSS:_InitStennis() -- Carrier Parameters. self.carrierparam.sterndist =-153 - self.carrierparam.deckheight = 19 + self.carrierparam.deckheight = 19.06 -- Total size of the carrier (approx as rectangle). self.carrierparam.totlength=310 -- Wiki says 332.8 meters overall length. @@ -4168,7 +4168,7 @@ function AIRBOSS:_InitStennis() self.carrierparam.totwidthstarboard=30 -- Landing runway. - self.carrierparam.rwyangle = -9 + self.carrierparam.rwyangle = -9.1359 self.carrierparam.rwylength = 225 self.carrierparam.rwywidth = 20 @@ -4311,7 +4311,7 @@ function AIRBOSS:_InitNimitz() -- Carrier Parameters. self.carrierparam.sterndist =-164 - self.carrierparam.deckheight = 20 + self.carrierparam.deckheight = 20.1494 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\Database\USS_CVN_7X.lua -- Total size of the carrier (approx as rectangle). self.carrierparam.totlength=332.8 -- Wiki says 332.8 meters overall length. @@ -4319,7 +4319,7 @@ function AIRBOSS:_InitNimitz() self.carrierparam.totwidthstarboard=35 -- Landing runway. - self.carrierparam.rwyangle = -9 + self.carrierparam.rwyangle = -9.1359 --DCS World OpenBeta\CoreMods\tech\USS_Nimitz\scripts\USS_Nimitz_RunwaysAndRoutes.lua self.carrierparam.rwylength = 250 self.carrierparam.rwywidth = 25 @@ -10341,7 +10341,7 @@ function AIRBOSS:_GetSternCoord() stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(7, FB+90) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8.5, FB+90) + stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(9.5, FB+90) end -- Set altitude. From 1df7a12587bfcfbbb3a61a29a6937fd0cfbeb712 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 2 Mar 2021 14:28:37 +0100 Subject: [PATCH 135/382] Update Intelligence.lua Finalized Cluster functions, added bits and bobs --- Moose Development/Moose/Ops/Intelligence.lua | 227 ++++++++++++++++--- 1 file changed, 191 insertions(+), 36 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 3c6993761..4080b6e32 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -31,6 +31,7 @@ -- @field #boolean clustermarkers If true, create cluster markers on F10 map. -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. +-- @field #number clusterradius Radius im kilometers in which groups/units are considered to belong to a cluster -- @extends Core.Fsm#FSM --- Top Secret! @@ -46,7 +47,7 @@ -- @field #INTEL INTEL = { ClassName = "INTEL", - verbose = 2, + verbose = 0, lid = nil, alias = nil, filterCategory = {}, @@ -56,6 +57,7 @@ INTEL = { ContactsUnknown = {}, Clusters = {}, clustercounter = 1, + clusterradius = 15, } --- Detected item info. @@ -74,6 +76,8 @@ INTEL = { -- @field #boolean isship -- @field #boolean ishelo -- @field #boolean isgrund +-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact +-- @field #string recce The name of the recce unit that detected this contact --- Cluster info. -- @type INTEL.Cluster @@ -85,11 +89,12 @@ INTEL = { -- @field #number threatlevelAve Average of threat levels. -- @field Core.Point#COORDINATE coordinate Coordinate of the cluster. -- @field Wrapper.Marker#MARKER marker F10 marker. +-- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this cluster --- INTEL class version. -- @field #string version -INTEL.version="0.1.0" +INTEL.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -175,6 +180,8 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:AddTransition("*", "NewContact", "*") -- New contact has been detected. self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more. + self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected. + self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. -- Defaults self:SetForgetTime() @@ -210,10 +217,43 @@ function INTEL:New(DetectionSet, Coalition, Alias) -- @function [parent=#INTEL] __Status -- @param #INTEL self -- @param #number delay Delay in seconds. + + --- On After "NewContact" event. + -- @function [parent=#INTEL] OnAfterNewContact + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Detected contact. + + --- On After "LostContact" event. + -- @function [parent=#INTEL] OnAfterLostContact + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Lost contact. + + --- On After "NewCluster" event. + -- @function [parent=#INTEL] OnAfterNewCluster + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Contact Contact Detected contact. + -- @param #INTEL.Cluster Cluster Detected cluster + + --- On After "LostCluster" event. + -- @function [parent=#INTEL] OnAfterLostCluster + -- @param #INTEL self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #INTEL.Cluster Cluster Lost cluster + -- @param Ops.Auftrag#AUFTRAG Mission The Auftrag connected with this cluster or nil return self end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -307,7 +347,7 @@ function INTEL:SetFilterCategory(Categories) for _,category in pairs(self.filterCategory) do text=text..string.format("%d,", category) end - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -334,7 +374,7 @@ function INTEL:FilterCategoryGroup(GroupCategories) for _,category in pairs(self.filterCategoryGroup) do text=text..string.format("%d,", category) end - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -360,6 +400,40 @@ function INTEL:SetVerbosity(Verbosity) return self end +--- Add a Mission (Auftrag) to a contact for tracking. +-- @param #INTEL self +-- @param #INTEL.Contact Contact The contact +-- @param Ops.Auftrag#AUFTRAG Mission The mission connected with this contact +-- @return #INTEL self +function INTEL:AddMissionToContact(Contact, Mission) + if Mission and Contact then + Contact.mission = Mission + end + return self +end + +--- Add a Mission (Auftrag) to a cluster for tracking. +-- @param #INTEL self +-- @param #INTEL.Cluster Cluster The cluster +-- @param Ops.Auftrag#AUFTRAG Mission The mission connected with this cluster +-- @return #INTEL self +function INTEL:AddMissionToCluster(Cluster, Mission) + if Mission and Cluster then + Cluster.mission = Mission + end + return self +end + +--- Change radius of the Clusters +-- @param #INTEL self +-- @param #number radius The radius of the clusters +-- @return #INTEL self +function INTEL:SetClusterRadius(radius) + local radius = radius or 15 + self.clusterradius = radius + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -398,10 +472,11 @@ function INTEL:onafterStatus(From, Event, To) -- Number of total contacts. local Ncontacts=#self.Contacts + local Nclusters=#self.Clusters -- Short info. if self.verbose>=1 then - local text=string.format("Status %s [Agents=%s]: Contacts=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, #self.ContactsUnknown, #self.ContactsLost) + local text=string.format("Status %s [Agents=%s]: Contacts=%d, Clusters=%d, New=%d, Lost=%d", fsmstate, self.detectionset:CountAlive(), Ncontacts, Nclusters, #self.ContactsUnknown, #self.ContactsLost) self:I(self.lid..text) end @@ -430,7 +505,8 @@ function INTEL:UpdateIntel() -- Set of all detected units. local DetectedUnits={} - + -- Set of which units was detected by which recce + local RecceDetecting = {} -- Loop over all units providing intel. for _,_group in pairs(self.detectionset.Set or {}) do local group=_group --Wrapper.Group#GROUP @@ -441,7 +517,7 @@ function INTEL:UpdateIntel() local recce=_recce --Wrapper.Unit#UNIT -- Get detected units. - self:GetDetectedUnits(recce, DetectedUnits) + self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting) end @@ -498,7 +574,7 @@ function INTEL:UpdateIntel() end end if not keepit then - self:I(self.lid..string.format("Removing unit %s category=%d", unitname, unit:GetCategory())) + self:T(self.lid..string.format("Removing unit %s category=%d", unitname, unit:GetCategory())) table.insert(remove, unitname) end end @@ -511,17 +587,20 @@ function INTEL:UpdateIntel() end -- Create detected groups. - local DetectedGroups={} + local DetectedGroups={} + local RecceGroups={} for unitname,_unit in pairs(DetectedUnits) do local unit=_unit --Wrapper.Unit#UNIT local group=unit:GetGroup() if group then - DetectedGroups[group:GetName()]=group + local groupname = group:GetName() + DetectedGroups[groupname]=group + RecceGroups[groupname]=RecceDetecting[unitname] end end -- Create detected contacts. - self:CreateDetectedItems(DetectedGroups) + self:CreateDetectedItems(DetectedGroups, RecceGroups) -- Paint a picture of the battlefield. if self.clusteranalysis then @@ -537,8 +616,9 @@ end --- Create detected items. -- @param #INTEL self -- @param #table DetectedGroups Table of detected Groups -function INTEL:CreateDetectedItems(DetectedGroups) - +-- @param #table RecceDetecting Table of detecting recce names +function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) + self:F({RecceDetecting=RecceDetecting}) -- Current time. local Tnow=timer.getAbsTime() @@ -578,7 +658,8 @@ function INTEL:CreateDetectedItems(DetectedGroups) item.position=group:GetCoordinate() item.velocity=group:GetVelocityVec3() item.speed=group:GetVelocityMPS() - + item.recce=RecceDetecting[groupname] + self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknonw", item.recce or "unknown")) -- Add contact to table. self:AddContact(item) @@ -611,17 +692,20 @@ end -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self -- @param Wrapper.Unit#UNIT Unit The unit detecting. +-- @param #table DetectedUnits Table of detected units to be filled +-- @param #table RecceDetecting Table of recce per unit to be filled -- @param #boolean DetectVisual (Optional) If *false*, do not include visually detected targets. -- @param #boolean DetectOptical (Optional) If *false*, do not include optically detected targets. -- @param #boolean DetectRadar (Optional) If *false*, do not include targets detected by radar. -- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST. -- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR. -- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link. -function INTEL:GetDetectedUnits(Unit, DetectedUnits, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) +function INTEL:GetDetectedUnits(Unit, DetectedUnits, RecceDetecting, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) -- Get detected DCS units. local detectedtargets=Unit:GetDetectedTargets(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) - + local reccename = Unit:GetName() + for DetectionObjectID, Detection in pairs(detectedtargets or {}) do local DetectedObject=Detection.object -- DCS#Object @@ -634,7 +718,8 @@ function INTEL:GetDetectedUnits(Unit, DetectedUnits, DetectVisual, DetectOptical local unitname=unit:GetName() DetectedUnits[unitname]=unit - + RecceDetecting[unitname]=reccename + self:T(string.format("Unit %s detect by %s", unitname, reccename)) end end end @@ -652,7 +737,7 @@ end -- @param #string To To state. -- @param #INTEL.Contact Contact Detected contact. function INTEL:onafterNewContact(From, Event, To, Contact) - self:I(self.lid..string.format("NEW contact %s", Contact.groupname)) + self:F(self.lid..string.format("NEW contact %s", Contact.groupname)) table.insert(self.ContactsUnknown, Contact) end @@ -663,10 +748,37 @@ end -- @param #string To To state. -- @param #INTEL.Contact Contact Detected contact. function INTEL:onafterLostContact(From, Event, To, Contact) - self:I(self.lid..string.format("LOST contact %s", Contact.groupname)) + self:F(self.lid..string.format("LOST contact %s", Contact.groupname)) table.insert(self.ContactsLost, Contact) end +--- On after "NewCluster" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.Contact Contact Detected contact. +-- @param #INTEL.Cluster Cluster Detected cluster +function INTEL:onafterNewCluster(From, Event, To, Contact, Cluster) + self:F(self.lid..string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)) +end + +--- On after "LostCluster" event. +-- @param #INTEL self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #INTEL.Cluster Cluster Lost cluster +-- @param Ops.Auftrag#AUFTRAG Mission The Auftrag connected with this cluster or nil +function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission) + local text = self.lid..string.format("LOST cluster %d", Cluster.index) + if Mission then + local mission=Mission --Ops.Auftrag#AUFTRAG + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + end + self:T(text) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -761,11 +873,27 @@ function INTEL:PaintPicture() self:RemoveContactFromCluster(contact, cluster) end end - + -- clean up cluster table + local ClusterSet = {} + for _i,_cluster in pairs(self.Clusters) do + if (_cluster.size > 0) and (self:ClusterCountUnits(_cluster) > 0) then + table.insert(ClusterSet,_cluster) + else + local mission = _cluster.mission or nil + local marker = _cluster.marker + if marker then + marker:Remove() + end + self:LostCluster(_cluster, mission) + end + end + self.Clusters = ClusterSet + -- update positions + self:_UpdateClusterPositions() for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact - + self:T(string.format("Paint Picture: checking for %s",contact.groupname)) -- Check if this contact is in any cluster. local isincluster=self:CheckContactInClusters(contact) @@ -773,7 +901,7 @@ function INTEL:PaintPicture() local currentcluster=self:GetClusterOfContact(contact) if currentcluster then - + --self:I(string.format("Paint Picture: %s has current cluster",contact.groupname)) --- -- Contact is currently part of a cluster. --- @@ -781,8 +909,8 @@ function INTEL:PaintPicture() -- Check if the contact is still connected to the cluster. local isconnected=self:IsContactConnectedToCluster(contact, currentcluster) - if not isconnected then - + if (not isconnected) and (currentcluster.size > 1) then + --self:I(string.format("Paint Picture: %s has LOST current cluster",contact.groupname)) local cluster=self:IsContactPartOfAnyClusters(contact) if cluster then @@ -791,6 +919,7 @@ function INTEL:PaintPicture() local newcluster=self:CreateCluster(contact.position) self:AddContactToCluster(contact, newcluster) + self:NewCluster(contact, newcluster) end end @@ -801,7 +930,7 @@ function INTEL:PaintPicture() --- -- Contact is not in any cluster yet. --- - + --self:I(string.format("Paint Picture: %s has NO current cluster",contact.groupname)) local cluster=self:IsContactPartOfAnyClusters(contact) if cluster then @@ -810,6 +939,7 @@ function INTEL:PaintPicture() local newcluster=self:CreateCluster(contact.position) self:AddContactToCluster(contact, newcluster) + self:NewCluster(contact, newcluster) end end @@ -819,13 +949,13 @@ function INTEL:PaintPicture() -- Update F10 marker text if cluster has changed. - if self.clustermarkers then --honor markersettings + if self.clustermarkers then for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - + local coordinate=self:GetClusterCoordinate(cluster) - - + + -- Update F10 marker. self:UpdateClusterMarker(cluster) end @@ -986,9 +1116,11 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) if Contact.groupname~=contact.groupname then - local dist=Contact.position:Get2DDistance(contact.position) + --local dist=Contact.position:Get2DDistance(contact.position) + local dist=Contact.position:DistanceFromPointVec2(contact.position) - if dist<10*1000 then + local radius = self.clusterradius or 15 + if dist1000 then return true @@ -1083,6 +1216,27 @@ function INTEL:CheckClusterCoordinateChanged(cluster, coordinate) end +--- Update coordinates of the known clusters. +-- @param #INTEL self +function INTEL:_UpdateClusterPositions() + for _,_cluster in pairs (self.Clusters) do + local coord = self:GetClusterCoordinate(_cluster) + _cluster.coordinate = coord + self:T(self.lid..string.format("Cluster size: %s", _cluster.size)) + end +end + +--- Count number of units in cluster +-- @param #INTEL self +-- @param #INTEL.Cluster Cluster The cluster +-- @return #number unitcount +function INTEL:ClusterCountUnits(Cluster) + local unitcount = 0 + for _,_group in pairs (Cluster.Contacts) do -- get Wrapper.GROUP#GROUP _group + unitcount = unitcount + _group.group:CountAliveUnits() + end + return unitcount +end --- Update cluster F10 marker. -- @param #INTEL self @@ -1091,7 +1245,8 @@ end function INTEL:UpdateClusterMarker(cluster) -- Create a marker. - local text=string.format("Cluster #%d. Size %d, TLsum=%d", cluster.index, cluster.size, cluster.threatlevelSum) + local unitcount = self:ClusterCountUnits(cluster) + local text=string.format("Cluster #%d. Size %d, Units %d, TLsum=%d", cluster.index, cluster.size, unitcount, cluster.threatlevelSum) if not cluster.marker then cluster.marker=MARKER:New(cluster.coordinate, text):ToAll() From 3b2cbea1c43753825d2b8c1d82b51e00eeb5004f Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Mar 2021 21:12:51 +0100 Subject: [PATCH 136/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 047912831..c365e03cc 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -179,7 +179,6 @@ OPSGROUP = { --- OPS group element. -- @type OPSGROUP.Element --- -- @field #string name Name of the element, i.e. the unit. -- @field #string status The element status. See @{#OPSGROUP.ElementStatus}. -- @field Wrapper.Unit#UNIT unit The UNIT object. @@ -187,6 +186,10 @@ OPSGROUP = { -- @field DCS#Unit DCSunit The DCS unit object. -- @field #boolean ai If true, element is AI. -- @field #string skill Skill level. +-- +-- @field Core.Zone#ZONE zoneBoundingbox Bounding box zone of the +-- @field Core.Zone#ZONE zoneLoad Loading zone. +-- @field Core.Zone#ZONE zoneUnload Unloading zone. -- -- @field #string typename Type name. -- @field #number category Aircraft category. From 3c6089884edfa7af0265d2ec7553e5a7a4d4d9ae Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 2 Mar 2021 21:19:30 +0100 Subject: [PATCH 137/382] Update Airboss.lua - Added MP wire correction function. --- Moose Development/Moose/Ops/Airboss.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index d8e631019..12370b7a7 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -2641,6 +2641,15 @@ function AIRBOSS:SetRecoveryTurnTime(interval) return self end +--- Set multiplayer environment wire correction. +-- @param #AIRBOSS self +-- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @return #AIRBOSS self +function AIRBOSS:SetMPWireCorrection(Dcorr) + self.mpWireCorrection=Dcorr or 8.7 + return self +end + --- Set time interval for updating queues and other stuff. -- @param #AIRBOSS self -- @param #number interval Time interval in seconds. Default 30 sec. @@ -10371,6 +10380,11 @@ function AIRBOSS:_GetWire(Lcoord, dc) -- Corrected landing distance wrt to stern. Landing distance needs to be reduced due to delayed landing event for human players. local d=Ldist-dc + + -- Multiplayer wire correction. + if self.mpWireCorrection then + d=d-self.mpWireCorrection + end -- Shift wires from stern to their correct position. local w1=self.carrierparam.wire1 From 187643f6aeedc9faa0e4be61fa27fd613eeab17d Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Tue, 2 Mar 2021 13:36:54 -0800 Subject: [PATCH 138/382] Groove time additions Changes on screen description of groove times to match NATOPs grading NESA LIG Changes unicorn groove time from 16-18secs to 15-18.99 seconds. No change to grading. Reference GitHub issues 1445 &1446. --- Moose Development/Moose/Ops/Airboss.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1ecc528dd..7cdf8a06d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,6 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] +-- * [T-45C Goshawk (VNAO)(Player & AI)] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -12044,15 +12045,15 @@ function AIRBOSS:_EvalGrooveTime(playerData) local grade="" if t<9 then - grade="--" - elseif t<12 then - grade="(OK)" - elseif t<22 then - grade="OK" + grade="_NESA_" + elseif t<15 then + grade="NESA" + elseif t<19 then + grade="OK Groove" elseif t<=24 then - grade="(OK)" + grade="(LIG)" else - grade="--" + grade="LIG" end -- The unicorn! @@ -12093,7 +12094,7 @@ function AIRBOSS:_LSOgrade(playerData) -- Groove time 16-18 sec for a unicorn. local Tgroove=playerData.Tgroove - local TgrooveUnicorn=Tgroove and (Tgroove>=16.0 and Tgroove<=18.0) or false + local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false local grade local points From f4a3f6d43370dbecf7b91ce23d8115eb02c46e61 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Mar 2021 00:03:39 +0100 Subject: [PATCH 139/382] OPS --- Moose Development/Moose/Ops/OpsGroup.lua | 56 +++++++++++++++++++ Moose Development/Moose/Utilities/Utils.lua | 62 +++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c365e03cc..60622ee81 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5772,6 +5772,19 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Create a zone around the carrier. local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) + + + local d={} + d.p1={x=vec2.x-l/2, y=vec2.y-w/2} --DCS#Vec2 + d.p2={x=vec2.x-l/2, y=vec2.y+w/2} --DCS#Vec2 + d.p3={x=d2.x+20, y=d2.y+20} + d.p4={x=d1.x+20, y=d1.y+20} + + for _,_p in pairs(d) do + local p=_p --#DCSVec2 + end + + local zoneCarrier=ZONE_POLYGON_BASE:New("Carrier", {d1, d2, d3, d4}) -- Random coordinate/heading in the zone. Coordinate=zoneCarrier:GetRandomCoordinate(50) @@ -7978,6 +7991,48 @@ function OPSGROUP:GetElementByName(unitname) return nil end +--- Get the bounding box of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementBoundingBox(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + local l=element.length + local w=element.width + + local heading=element.unit:GetHeading() + + env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + + local vec2=self:GetVec2(element.name) + + -- Set of + local b={} + b[1]={y=l/2, x=-w/2} --DCS#Vec2 + b[2]={y=l/2, x=w/2} --DCS#Vec2 + b[3]={y=-l/2, x=w/2} --DCS#Vec2 + b[4]={y=-l/2, x=-w/2} --DCS#Vec2 + + for i,p in pairs(b) do + b[i]=UTILS.Vec2Rotate2D(p, heading) + end + + local d=UTILS.Vec2Norm(vec2) + local h=UTILS.Vec2Hdg(vec2) + for i,p in pairs(b) do + --b[i]=UTILS.Vec2Translate(p, d, h) + end + + return ZONE_POLYGON_BASE:New(element.name, b) + end + + return nil +end + --- Get the first element of a group, which is alive. -- @param #OPSGROUP self -- @return #OPSGROUP.Element The element or `#nil` if no element is alive any more. @@ -7995,6 +8050,7 @@ function OPSGROUP:GetElementAlive() return nil end + --- Get number of elements alive. -- @param #OPSGROUP self -- @param #string status (Optional) Only count number, which are in a special status. diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index adb9cea66..bc5a1a352 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -931,6 +931,15 @@ function UTILS.VecDot(a, b) return a.x*b.x + a.y*b.y + a.z*b.z end +--- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. +-- @return #number Scalar product of the two vectors a*b. +function UTILS.Vec2Dot(a, b) + return a.x*b.x + a.y*b.y +end + + --- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @return #number Norm of the vector. @@ -938,6 +947,13 @@ function UTILS.VecNorm(a) return math.sqrt(UTILS.VecDot(a, a)) end +--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @return #number Norm of the vector. +function UTILS.Vec2Norm(a) + return math.sqrt(UTILS.Vec2Dot(a, a)) +end + --- Calculate the distance between two 2D vectors. -- @param DCS#Vec2 a Vector in 3D with x, y components. -- @param DCS#Vec2 b Vector in 3D with x, y components. @@ -1020,6 +1036,17 @@ function UTILS.VecHdg(a) return h end +--- Calculate "heading" of a 2D vector in the X-Y plane. +-- @param DCS#Vec2 a Vector in "D with x, y components. +-- @return #number Heading in degrees in [0,360). +function UTILS.Vec2Hdg(a) + local h=math.deg(math.atan2(a.y, a.x)) + if h<0 then + h=h+360 + end + return h +end + --- Calculate the difference between two "heading", i.e. angles in [0,360) deg. -- @param #number h1 Heading one. -- @param #number h2 Heading two. @@ -1056,6 +1083,22 @@ function UTILS.VecTranslate(a, distance, angle) return {x=TX, y=a.y, z=TY} end +--- Translate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number distance The distance to translate. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Translated vector. +function UTILS.Vec2Translate(a, distance, angle) + + local SX = a.x + local SY = a.y + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY + + return {x=TX, y=TY} +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -1076,6 +1119,25 @@ function UTILS.Rotate2D(a, angle) return A end +--- Rotate 2D vector in the 2D (x,z) plane. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec2 Vector rotated in the (x,y) plane. +function UTILS.Vec2Rotate2D(a, angle) + + local phi=math.rad(angle) + + local x=a.y + local y=a.x + + local Z=x*math.cos(phi)-y*math.sin(phi) + local X=x*math.sin(phi)+y*math.cos(phi) + + local A={x=X, y=Z} + + return A +end + --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". From b0e3201d3cc22e2da638ea77b6abdc5eece12a59 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 4 Mar 2021 11:13:30 +0100 Subject: [PATCH 140/382] Update Intelligence.lua Added Docu --- Moose Development/Moose/Ops/Intelligence.lua | 58 ++++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 4080b6e32..ca95135eb 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1,8 +1,11 @@ --- **Ops** - Office of Military Intelligence. -- --- **Main Features:** +-- ## Main Features: -- --- * Stuff +-- * Detect and track contacts consistently +-- * Detect and track clusters of contacts consistently +-- * Use FSM events to link functionality into your scripts +-- * Easy setup -- -- === -- @@ -41,9 +44,42 @@ -- ![Banner Image](..\Presentations\CarrierAirWing\INTEL_Main.jpg) -- -- # The INTEL Concept +-- +-- * Lightweight replacement for @{Functional.Detection#DETECTION} +-- * Detect and track contacts consistently +-- * Detect and track clusters of contacts consistently +-- * Once detected and still alive, planes will be tracked 10 minutes, helicopters 20 minutes, ships and trains 1 hour, ground units 2 hours +-- * Use FSM events to link functionality into your scripts +-- +-- # Basic Usage -- --- --- +-- ## set up a detection SET_GROUP +-- +-- `Red_DetectionSetGroup = SET_GROUP:New()` +-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )` +-- `Red_DetectionSetGroup:FilterOnce()` +-- +-- ## New Intel type detection for the red side, logname "KGB" +-- +-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")` +-- `RedIntel:SetClusterAnalysis(true,true)` +-- `RedIntel:SetVerbosity(2)` +-- `RedIntel:Start()` +-- +-- ## Hook into new contacts found +-- +-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)` +-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` +-- +-- ## And/or new clusters found +-- +-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)` +-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` +-- -- @field #INTEL INTEL = { ClassName = "INTEL", @@ -57,7 +93,7 @@ INTEL = { ContactsUnknown = {}, Clusters = {}, clustercounter = 1, - clusterradius = 15, + clusterradius = 10, } --- Detected item info. @@ -314,7 +350,7 @@ function INTEL:RemoveRejectZone(RejectZone) return self end ---- Set forget contacts time interval. +--- Set forget contacts time interval. For unknown contacts only. -- Previously known contacts that are not detected any more, are "lost" after this time. -- This avoids fast oscillations between a contact being detected and undetected. -- @param #INTEL self @@ -429,7 +465,7 @@ end -- @param #number radius The radius of the clusters -- @return #INTEL self function INTEL:SetClusterRadius(radius) - local radius = radius or 15 + local radius = radius or 10 self.clusterradius = radius return self end @@ -1046,7 +1082,7 @@ function INTEL:CalcClusterThreatlevelSum(cluster) threatlevel=threatlevel+contact.threatlevel end - + cluster.threatlevelSum = threatlevel return threatlevel end @@ -1058,7 +1094,7 @@ function INTEL:CalcClusterThreatlevelAverage(cluster) local threatlevel=self:CalcClusterThreatlevelSum(cluster) threatlevel=threatlevel/cluster.size - + cluster.threatlevelAve = threatlevel return threatlevel end @@ -1078,7 +1114,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster) end end - + cluster.threatlevelMax = threatlevel return threatlevel end @@ -1119,7 +1155,7 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) --local dist=Contact.position:Get2DDistance(contact.position) local dist=Contact.position:DistanceFromPointVec2(contact.position) - local radius = self.clusterradius or 15 + local radius = self.clusterradius or 10 if dist Date: Thu, 4 Mar 2021 16:15:57 +0100 Subject: [PATCH 141/382] Update Controllable.lua 2 functions missing though they are in the doc header --- .../Moose/Wrapper/Controllable.lua | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 4c527e997..059240c39 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3687,3 +3687,57 @@ function CONTROLLABLE:OptionAAAttackRange(range) end return nil end + +--- Defines the range at which a GROUND unit/group is allowed to use its weapons automatically. +-- @param #CONTROLLABLE self +-- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionEngageRange(EngageRange) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + EngageRange=EngageRange or 100 + if EngageRange < 0 or EngageRange > 100 then + EngageRange = 100 + end + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) + end + end + return self + end + return nil +end + +--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving. +-- @param #CONTROLLABLE self +-- @param #number speed Speed of the controllable, default 20 +-- @param #number radius Radius of the relocation zone, default 500 +-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true +-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false +function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) + self:F2( { self.ControllableName } ) + + local _coord = self:GetCoordinate() + local _radius = radius or 500 + local _speed = speed or 20 + local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) + local _onroad = onroad or true + local _grptsk = {} + local _candoroad = false + local _shortcut = shortcut or false + + -- create a DCS Task an push it on the group + -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) + if onroad then + _grptsk, _candoroad = self:TaskGroundOnRoad(_tocoord,_speed,"Off Road",_shortcut) + self:Route(_grptsk,5) + else + self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") + end + + return self +end From 56e2b06e9dd9a644cb978d4a051626806322af77 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 7 Mar 2021 23:47:29 +0100 Subject: [PATCH 142/382] OPS Cargo --- Moose Development/Moose/Core/Zone.lua | 28 +- Moose Development/Moose/Ops/OpsGroup.lua | 371 ++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 10 +- .../Moose/Wrapper/Positionable.lua | 33 +- 5 files changed, 332 insertions(+), 112 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 9fd532dbd..c4b0fd348 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1728,26 +1728,28 @@ end -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() - self:F2() - --- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - local Vec2Found = false - local Vec2 + -- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... + + -- Get the bounding square. local BS = self:GetBoundingSquare() - self:T2( BS ) + local Nmax=1000 ; local n=0 + while n0 then @@ -1451,13 +1451,136 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) local unit=element.unit if unit and unit:IsAlive() then - unit:Explode(ExplosionPower) + unit:Explode(ExplosionPower or 100) end end end + return self end +--- Set that this carrier is an all aspect loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderAllAspect(Length, Width) + self.carrierLoader.type="front" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a front loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderFront(Length, Width) + self.carrierLoader.type="front" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a back loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderBack(Length, Width) + self.carrierLoader.type="back" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a starboard (right side) loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderStarboard(Length, Width) + self.carrierLoader.type="right" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + +--- Set that this carrier is a port (left side) loader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierLoaderPort(Length, Width) + self.carrierLoader.type="left" + self.carrierLoader.length=Length or 50 + self.carrierLoader.width=Width or 20 + return self +end + + +--- Set that this carrier is an all aspect unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderAllAspect(Length, Width) + self.carrierUnloader.type="front" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a front unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderFront(Length, Width) + self.carrierUnloader.type="front" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a back unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderBack(Length, Width) + self.carrierUnloader.type="back" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a starboard (right side) unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderStarboard(Length, Width) + self.carrierUnloader.type="right" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + +--- Set that this carrier is a port (left side) unloader. +-- @param #OPSGROUP self +-- @param #number Length Length of loading zone in meters. Default 50 m. +-- @param #number Width Width of loading zone in meters. Default 20 m. +-- @return #OPSGROUP self +function OPSGROUP:SetCarrierUnloaderPort(Length, Width) + self.carrierUnloader.type="left" + self.carrierUnloader.length=Length or 50 + self.carrierUnloader.width=Width or 20 + return self +end + + --- Check if this is a FLIGHTGROUP. -- @param #OPSGROUP self -- @return #boolean If true, this is an airplane or helo group. @@ -5767,27 +5890,12 @@ function OPSGROUP:onafterUnloading(From, Event, To) Coordinate=self.cargoTransport.disembarkzone:GetRandomCoordinate() else - - -- TODO: Optimize with random Vec2 - - -- Create a zone around the carrier. - local zoneCarrier=ZONE_RADIUS:New("Carrier", self:GetVec2(), 100) - - local d={} - d.p1={x=vec2.x-l/2, y=vec2.y-w/2} --DCS#Vec2 - d.p2={x=vec2.x-l/2, y=vec2.y+w/2} --DCS#Vec2 - d.p3={x=d2.x+20, y=d2.y+20} - d.p4={x=d1.x+20, y=d1.y+20} - - for _,_p in pairs(d) do - local p=_p --#DCSVec2 - end - - local zoneCarrier=ZONE_POLYGON_BASE:New("Carrier", {d1, d2, d3, d4}) + -- Get random point in disembark zone. + local zoneCarrier=self:GetElementZoneUnload(cargo.opsgroup:_GetMyCarrierElement().name) -- Random coordinate/heading in the zone. - Coordinate=zoneCarrier:GetRandomCoordinate(50) + Coordinate=zoneCarrier:GetRandomCoordinate() end @@ -5800,8 +5908,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) end -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) - + self.cargoTransport:Unloaded(cargo.opsgroup) end @@ -5898,13 +6005,16 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Respawn group. OpsGroup:_Respawn(0, Template) - if self:IsNavygroup() then - self.currentwp=1 - NAVYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) - elseif self:IsArmygroup() then - self.currentwp=1 - ARMYGROUP.AddWaypoint(self, Coordinate, nil, nil, nil, false) - end + -- Add current waypoint. These have been cleard on loading. + if OpsGroup:IsNavygroup() then + OpsGroup.currentwp=1 + OpsGroup.passedfinalwp=true + NAVYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) + elseif OpsGroup:IsArmygroup() then + OpsGroup.currentwp=1 + OpsGroup.passedfinalwp=true + ARMYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) + end else @@ -5919,11 +6029,10 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated end -- Trigger "Disembarked" event. - OpsGroup:Disembarked(OpsGroup.carrierGroup, OpsGroup.carrier) + OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(), OpsGroup:_GetMyCarrierElement()) - -- No carrier any more. - OpsGroup.carrier=nil - OpsGroup.carrierGroup=nil + -- Remove my carrier. + OpsGroup:_RemoveMyCarrier() end @@ -5966,11 +6075,8 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The cargo transport assignment. function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) - - -- Set cargo status. - CargoTransport.status=OPSGROUP.TransportStatus.DELIVERED -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then @@ -7995,44 +8101,173 @@ end -- @param #OPSGROUP self -- @param #string UnitName Name of unit. -- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. -function OPSGROUP:GetElementBoundingBox(UnitName) +function OPSGROUP:GetElementZoneBoundingBox(UnitName) local element=self:GetElementByName(UnitName) if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + -- Create a new zone if necessary. + element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box", {}) local l=element.length local w=element.width - local heading=element.unit:GetHeading() + local X=self:GetOrientationX(element.name) + local heading=math.deg(math.atan2(X.z, X.x)) env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) - - local vec2=self:GetVec2(element.name) - - -- Set of + + -- Set of edges facing "North" at the origin of the map. local b={} - b[1]={y=l/2, x=-w/2} --DCS#Vec2 - b[2]={y=l/2, x=w/2} --DCS#Vec2 - b[3]={y=-l/2, x=w/2} --DCS#Vec2 - b[4]={y=-l/2, x=-w/2} --DCS#Vec2 + b[1]={x=l/2, y=-w/2} --DCS#Vec2 + b[2]={x=l/2, y=w/2} --DCS#Vec2 + b[3]={x=-l/2, y=w/2} --DCS#Vec2 + b[4]={x=-l/2, y=-w/2} --DCS#Vec2 + -- Rotate box to match current heading of the unit. for i,p in pairs(b) do b[i]=UTILS.Vec2Rotate2D(p, heading) end - + + -- Translate the zone to the positon of the unit. + local vec2=self:GetVec2(element.name) local d=UTILS.Vec2Norm(vec2) local h=UTILS.Vec2Hdg(vec2) for i,p in pairs(b) do - --b[i]=UTILS.Vec2Translate(p, d, h) + b[i]=UTILS.Vec2Translate(p, d, h) end + + -- Update existing zone. + element.zoneBoundingbox:UpdateFromVec2(b) - return ZONE_POLYGON_BASE:New(element.name, b) + return element.zoneBoundingbox end return nil end +--- Get the loading zone of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementZoneLoad(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + element.zoneLoad=element.zoneLoad or ZONE_POLYGON_BASE:New(element.name.." Zone Load", {}) + + self:_GetElementZoneLoader(element, element.zoneLoad, self.carrierLoader) + + return element.zoneLoad + end + + return nil +end + +--- Get the unloading zone of the element. +-- @param #OPSGROUP self +-- @param #string UnitName Name of unit. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:GetElementZoneUnload(UnitName) + + local element=self:GetElementByName(UnitName) + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + + element.zoneUnload=element.zoneUnload or ZONE_POLYGON_BASE:New(element.name.." Zone Unload", {}) + + self:_GetElementZoneLoader(element, element.zoneUnload, self.carrierUnloader) + + return element.zoneUnload + end + + return nil +end + +--- Get/update the (un-)loading zone of the element. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Element Element Element. +-- @param Core.Zone#ZONE_POLYGON Zone The zone. +-- @param #OPSGROUP.CarrierLoader Loader Loader parameters. +-- @return Core.Zone#ZONE_POLYGON Bounding box polygon zone. +function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) + + if Element.status~=OPSGROUP.ElementStatus.DEAD then + + local l=Element.length + local w=Element.width + + -- Orientation 3D vector where the "nose" is pointing. + local X=self:GetOrientationX(Element.name) + + -- Heading in deg. + local heading=math.deg(math.atan2(X.z, X.x)) + + env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + + -- Bounding box at the origin of the map facing "North". + local b={} + + -- Create polygon rectangles. + if Loader.type:lower()=="front" then + table.insert(b, {x= l/2, y=-Loader.width/2}) -- left, low + table.insert(b, {x= l/2+Loader.length, y=-Loader.width/2}) -- left, up + table.insert(b, {x= l/2+Loader.length, y= Loader.width/2}) -- right, up + table.insert(b, {x= l/2, y= Loader.width/2}) -- right, low + elseif Loader.type:lower()=="back" then + table.insert(b, {x=-l/2, y=-Loader.width/2}) -- left, low + table.insert(b, {x=-l/2-Loader.length, y=-Loader.width/2}) -- left, up + table.insert(b, {x=-l/2-Loader.length, y= Loader.width/2}) -- right, up + table.insert(b, {x=-l/2, y= Loader.width/2}) -- right, low + elseif Loader.type:lower()=="left" then + table.insert(b, {x= Loader.length/2, y= -w/2}) -- right, up + table.insert(b, {x= Loader.length/2, y= -w/2-Loader.width}) -- left, up + table.insert(b, {x=-Loader.length/2, y= -w/2-Loader.width}) -- left, down + table.insert(b, {x=-Loader.length/2, y= -w/2}) -- right, down + elseif Loader.type:lower()=="right" then + table.insert(b, {x= Loader.length/2, y= w/2}) -- right, up + table.insert(b, {x= Loader.length/2, y= w/2+Loader.width}) -- left, up + table.insert(b, {x=-Loader.length/2, y= w/2+Loader.width}) -- left, down + table.insert(b, {x=-Loader.length/2, y= w/2}) -- right, down + else + -- All aspect. Rectangle around the unit but need to cut out the area of the unit itself. + b[1]={x= l/2, y=-w/2} --DCS#Vec2 + b[2]={x= l/2, y= w/2} --DCS#Vec2 + b[3]={x=-l/2, y= w/2} --DCS#Vec2 + b[4]={x=-l/2, y=-w/2} --DCS#Vec2 + table.insert(b, {x=b[1].x+Loader.length, y=b[1].y-Loader.width}) + table.insert(b, {x=b[2].x+Loader.length, y=b[2].y+Loader.width}) + table.insert(b, {x=b[3].x-Loader.length, y=b[3].y+Loader.width}) + table.insert(b, {x=b[4].x-Loader.length, y=b[4].y-Loader.width}) + end + + -- Rotate edges to match the current heading of the unit. + for i,p in pairs(b) do + b[i]=UTILS.Vec2Rotate2D(p, heading) + end + + -- Translate box to the current position of the unit. + local vec2=self:GetVec2(Element.name) + local d=UTILS.Vec2Norm(vec2) + local h=UTILS.Vec2Hdg(vec2) + + for i,p in pairs(b) do + b[i]=UTILS.Vec2Translate(p, d, h) + end + + -- Update existing zone. + Zone:UpdateFromVec2(b) + + return Zone + end + + return nil +end + + --- Get the first element of a group, which is alive. -- @param #OPSGROUP self -- @return #OPSGROUP.Element The element or `#nil` if no element is alive any more. @@ -8399,9 +8634,7 @@ function OPSGROUP:_AddElementByName(unitname) end - -- Max cargo weight - --element.weightMaxCargo=math.max(element.weightMaxTotal-element.weightEmpty, 0) - + -- Max cargo weight: unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index ef21c495a..2c03b672f 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -97,7 +97,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.3" +OPSTRANSPORT.version="0.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index bc5a1a352..25f832ee5 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1127,13 +1127,13 @@ function UTILS.Vec2Rotate2D(a, angle) local phi=math.rad(angle) - local x=a.y - local y=a.x + local x=a.x + local y=a.y - local Z=x*math.cos(phi)-y*math.sin(phi) - local X=x*math.sin(phi)+y*math.cos(phi) + local X=x*math.cos(phi)-y*math.sin(phi) + local Y=x*math.sin(phi)+y*math.cos(phi) - local A={x=X, y=Z} + local A={x=X, y=Y} return A end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 67f892052..338417123 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1394,18 +1394,6 @@ do -- Cargo return ItemCount end --- --- Get Cargo Bay Free Volume in m3. --- -- @param #POSITIONABLE self --- -- @return #number CargoBayFreeVolume --- function POSITIONABLE:GetCargoBayFreeVolume() --- local CargoVolume = 0 --- for CargoName, Cargo in pairs( self.__.Cargo ) do --- CargoVolume = CargoVolume + Cargo:GetVolume() --- end --- return self.__.CargoBayVolumeLimit - CargoVolume --- end --- - --- Get Cargo Bay Free Weight in kg. -- @param #POSITIONABLE self -- @return #number CargoBayFreeWeight @@ -1423,13 +1411,6 @@ do -- Cargo return self.__.CargoBayWeightLimit - CargoWeight end --- --- Get Cargo Bay Volume Limit in m3. --- -- @param #POSITIONABLE self --- -- @param #number VolumeLimit --- function POSITIONABLE:SetCargoBayVolumeLimit( VolumeLimit ) --- self.__.CargoBayVolumeLimit = VolumeLimit --- end - --- Set Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self -- @param #number WeightLimit @@ -1458,11 +1439,13 @@ do -- Cargo 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, + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) @@ -1507,9 +1490,11 @@ do -- Cargo } local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 + self.__.CargoBayWeightLimit = CargoBayWeightLimit end end + self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit}) end end --- Cargo From 50f3016711a319fc626e8c6ee6a75010e8a96496 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 10 Mar 2021 11:36:10 +0100 Subject: [PATCH 143/382] Update Sead.lua latest --- Moose Development/Moose/Functional/Sead.lua | 86 +++++++++------------ 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index ae886d712..2c5abb451 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -1,26 +1,26 @@ --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- === --- +-- -- ## Features: --- +-- -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) --- +-- -- === --- +-- -- ### Authors: **FlightControl**, **applevangelist** --- +-- -- Last Update: Feb 2021 --- +-- -- === --- +-- -- @module Functional.Sead -- @image SEAD.JPG @@ -28,24 +28,24 @@ -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. --- +-- -- # Constructor: --- +-- -- Use the @{#SEAD.New}() constructor to create a new SEAD object. --- +-- -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) --- +-- -- @field #SEAD SEAD = { - ClassName = "SEAD", + ClassName = "SEAD", TargetSkill = { Average = { Evade = 30, DelayOn = { 40, 60 } } , Good = { Evade = 20, DelayOn = { 30, 50 } } , High = { Evade = 15, DelayOn = { 20, 40 } } , - Excellent = { Evade = 10, DelayOn = { 10, 30 } } - }, + Excellent = { Evade = 10, DelayOn = { 10, 30 } } + }, SEADGroupPrefixes = {}, SuppressedGroups = {}, EngagementRange = 75 -- default 75% engagement range Feature Request #1355 @@ -84,7 +84,7 @@ SEAD = { ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. @@ -99,7 +99,7 @@ function SEAD:New( SEADGroupPrefixes ) local self = BASE:Inherit( self, BASE:New() ) self:F( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -107,7 +107,7 @@ function SEAD:New( SEADGroupPrefixes ) else self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes end - + self:HandleEvent( EVENTS.Shot ) self:I("*** SEAD - Started Version 0.2.5") return self @@ -120,7 +120,7 @@ end function SEAD:UpdateSet( SEADGroupPrefixes ) self:F( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -174,10 +174,10 @@ function SEAD:OnEventShot( EventData ) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) self:T({ SEADWeapon }) - + --[[check for SEAD missiles if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired - or + or SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired @@ -205,7 +205,7 @@ function SEAD:OnEventShot( EventData ) SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired --]] if self:_CheckHarms(SEADWeaponName) then - + local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) -- Unit name @@ -222,7 +222,7 @@ function SEAD:OnEventShot( EventData ) self:T( '*** SEAD - Group Found' ) break end - end + end if SEADGroupFound == true then -- yes we are being attacked if _targetskill == "Random" then -- when skill is random, choose a skill local Skills = { "Average", "Good", "High", "Excellent" } @@ -231,42 +231,26 @@ function SEAD:OnEventShot( EventData ) self:T( _targetskill ) if self.TargetSkill[_targetskill] then if (_evade > self.TargetSkill[_targetskill].Evade) then - + self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) - + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimcont= _targetMimgroup:getController() - + routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - + --tracker ID table to switch groups off and on again - local id = { + local id = { groupName = _targetMimgroup, ctrl = _targetMimcont } - local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) - - if SuppressedGroups1[id.groupName] == nil then - - SuppressedGroups1[id.groupName] = { - SuppressionEndTime1 = timer.getTime() + delay1, - SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } - - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function - --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) - end - - local SuppressedGroups = {} - - local function SuppressionEnd(id) + local function SuppressionEnd(id) --switch group back on local range = self.EngagementRange -- Feature Request #1355 - --env.info(string.format("*** SEAD - Engagement Range is %d", range)) + self:T(string.format("*** SEAD - Engagement Range is %d", range)) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - SuppressedGroups[id.groupName] = nil + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done end -- randomize switch-on time local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) From 6db2c4465d74d616412a0ddbd7dd1b9f8cb3e6a4 Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Sun, 21 Mar 2021 17:35:16 -0700 Subject: [PATCH 144/382] Grading additions Addition of: - AA - Angled Approach call (advisory) - Drift Right/Left - DR/DL (advisory) - Overshoot - OS - graded for start Updated default SetMPWireCorrection(Dcorr) to 12 from 8.7 based on feedback. Minor clean up and updates for intro section to include new additions and other changes previously made. TonyG --- Moose Development/Moose/Ops/Airboss.lua | 92 +++++++++++++++++++++---- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7cdf8a06d..1dd4e34f5 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,7 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] --- * [T-45C Goshawk (VNAO)(Player & AI)] +-- * [T-45C Goshawk] https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -573,7 +573,10 @@ -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * Too **H**igh or too **LO**w: H, LO -- * Too **F**ast or too **SLO**w: F, SLO --- * **Fly through** glideslope **down** or **up**: \\ , / +-- * **O**ver**S**hoot: OS, only referenced during **X** +-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only +-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only +-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only -- -- Each grading, x, is subdivided by -- @@ -634,7 +637,7 @@ -- -- ## Foul Deck Waveoff -- --- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, +-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations, -- or with an aircraft approaching the 3/4 NM during Case III operations. -- -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. @@ -1264,6 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. +-- @field #string T-45 T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -2650,10 +2654,10 @@ end --- Set multiplayer environment wire correction. -- @param #AIRBOSS self --- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @param #number Dcorr Correction distance in meters. Default 12 m. -- @return #AIRBOSS self function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 8.7 + self.mpWireCorrection=Dcorr or 12 return self end @@ -2819,12 +2823,14 @@ end -- @param #number Right -- @param #number RIGHT -- @return #AIRBOSS self -function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) self.lue._max=_max or 0.5 self.lue._min=_min or -0.5 self.lue.Left=Left or -1.0 + self.lue.LeftMed=LeftMed or -2.0 self.lue.LEFT=LEFT or -3.0 self.lue.Right=Right or 1.0 + self.lue.RightMed=RightMed or 2.0 self.lue.RIGHT=RIGHT or 3.0 return self end @@ -10096,6 +10102,27 @@ function AIRBOSS:_Groove(playerData) -- Distance in NM. local d=UTILS.MetersToNM(rho) + -- Drift on lineup. + if rho>=RAR and rho<=RIM then + if gd.LUE>0.21 and lineupError<-0.21 then + env.info" Drift Right across centre ==> DR-" + gd.Drift=" DR" + self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE<-0.21 and lineupError>0.21 then + env.info" Drift Left ==> DL-" + gd.Drift=" DL" + self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE>0.12 and lineupError<-0.12 then + env.info" Little Drift Right across centre ==> (DR-)" + gd.Drift=" (DR)" + self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE<-0.12 and lineupError>0.12 then + env.info" Little Drift Left across centre ==> (DL-)" + gd.Drift=" (DL)" + self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + end + end + -- Update max deviation of line up error. if math.abs(lineupError)>math.abs(gd.LUE) then self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE)) @@ -12092,7 +12119,7 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 16-18 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false @@ -12227,7 +12254,31 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) -- Aircraft specific AoA values. local acaoa=self:_GetAircraftAoA(playerData) - + + --Angled Approach. + local P=nil + if LUE>self.lue.RIGHT and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then + P=underline("AA") + elseif + LUE>self.lue.RightMed and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then + P=("AA ") + elseif + LUE>self.lue.Right and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then + P=little("AA ") + end + + --Overshoot Start. + local O=nil + if LUEacaoa.SLOW then @@ -12260,7 +12311,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) A=little("LO") end - -- Line up. Good [-0.5, 0.5] + -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] local D=nil if LUE>self.lue.RIGHT then D=underline("LUL") @@ -12268,11 +12319,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE Date: Sun, 21 Mar 2021 17:38:06 -0700 Subject: [PATCH 145/382] Revert "Grading additions" This reverts commit 6db2c4465d74d616412a0ddbd7dd1b9f8cb3e6a4. --- Moose Development/Moose/Ops/Airboss.lua | 92 ++++--------------------- 1 file changed, 13 insertions(+), 79 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1dd4e34f5..7cdf8a06d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,7 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] --- * [T-45C Goshawk] https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] +-- * [T-45C Goshawk (VNAO)(Player & AI)] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -573,10 +573,7 @@ -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * Too **H**igh or too **LO**w: H, LO -- * Too **F**ast or too **SLO**w: F, SLO --- * **O**ver**S**hoot: OS, only referenced during **X** --- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only --- * **D**rift **L**eft or **R**ight:DL, DR, advisory only --- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only +-- * **Fly through** glideslope **down** or **up**: \\ , / -- -- Each grading, x, is subdivided by -- @@ -637,7 +634,7 @@ -- -- ## Foul Deck Waveoff -- --- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations, +-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, -- or with an aircraft approaching the 3/4 NM during Case III operations. -- -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. @@ -1267,7 +1264,6 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. --- @field #string T-45 T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -2654,10 +2650,10 @@ end --- Set multiplayer environment wire correction. -- @param #AIRBOSS self --- @param #number Dcorr Correction distance in meters. Default 12 m. +-- @param #number Dcorr Correction distance in meters. Default 8.7 m. -- @return #AIRBOSS self function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 12 + self.mpWireCorrection=Dcorr or 8.7 return self end @@ -2823,14 +2819,12 @@ end -- @param #number Right -- @param #number RIGHT -- @return #AIRBOSS self -function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) self.lue._max=_max or 0.5 self.lue._min=_min or -0.5 self.lue.Left=Left or -1.0 - self.lue.LeftMed=LeftMed or -2.0 self.lue.LEFT=LEFT or -3.0 self.lue.Right=Right or 1.0 - self.lue.RightMed=RightMed or 2.0 self.lue.RIGHT=RIGHT or 3.0 return self end @@ -10102,27 +10096,6 @@ function AIRBOSS:_Groove(playerData) -- Distance in NM. local d=UTILS.MetersToNM(rho) - -- Drift on lineup. - if rho>=RAR and rho<=RIM then - if gd.LUE>0.21 and lineupError<-0.21 then - env.info" Drift Right across centre ==> DR-" - gd.Drift=" DR" - self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.21 and lineupError>0.21 then - env.info" Drift Left ==> DL-" - gd.Drift=" DL" - self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE>0.12 and lineupError<-0.12 then - env.info" Little Drift Right across centre ==> (DR-)" - gd.Drift=" (DR)" - self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.12 and lineupError>0.12 then - env.info" Little Drift Left across centre ==> (DL-)" - gd.Drift=" (DL)" - self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - end - end - -- Update max deviation of line up error. if math.abs(lineupError)>math.abs(gd.LUE) then self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE)) @@ -12119,7 +12092,7 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 15-18.99 sec for a unicorn. + -- Groove time 16-18 sec for a unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false @@ -12254,31 +12227,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) -- Aircraft specific AoA values. local acaoa=self:_GetAircraftAoA(playerData) - - --Angled Approach. - local P=nil - if LUE>self.lue.RIGHT and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then - P=underline("AA") - elseif - LUE>self.lue.RightMed and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then - P=("AA ") - elseif - LUE>self.lue.Right and ROL<=4 and step==AIRBOSS.PatternStep.GROOVE_XX then - P=little("AA ") - end - - --Overshoot Start. - local O=nil - if LUEacaoa.SLOW then @@ -12311,7 +12260,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) A=little("LO") end - -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] + -- Line up. Good [-0.5, 0.5] local D=nil if LUE>self.lue.RIGHT then D=underline("LUL") @@ -12319,11 +12268,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE Date: Sun, 21 Mar 2021 17:40:44 -0700 Subject: [PATCH 146/382] Grading Additions Addition of: - AA - Angled Approach call (advisory) - Drift Right/Left - DR/DL (advisory) - Overshoot - OS - graded for start Updated default SetMPWireCorrection(Dcorr) to 12 from 8.7 based on feedback. Minor clean up and updates for intro section to include new additions and other changes previously made. TonyG --- Moose Development/Moose/Ops/Airboss.lua | 96 +++++++++++++++++++++---- 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 7cdf8a06d..48cc01e61 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,7 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] --- * [T-45C Goshawk (VNAO)(Player & AI)] +-- * [T-45C Goshawk] https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -573,7 +573,10 @@ -- * **L**ined **U**p **L**eft or **R**ight: LUL, LUR -- * Too **H**igh or too **LO**w: H, LO -- * Too **F**ast or too **SLO**w: F, SLO --- * **Fly through** glideslope **down** or **up**: \\ , / +-- * **O**ver**S**hoot: OS, only referenced during **X** +-- * **Fly through** glideslope **down** or **up**: \\ , /, advisory only +-- * **D**rift **L**eft or **R**ight:DL, DR, advisory only +-- * **A**ngled **A**pproach: Angled approach (wings level and LUL): AA, advisory only -- -- Each grading, x, is subdivided by -- @@ -634,7 +637,7 @@ -- -- ## Foul Deck Waveoff -- --- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is crossing the ship's wake during Case I/II operations, +-- A foul deck waveoff is called by the LSO if an aircraft is detected within the landing area when an approaching aircraft is at position IM-IC during Case I/II operations, -- or with an aircraft approaching the 3/4 NM during Case III operations. -- -- The approaching aircraft will be notified via LSO radio comms and is supposed to overfly the landing area to enter the Bolter pattern. **This pass is not graded**. @@ -1264,6 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. +-- @field #string T-45 T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -2650,10 +2654,10 @@ end --- Set multiplayer environment wire correction. -- @param #AIRBOSS self --- @param #number Dcorr Correction distance in meters. Default 8.7 m. +-- @param #number Dcorr Correction distance in meters. Default 12 m. -- @return #AIRBOSS self function AIRBOSS:SetMPWireCorrection(Dcorr) - self.mpWireCorrection=Dcorr or 8.7 + self.mpWireCorrection=Dcorr or 12 return self end @@ -2819,12 +2823,14 @@ end -- @param #number Right -- @param #number RIGHT -- @return #AIRBOSS self -function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LEFT, Right, RIGHT) +function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) self.lue._max=_max or 0.5 self.lue._min=_min or -0.5 self.lue.Left=Left or -1.0 + self.lue.LeftMed=LeftMed or -2.0 self.lue.LEFT=LEFT or -3.0 self.lue.Right=Right or 1.0 + self.lue.RightMed=RightMed or 2.0 self.lue.RIGHT=RIGHT or 3.0 return self end @@ -10096,6 +10102,27 @@ function AIRBOSS:_Groove(playerData) -- Distance in NM. local d=UTILS.MetersToNM(rho) + -- Drift on lineup. + if rho>=RAR and rho<=RIM then + if gd.LUE>0.21 and lineupError<-0.21 then + env.info" Drift Right across centre ==> DR-" + gd.Drift=" DR" + self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE<-0.21 and lineupError>0.21 then + env.info" Drift Left ==> DL-" + gd.Drift=" DL" + self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE>0.12 and lineupError<-0.12 then + env.info" Little Drift Right across centre ==> (DR-)" + gd.Drift=" (DR)" + self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + elseif gd.LUE<-0.12 and lineupError>0.12 then + env.info" Little Drift Left across centre ==> (DL-)" + gd.Drift=" (DL)" + self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) + end + end + -- Update max deviation of line up error. if math.abs(lineupError)>math.abs(gd.LUE) then self:T(self.lid..string.format("Got bigger LUE at step %s, d=%.3f: LUE %.3f>%.3f", gs, d, lineupError, gd.LUE)) @@ -12092,7 +12119,7 @@ function AIRBOSS:_LSOgrade(playerData) local nS=count(G, '%(') local nN=N-nS-nL - -- Groove time 16-18 sec for a unicorn. + -- Groove time 15-18.99 sec for a unicorn. local Tgroove=playerData.Tgroove local TgrooveUnicorn=Tgroove and (Tgroove>=15.0 and Tgroove<=18.99) or false @@ -12227,7 +12254,35 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) -- Aircraft specific AoA values. local acaoa=self:_GetAircraftAoA(playerData) - + + --Angled Approach. + local P=nil + if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 then + if LUE>self.lue.RIGHT then + P=underline("AA") + elseif + LUE>self.lue.RightMed then + P=("AA ") + elseif + LUE>self.lue.Right then + P=little("AA ") + end + end + + --Overshoot Start. + local O=nil + if step==AIRBOSS.PatternStep.GROOVE_XX then + if LUEacaoa.SLOW then @@ -12260,7 +12315,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) A=little("LO") end - -- Line up. Good [-0.5, 0.5] + -- Line up. XX Step replaced by Overshoot start (OS). Good [-0.5, 0.5] local D=nil if LUE>self.lue.RIGHT then D=underline("LUL") @@ -12268,11 +12323,11 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE Date: Mon, 22 Mar 2021 11:47:39 +0100 Subject: [PATCH 147/382] Utils - added Convert knots to alitude corrected KIAS Added - Convert knots to alitude corrected KIAS, e.g. for tankers. --- Moose Development/Moose/Utilities/Utils.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index fc2acdd9d..693550719 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -408,6 +408,14 @@ UTILS.hPa2inHg = function( hPa ) return hPa * 0.0295299830714 end +--- Convert knots to alitude corrected KIAS, e.g. for tankers. +-- @param #number knots Speed in knots. +-- @param #number altitude Altitude in feet +-- @return #number Corrected KIAS +UTILS.KnotsToAltKIAS = function( knots, altitude ) + return (knots * 0.018 * (altitude / 1000)) + knots +end + --- Convert pressure from hecto Pascal (hPa) to millimeters of mercury (mmHg). -- @param #number hPa Pressure in hPa. -- @return #number Pressure in mmHg. @@ -1457,4 +1465,4 @@ function UTILS.GetOSTime() end return nil -end \ No newline at end of file +end From d83283c709019dbed90568b46ed54c1ab97dfb7d Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Mon, 22 Mar 2021 10:10:33 -0700 Subject: [PATCH 148/382] Update Airboss.lua formatting fixes --- Moose Development/Moose/Ops/Airboss.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 48cc01e61..ac309504b 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -39,7 +39,7 @@ -- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] --- * [T-45C Goshawk] https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] +-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) -- * F-14A Tomcat (AI) -- * E-2D Hawkeye (AI) @@ -1267,7 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. --- @field #string T-45 T-45C by VNAO +-- @field #string T45 T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -12262,10 +12262,10 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) P=underline("AA") elseif LUE>self.lue.RightMed then - P=("AA ") + P="AA " elseif LUE>self.lue.Right then - P=little("AA ") + P=little("AA") end end @@ -12276,7 +12276,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) O=underline("OS") elseif LUE Date: Mon, 22 Mar 2021 19:01:04 -0700 Subject: [PATCH 149/382] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index ac309504b..3beeed272 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1267,7 +1267,7 @@ AIRBOSS = { -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. -- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod. --- @field #string T45 T-45C by VNAO +-- @field #string T45C T-45C by VNAO AIRBOSS.AircraftCarrier={ AV8B="AV8BNA", HORNET="FA-18C_hornet", @@ -1344,6 +1344,8 @@ AIRBOSS.CarrierType={ -- @field #number _min Min _OK_ value. Default -0.5 deg. -- @field #number Left (LUR) threshold. Default -1.0 deg. -- @field #number Right (LUL) threshold. Default 1.0 deg. +-- @field #number LeftMed threshold for AA/OS measuring. Default -2.0 deg. +-- @field #number RightMed threshold for AA/OS measuring. Default 2.0 deg. -- @field #number LEFT LUR threshold. Default -3.0 deg. -- @field #number RIGHT LUL threshold. Default 3.0 deg. @@ -2819,8 +2821,10 @@ end -- @param #number _max -- @param #number _min -- @param #number Left +-- @param #number LeftMed -- @param #number LEFT -- @param #number Right +-- @param #number RightMed -- @param #number RIGHT -- @return #AIRBOSS self function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT) From 564f95781e32c02cc5f31c6743ae6bf7d65873b0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 28 Mar 2021 20:29:15 +0200 Subject: [PATCH 150/382] Update Positionable.lua - Added cargo bay limits for ground (by kappa) and naval units. --- .../Moose/Wrapper/Positionable.lua | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 4c94ed2d1..51a0718c2 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1457,11 +1457,14 @@ do -- Cargo 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, + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. + ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) @@ -1469,40 +1472,47 @@ do -- Cargo local Desc = self:GetDesc() local Weights = { - ["M1126 Stryker ICV"] = 9, - ["M-113"] = 9, ["AAV7"] = 25, - ["M2A1_halftrack"] = 9, - ["BMD-1"] = 9, + ["Bedford_MWD"] = 8, -- new by kappa + ["Blitz_36-6700A"] = 10, -- new by kappa + ["BMD-1"] = 9, -- IRL should be 4 passengers ["BMP-1"] = 8, ["BMP-2"] = 7, - ["BMP-3"] = 8, + ["BMP-3"] = 8, -- IRL should be 7+2 passengers ["Boman"] = 25, - ["BTR-80"] = 9, - ["BTR_D"] = 12, + ["BTR-80"] = 9, -- IRL should be 7 passengers + ["BTR-82A"] = 9, -- new by kappa -- IRL should be 7 passengers + ["BTR_D"] = 12, -- IRL should be 10 passengers ["Cobra"] = 8, + ["Land_Rover_101_FC"] = 11, -- new by kappa + ["Land_Rover_109_S3"] = 7, -- new by kappa ["LAV-25"] = 6, ["M-2 Bradley"] = 6, ["M1043 HMMWV Armament"] = 4, ["M1045 HMMWV TOW"] = 4, ["M1126 Stryker ICV"] = 9, ["M1134 Stryker ATGM"] = 9, + ["M2A1_halftrack"] = 9, + ["M-113"] = 9, -- IRL should be 11 passengers ["Marder"] = 6, - ["MCV-80"] = 9, + ["MCV-80"] = 9, -- IRL should be 7 passengers ["MLRS FDDM"] = 4, - ["MTLB"] = 25, - ["TPZ"] = 10, - ["Ural-4320 APA-5D"] = 10, + ["MTLB"] = 25, -- IRL should be 11 passengers ["GAZ-66"] = 8, ["GAZ-3307"] = 12, ["GAZ-3308"] = 14, - ["Tigr_233036"] = 6, + ["Grad_FDDM"] = 6, -- new by kappa ["KAMAZ Truck"] = 12, ["KrAZ6322"] = 12, ["M 818"] = 12, + ["Tigr_233036"] = 6, + ["TPZ"] = 10, + ["UAZ-469"] = 4, -- new by kappa ["Ural-375"] = 12, ["Ural-4320-31"] = 14, + ["Ural-4320 APA-5D"] = 10, ["Ural-4320T"] = 14, + ["ZBD04A"] = 7, -- new by kappa } local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 95 From 5a1faba79683682059c4b1525d9a95749f9bc61b Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Sun, 28 Mar 2021 15:25:59 -0700 Subject: [PATCH 151/382] CASE III fix for OS/AA --- Moose Development/Moose/Ops/Airboss.lua | 34 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 3beeed272..041843d56 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -301,7 +301,7 @@ -- -- ![Banner Image](..\Presentations\AIRBOSS\Airboss_Case3.png) -- --- A Case III recovery is conducted during nighttime. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. +-- A Case III recovery is conducted during nighttime or when the visibility is below CASE II minima during the day. The holding position and the landing pattern are rather different from a Case I recovery as can be seen in the image above. -- -- The first holding zone starts 21 NM astern the carrier at angels 6. The separation between the stacks is 1000 ft just like in Case I. However, the distance to the boat -- increases by 1 NM with each stack. The general form can be written as D=15+6+(N-1), where D is the distance to the boat in NM and N the number of the stack starting at N=1. @@ -10108,19 +10108,19 @@ function AIRBOSS:_Groove(playerData) -- Drift on lineup. if rho>=RAR and rho<=RIM then - if gd.LUE>0.21 and lineupError<-0.21 then + if gd.LUE>0.22 and lineupError<-0.22 then env.info" Drift Right across centre ==> DR-" gd.Drift=" DR" self:T(self.lid..string.format("Got Drift Right across centre step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.21 and lineupError>0.21 then + elseif gd.LUE<-0.22 and lineupError>0.22 then env.info" Drift Left ==> DL-" gd.Drift=" DL" self:T(self.lid..string.format("Got Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE>0.12 and lineupError<-0.12 then + elseif gd.LUE>0.13 and lineupError<-0.14 then env.info" Little Drift Right across centre ==> (DR-)" gd.Drift=" (DR)" self:T(self.lid..string.format("Got Little Drift Right across centre at step %s, d=%.3f: Max LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) - elseif gd.LUE<-0.12 and lineupError>0.12 then + elseif gd.LUE<-0.13 and lineupError>0.14 then env.info" Little Drift Left across centre ==> (DL-)" gd.Drift=" (DL)" self:E(self.lid..string.format("Got Little Drift Left across centre at step %s, d=%.3f: Min LUE=%.3f, lower LUE=%.3f", gs, d, gd.LUE, lineupError)) @@ -12261,7 +12261,7 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) --Angled Approach. local P=nil - if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 then + if step==AIRBOSS.PatternStep.GROOVE_XX and ROL<=4.0 and playerData.case<3 then if LUE>self.lue.RIGHT then P=underline("AA") elseif @@ -12327,12 +12327,22 @@ function AIRBOSS:_Flightdata2Text(playerData, groovestep) D="LUL" elseif LUE>self.lue._max then D=little("LUL") - elseif LUE Date: Mon, 29 Mar 2021 09:53:30 +0200 Subject: [PATCH 152/382] Update AI_A2A_Cap.lua Fixes #1474 --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 08062cc11..f86bed9c0 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -143,7 +143,7 @@ end -- @return #AI_A2A_CAP function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType ) - return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType ) + return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) end From 3e22411328dd37ea28b9ac068c2372ea357cfa44 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 29 Mar 2021 18:01:42 +0200 Subject: [PATCH 153/382] Events - Added new DCS events --- Moose Development/Moose/Core/Base.lua | 31 +++++++++++++++++++++ Moose Development/Moose/Core/Event.lua | 38 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 4398c6d1a..1b809252a 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -676,6 +676,37 @@ do -- Event Handling -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. + --- Paratrooper landing. + -- @function [parent=#BASE] OnEventParatrooperLanding + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Discard chair after ejection. + -- @function [parent=#BASE] OnEventDiscardChairAfterEjection + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Weapon add. Fires when entering a mission per pylon with the name of the weapon (double pylons not counted, infinite wep reload not counted. + -- @function [parent=#BASE] OnEventParatrooperLanding + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Trigger zone. + -- @function [parent=#BASE] OnEventTriggerZone + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Landing quality mark. + -- @function [parent=#BASE] OnEventLandingQualityMark + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- BDA. + -- @function [parent=#BASE] OnEventBDA + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Occurs when a player enters a slot and takes control of an aircraft. -- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event. -- initiator : The unit that is being taken control of. diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 4588c6ba7..22b86e1ce 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -241,6 +241,13 @@ EVENTS = { Score = world.event.S_EVENT_SCORE or -1, UnitLost = world.event.S_EVENT_UNIT_LOST or -1, LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, + -- Added with DCS 2.7.0 + ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1, + DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1, + WeaponAdd = world.event.S_EVENT_WEAPON_ADD or -1, + TriggerZone = world.event.S_EVENT_TRIGGER_ZONE or -1, + LandingQualityMark = world.event.S_EVENT_LANDING_QUALITY_MARK or -1, + BDA = world.event.S_EVENT_BDA or -1, } --- The Event structure @@ -521,6 +528,37 @@ local _EVENTMETA = { Order = 1, Event = "OnEventLandingAfterEjection", Text = "S_EVENT_LANDING_AFTER_EJECTION" + }, + -- Added with DCS 2.7.0 + [EVENTS.ParatrooperLanding] = { + Order = 1, + Event = "OnEventParatrooperLanding", + Text = "S_EVENT_PARATROOPER_LENDING" + }, + [EVENTS.DicardChairAfterEjection] = { + Order = 1, + Event = "OnEventDiscardChairAfterEjection", + Text = "S_EVENT_DISCARD_CHAIR_AFTER_EJECTION" + }, + [EVENTS.WeaponAdd] = { + Order = 1, + Event = "OnEventWeaponAdd", + Text = "S_EVENT_WEAPON_ADD" + }, + [EVENTS.TriggerZone] = { + Order = 1, + Event = "OnEventTriggerZone", + Text = "S_EVENT_TRIGGER_ZONE" + }, + [EVENTS.LandingQualityMark] = { + Order = 1, + Event = "OnEventLandingQualityMark", + Text = "S_EVENT_LANDING_QUALITYMARK" + }, + [EVENTS.BDA] = { + Order = 1, + Event = "OnEventBDA", + Text = "S_EVENT_BDA" }, } From f161f08fc17af02305f1a3cba35119b357918cbd Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 29 Mar 2021 19:55:11 +0200 Subject: [PATCH 154/382] Update Event.lua - Fixed typo --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 22b86e1ce..c996d9bc7 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -535,7 +535,7 @@ local _EVENTMETA = { Event = "OnEventParatrooperLanding", Text = "S_EVENT_PARATROOPER_LENDING" }, - [EVENTS.DicardChairAfterEjection] = { + [EVENTS.DiscardChairAfterEjection] = { Order = 1, Event = "OnEventDiscardChairAfterEjection", Text = "S_EVENT_DISCARD_CHAIR_AFTER_EJECTION" From 31b93fb42c40a2292754e7a57831a78e61114f9b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 1 Apr 2021 13:21:40 +0200 Subject: [PATCH 155/382] Update FlightGroup.lua Avoid endless when plane/heli is not airborne by needs to RTB --- Moose Development/Moose/Ops/FlightGroup.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ad0f1a6ce..09ce9a524 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -147,6 +147,7 @@ FLIGHTGROUP = { Tparking = nil, menu = nil, ishelo = nil, + RTBRecallCount = 0, } @@ -2150,11 +2151,16 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) end if not self.group:IsAirborne(true) then - self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 10 sec.")) + -- this should really not happen, either the AUFTRAG is cancelled before the group was airborne or it is stuck at the ground for some reason + self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec.")) allowed=false - Tsuspend=-10 + Tsuspend=-20 + self.RTBRecallCount = self.RTBRecallCount+1 + if self.RTBRecallCount > 3 then + self:Despawn(5) + end end - + -- Only if fuel is not low or critical. if not (self:IsFuelLow() or self:IsFuelCritical()) then From 903e8711f7415bc4dc15f9491bd5b6907d3541af Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 1 Apr 2021 13:28:59 +0200 Subject: [PATCH 156/382] Update AirWing.lua Make Patrolpoint Markes Switchable --- Moose Development/Moose/Ops/AirWing.lua | 59 +++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 2f3870403..234d89ed5 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -119,6 +119,7 @@ AIRWING = { pointsTANKER = {}, pointsAWACS = {}, wingcommander = nil, + markpoints = false, } --- Squadron asset. @@ -209,6 +210,7 @@ function AIRWING:New(warehousename, airwingname) self.nflightsTANKERprobe=0 self.nflightsRecoveryTanker=0 self.nflightsRescueHelo=0 + self.markpoints = false ------------------------ --- Pseudo Functions --- @@ -230,6 +232,24 @@ function AIRWING:New(warehousename, airwingname) -- @function [parent=#AIRWING] __Stop -- @param #AIRWING self -- @param #number delay Delay in seconds. + + --- On after "FlightOnMission" event. Triggered when an asset group starts a mission. + -- @function [parent=#AIRWING] OnAfterFlightOnMission + -- @param #AIRWING self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param Ops.FlightGroup#FLIGHTGROUP Flightgroup The Flightgroup on mission + -- @param Ops.Auftrag#AUFTRAG Mission The Auftrag of the Flightgroup + + --- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. + -- @function [parent=#AIRWING] OnAfterAssetReturned + -- @param #AIRWING self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.Squadron#SQUADRON Squadron The asset squadron. + -- @param #AIRWING.SquadronAsset Asset The asset that returned. return self end @@ -647,6 +667,19 @@ function AIRWING:SetNumberTankerBoom(Nboom) return self end +--- Set markers on the map for Patrol Points. +-- @param #AIRWING self +-- @param #boolean onoff Set to true to switch markers on. +-- @return #AIRWING self +function AIRWING:ShowPatrolPointMarkers(onoff) + if onoff then + self.markpoints = true + else + self.markpoints = false + end + return self +end + --- Set number of TANKER flights with Probe constantly in the air. -- @param #AIRWING self -- @param #number Nprobe Number of flights. Default 1. @@ -689,12 +722,10 @@ end --- Update marker of the patrol point. -- @param #AIRWING.PatrolData point Patrol point table. function AIRWING.UpdatePatrolPointMarker(point) - - local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", - point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) - - point.marker:UpdateText(text, 1) - + local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", + point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) + + point.marker:UpdateText(text, 1) end @@ -717,10 +748,12 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL patrolpoint.altitude=Altitude or math.random(10,20)*1000 patrolpoint.speed=Speed or 350 patrolpoint.noccupied=0 - patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() - AIRWING.UpdatePatrolPointMarker(patrolpoint) - + if self.markpoints then + patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() + AIRWING.UpdatePatrolPointMarker(patrolpoint) + end + return patrolpoint end @@ -928,7 +961,7 @@ function AIRWING:CheckCAP() patrol.noccupied=patrol.noccupied+1 - AIRWING.UpdatePatrolPointMarker(patrol) + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end self:AddMission(missionCAP) @@ -972,7 +1005,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - AIRWING.UpdatePatrolPointMarker(patrol) + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -990,7 +1023,7 @@ function AIRWING:CheckTANKER() patrol.noccupied=patrol.noccupied+1 - AIRWING.UpdatePatrolPointMarker(patrol) + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end self:AddMission(mission) @@ -1018,7 +1051,7 @@ function AIRWING:CheckAWACS() patrol.noccupied=patrol.noccupied+1 - AIRWING.UpdatePatrolPointMarker(patrol) + if self.markpoints then AIRWING.UpdatePatrolPointMarker(patrol) end self:AddMission(mission) From b1465d89a6c50cd36741348edbf81bd0f47f4b73 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 4 Apr 2021 12:49:40 +0200 Subject: [PATCH 157/382] Update FlightGroup.lua Added check for velocity for the RTB loop (if group is taxiing). Added OutOfAAMissiles. --- Moose Development/Moose/Ops/FlightGroup.lua | 47 +++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 09ce9a524..afcedc072 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -140,6 +140,7 @@ FLIGHTGROUP = { fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, + outofAAMrtb = true, squadron = nil, flightcontrol = nil, flaghold = nil, @@ -477,6 +478,20 @@ function FLIGHTGROUP:SetFuelLowRTB(switch) return self end +--- Set if flight is out of Air-Air-Missiles, flight goes RTB. +-- @param #FLIGHTGROUP self +-- @param #boolean switch If true or nil, flight goes RTB. If false, turn this off. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOutOfAMMRTB(switch) + if switch==false then + self.outofAAMrtb=false + else + self.outofAAMrtb=true + end + return self +end + + --- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker. -- @param #FLIGHTGROUP self -- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off. @@ -1015,7 +1030,18 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if fuelmin RTB event is suspended for 20 sec.")) allowed=false Tsuspend=-20 - self.RTBRecallCount = self.RTBRecallCount+1 - if self.RTBRecallCount > 3 then + local groupspeed = self.group:GetVelocityMPS() + if groupspeed <= 1 then self.RTBRecallCount = self.RTBRecallCount+1 end + if self.RTBRecallCount > 6 then self:Despawn(5) end end From bab61271f2bef51cfa494e8d0f8a10992db4bacc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 4 Apr 2021 13:07:54 +0200 Subject: [PATCH 158/382] Update Intelligence.lua Make Markers Coalition specific --- Moose Development/Moose/Ops/Intelligence.lua | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index ca95135eb..9c4a3a876 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1,11 +1,11 @@ --- **Ops** - Office of Military Intelligence. -- --- ## Main Features: +-- **Main Features:** -- -- * Detect and track contacts consistently -- * Detect and track clusters of contacts consistently -- * Use FSM events to link functionality into your scripts --- * Easy setup +-- * Easy setup -- -- === -- @@ -80,6 +80,7 @@ -- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` -- `end` -- +-- -- @field #INTEL INTEL = { ClassName = "INTEL", @@ -93,7 +94,7 @@ INTEL = { ContactsUnknown = {}, Clusters = {}, clustercounter = 1, - clusterradius = 10, + clusterradius = 15, } --- Detected item info. @@ -130,7 +131,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.0" +INTEL.version="0.2.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -350,7 +351,7 @@ function INTEL:RemoveRejectZone(RejectZone) return self end ---- Set forget contacts time interval. For unknown contacts only. +--- Set forget contacts time interval. -- Previously known contacts that are not detected any more, are "lost" after this time. -- This avoids fast oscillations between a contact being detected and undetected. -- @param #INTEL self @@ -465,7 +466,7 @@ end -- @param #number radius The radius of the clusters -- @return #INTEL self function INTEL:SetClusterRadius(radius) - local radius = radius or 10 + local radius = radius or 15 self.clusterradius = radius return self end @@ -1082,7 +1083,7 @@ function INTEL:CalcClusterThreatlevelSum(cluster) threatlevel=threatlevel+contact.threatlevel end - cluster.threatlevelSum = threatlevel + cluster.threatlevelSum = threatlevel return threatlevel end @@ -1094,7 +1095,7 @@ function INTEL:CalcClusterThreatlevelAverage(cluster) local threatlevel=self:CalcClusterThreatlevelSum(cluster) threatlevel=threatlevel/cluster.size - cluster.threatlevelAve = threatlevel + cluster.threatlevelAve = threatlevel return threatlevel end @@ -1114,7 +1115,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster) end end - cluster.threatlevelMax = threatlevel + cluster.threatlevelMax = threatlevel return threatlevel end @@ -1155,7 +1156,7 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) --local dist=Contact.position:Get2DDistance(contact.position) local dist=Contact.position:DistanceFromPointVec2(contact.position) - local radius = self.clusterradius or 10 + local radius = self.clusterradius or 15 if dist Date: Sun, 4 Apr 2021 13:11:25 +0200 Subject: [PATCH 159/382] Update AirWing.lua Avoid loop if mission is governed by an Airwing. Mission cancel will ask airwing to cancel will ask flightgroup to cancel, which doesn't work if the latter is dead. Rare but happens. --- Moose Development/Moose/Ops/AirWing.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 234d89ed5..706133f4b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1464,7 +1464,9 @@ function AIRWING:onafterMissionCancel(From, Event, To, Mission) -- Info message. self:I(self.lid..string.format("Cancel mission %s", Mission.name)) - if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() then + local Ngroups = Mission:CountOpsGroups() + + if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() or Ngroups == 0 then Mission:Done() From cb813b70e1361bf9cdc4b635d71721e9ff8a0d0c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:31:10 +0200 Subject: [PATCH 160/382] Update Mantis.lua Added option to use AI on/off instead of changing the alert state --- Moose Development/Moose/Functional/Mantis.lua | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index c4564fb94..4d6565262 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Feb 2021 +-- Date: Apr 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -51,6 +51,7 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit +-- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default) -- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range @@ -189,7 +190,8 @@ MANTIS = { Shorad = nil, ShoradLink = false, ShoradTime = 600, - ShoradActDistance = 15000, + ShoradActDistance = 15000, + UseAIOnOff = false, } ----------------------------------------------------------------------- @@ -206,6 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) + --@param #boolean AIOnOff Make MANTIS switch AI on and off instead of changing the alarm state between RED and GREEN (optional) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -227,7 +230,7 @@ do -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- - function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs) + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, AIOnOff) -- DONE: Create some user functions for these -- DONE: Make HQ useful @@ -260,6 +263,8 @@ do self.ShoradLink = false self.ShoradTime = 600 self.ShoradActDistance = 15000 + -- TODO: add emissions on/off when available .... in 2 weeks + self.UseAIOnOff = AIOnOff or false if type(awacs) == "string" then self.advAwacs = true @@ -299,7 +304,7 @@ do end -- @field #string version - self.version="0.3.7" + self.version="0.4.0" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self @@ -458,6 +463,13 @@ do end end + --- Set using AI on/off instead of changing alarm state + -- @param #MANTIS self + -- @param #boolean switch Decide if we are changing alarm state or AI state + function MANTIS:SetUsingAIOnOff(switch) + self.UseAIOnOff = switch or false + end + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -701,7 +713,12 @@ do --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do local group = _group - group:OptionAlarmStateGreen() -- AI off + -- TODO: add emissions on/off + if self.UseAIOnOff then + group:SetAIOff() + else + group:OptionAlarmStateGreen() -- AI off + end group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range if group:IsGround() then local grpname = group:GetName() @@ -804,7 +821,11 @@ do local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) if IsInZone then --check any target in zone if samgroup:IsAlive() then - -- switch off SAM + -- switch on SAM + if self.UseAIOnOff then + -- TODO: add emissions on/off + samgroup:SetAIOn() + end samgroup:OptionAlarmStateRed() -- link in to SHORAD if available -- DONE: Test integration fully @@ -822,7 +843,12 @@ do else if samgroup:IsAlive() then -- switch off SAM - samgroup:OptionAlarmStateGreen() + if self.UseAIOnOff then + -- TODO: add emissions on/off + samgroup:SetAIOff() + else + samgroup:OptionAlarmStateGreen() + end --samgroup:OptionROEWeaponFree() --samgroup:SetAIOn() local text = string.format("SAM %s switched to alarm state GREEN!", name) @@ -857,6 +883,10 @@ do local name = _data[1] local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then + if self.UseAIOnOff then + -- TODO: add emissions on/off + samgroup:SetAIOn() + end samgroup:OptionAlarmStateRed() end -- end alive end -- end for loop From bddc1d7fdcb6531300141963fecb5606700c4a52 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:32:34 +0200 Subject: [PATCH 161/382] Update Shorad.lua Added option to use AI on/off instead of changing the alert state --- Moose Development/Moose/Functional/Shorad.lua | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 7ecf59d18..155a86829 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -38,6 +38,7 @@ -- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles -- @field #number DefenseLowProb Default 70, minimum detection limit -- @field #number DefenseHighProb Default 90, maximim detection limit +-- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default). -- @extends Core.Base#BASE --- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) @@ -94,7 +95,8 @@ SHORAD = { DefendHarms = true, DefendMavs = true, DefenseLowProb = 70, - DefenseHighProb = 90, + DefenseHighProb = 90, + UseAIOnOff = false, } ----------------------------------------------------------------------- @@ -174,7 +176,8 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self:I("*** SHORAD - Started Version 0.0.2") + self.UseAIOnOff = false -- Decide if we are using AI on/off (true) or AlarmState red/green (default) + self:I("*** SHORAD - Started Version 0.1.0") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -189,7 +192,11 @@ do self:T({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do + if self.UseAIOnOff then + _group:SetAIOff() + else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP + end end -- gather entropy for i=1,10 do @@ -272,6 +279,13 @@ do self.Radius = radius end + --- Set using AI on/off instead of changing alarm state + -- @param #SHORAD self + -- @param #boolean switch Decide if we are changing alarm state or AI state + function SHORAD:SetUsingAIOnOff(switch) + self.UseAIOnOff = switch or false + end + --- Check if a HARM was fired -- @param #SHORAD self -- @param #string WeaponName @@ -396,7 +410,11 @@ do local function SleepShorad(group) local groupname = group:GetName() self.ActiveGroups[groupname] = nil - group:OptionAlarmStateGreen() + if self.UseAIOnOff then + group:SetAIOff() + else + group:OptionAlarmStateGreen() + end local text = string.format("Sleeping SHORAD %s", group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) @@ -407,6 +425,9 @@ do local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + if self.UseAIOnOff then + _group:SetAIOn() + end _group:OptionAlarmStateRed() local groupname = _group:GetName() if self.ActiveGroups[groupname] == nil then -- no timer yet for this group From ca44e2762bc4c6afcc0f847ba574cd869ab37884 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Apr 2021 20:53:15 +0200 Subject: [PATCH 162/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a1d03a63a..6ad1cf5d0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -445,7 +445,9 @@ OPSGROUP.version="0.7.2" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. --- TODO: Options EPLRS, Afterburner restrict etc. +-- TODO: Options EPLRS +-- TODO: Afterburner restrict +-- TODO: What more options? -- TODO: Damage? -- TODO: Shot events? -- TODO: Marks to add waypoints/tasks on-the-fly. @@ -5029,6 +5031,7 @@ end --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. +-- @return #OPSGROUP self function OPSGROUP:AddOpsTransport(OpsTransport) -- Add this group as carrier for the transport. @@ -5039,6 +5042,8 @@ function OPSGROUP:AddOpsTransport(OpsTransport) table.insert(self.cargoqueue, OpsTransport) self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) + + return self end --- Delete a cargo transport assignment from the cargo queue @@ -7190,7 +7195,7 @@ function OPSGROUP:SwitchROT(rot) self.group:OptionROT(self.option.ROT) - self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) end From c1a8c088804857797fc98a709f7baca2a8240ef4 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 12 Apr 2021 13:02:16 +0200 Subject: [PATCH 163/382] Update FlightGroup.lua --- Moose Development/Moose/Ops/FlightGroup.lua | 54 ++++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index afcedc072..a71619474 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -140,7 +140,8 @@ FLIGHTGROUP = { fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, - outofAAMrtb = true, + outofAAMrtb = true, + outofAGMrtb = true, squadron = nil, flightcontrol = nil, flaghold = nil, @@ -148,7 +149,7 @@ FLIGHTGROUP = { Tparking = nil, menu = nil, ishelo = nil, - RTBRecallCount = 0, + RTBRecallCount = 0, } @@ -212,7 +213,7 @@ FLIGHTGROUP.version="0.6.1" -- TODO: Mark assigned parking spot on F10 map. -- TODO: Let user request a parking spot via F10 marker :) -- TODO: Monitor traveled distance in air ==> calculate fuel consumption ==> calculate range remaining. Will this give half way accurate results? --- TODO: Out of AG/AA missiles. Safe state of out-of-ammo. +-- DONE: Out of AG/AA missiles. Safe state of out-of-ammo. -- DONE: Add tasks. -- DONE: Waypoints, read, add, insert, detour. -- DONE: Get ammo. @@ -276,9 +277,9 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A missiles. Not implemented yet! - self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. Not implemented yet! - self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2G missiles. Not implemented yet! + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A missiles. + self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. + self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S(ship) missiles. Not implemented yet! self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. @@ -482,7 +483,7 @@ end -- @param #FLIGHTGROUP self -- @param #boolean switch If true or nil, flight goes RTB. If false, turn this off. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetOutOfAMMRTB(switch) +function FLIGHTGROUP:SetOutOfAAMRTB(switch) if switch==false then self.outofAAMrtb=false else @@ -491,6 +492,18 @@ function FLIGHTGROUP:SetOutOfAMMRTB(switch) return self end +--- Set if flight is out of Air-Ground-Missiles, flight goes RTB. +-- @param #FLIGHTGROUP self +-- @param #boolean switch If true or nil, flight goes RTB. If false, turn this off. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOutOfAGMRTB(switch) + if switch==false then + self.outofAGMrtb=false + else + self.outofAGMrtb=true + end + return self +end --- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker. -- @param #FLIGHTGROUP self @@ -1031,17 +1044,28 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:FuelCritical() end - -- Out of AA Ammo? CAP, GCICAP, INTERCEPT + -- Out of AA Missiles? CAP, GCICAP, INTERCEPT local CurrIsCap = false + -- Out of AG Missiles? BAI, SEAD, CAS, STRIKE + local CurrIsA2G = false + -- Check AUFTRAG Type local CurrAuftrag = self:GetMissionCurrent() if CurrAuftrag then local CurrAuftragType = CurrAuftrag:GetType() if CurrAuftragType == "CAP" or CurrAuftragType == "GCICAP" or CurrAuftragType == "INTERCEPT" then CurrIsCap = true end + if CurrAuftragType == "BAI" or CurrAuftragType == "CAS" or CurrAuftragType == "SEAD" or CurrAuftragType == "STRIKE" then CurrIsA2G = true end end + + -- Check A2A if (not self:CanAirToAir(true)) and CurrIsCap then self:OutOfMissilesAA() end + -- Check A2G + if (not self:CanAirToGround(false)) and CurrIsA2G then + self:OutOfMissilesAG() + end + end --- @@ -2087,6 +2111,20 @@ function FLIGHTGROUP:onafterOutOfMissilesAA(From, Event, To) end end +--- On after "OutOfMissilesAG" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterOutOfMissilesAG(From, Event, To) + self:I(self.lid.."Group is out of AG Missiles!") + if self.outofAGMrtb then + -- Back to destination or home. + local airbase=self.destbase or self.homebase + self:__RTB(-5,airbase) + end +end + --- Check if flight is done, i.e. -- -- * passed the final waypoint, From fa342a5b6bc4bae476f016a0bd6006b213262b0b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 14 Apr 2021 23:42:36 +0200 Subject: [PATCH 164/382] ATIS v0.9.1 - DCS 2.7 weather presetss --- Moose Development/Moose/Ops/ATIS.lua | 100 ++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 044963a28..001b43f1b 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -7,7 +7,7 @@ -- * Wind direction and speed -- * Visibility -- * Cloud coverage, base and ceiling --- * Temprature +-- * Temperature -- * Dew point (approximate as there is no relative humidity in DCS yet) -- * Pressure QNH/QFE -- * Weather phenomena: rain, thunderstorm, fog, dust @@ -564,7 +564,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.0" +ATIS.version="0.9.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1431,9 +1431,103 @@ function ATIS:onafterBroadcast(From, Event, To) local cloudceil=clouds.base+clouds.thickness local clouddens=clouds.density + -- Cloud preset (DCS 2.7) + local cloudspreset=clouds.preset or "Nothing" + -- Precepitation: 0=None, 1=Rain, 2=Thunderstorm, 3=Snow, 4=Snowstorm. - local precepitation=tonumber(clouds.iprecptns) + local precepitation=0 + if cloudspreset:find("Preset10") then + -- Scattered 5 + clouddens=4 + elseif cloudspreset:find("Preset11") then + -- Scattered 6 + clouddens=4 + elseif cloudspreset:find("Preset12") then + -- Scattered 7 + clouddens=4 + elseif cloudspreset:find("Preset13") then + -- Broken 1 + clouddens=7 + elseif cloudspreset:find("Preset14") then + -- Broken 2 + clouddens=7 + elseif cloudspreset:find("Preset15") then + -- Broken 3 + clouddens=7 + elseif cloudspreset:find("Preset16") then + -- Broken 4 + clouddens=7 + elseif cloudspreset:find("Preset17") then + -- Broken 5 + clouddens=7 + elseif cloudspreset:find("Preset18") then + -- Broken 6 + clouddens=7 + elseif cloudspreset:find("Preset19") then + -- Broken 7 + clouddens=7 + elseif cloudspreset:find("Preset20") then + -- Broken 8 + clouddens=7 + elseif cloudspreset:find("Preset21") then + -- Overcast 1 + clouddens=9 + elseif cloudspreset:find("Preset22") then + -- Overcast 2 + clouddens=9 + elseif cloudspreset:find("Preset23") then + -- Overcast 3 + clouddens=9 + elseif cloudspreset:find("Preset24") then + -- Overcast 4 + clouddens=9 + elseif cloudspreset:find("Preset25") then + -- Overcast 5 + clouddens=9 + elseif cloudspreset:find("Preset26") then + -- Overcast 6 + clouddens=9 + elseif cloudspreset:find("Preset27") then + -- Overcast 7 + clouddens=9 + elseif cloudspreset:find("Preset1") then + -- Light Scattered 1 + clouddens=1 + elseif cloudspreset:find("Preset2") then + -- Light Scattered 2 + clouddens=1 + elseif cloudspreset:find("Preset3") then + -- High Scattered 1 + clouddens=4 + elseif cloudspreset:find("Preset4") then + -- High Scattered 2 + clouddens=4 + elseif cloudspreset:find("Preset5") then + -- Scattered 1 + clouddens=4 + elseif cloudspreset:find("Preset6") then + -- Scattered 2 + clouddens=4 + elseif cloudspreset:find("Preset7") then + -- Scattered 3 + clouddens=4 + elseif cloudspreset:find("Preset8") then + -- High Scattered 3 + clouddens=4 + elseif cloudspreset:find("Preset9") then + -- Scattered 4 + clouddens=4 + elseif cloudspreset:find("RainyPreset") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + end + local CLOUDBASE=string.format("%d", UTILS.MetersToFeet(cloudbase)) local CLOUDCEIL=string.format("%d", UTILS.MetersToFeet(cloudceil)) From e8a21679342f81f9fb08189ba77bb79a8f9fd362 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 15 Apr 2021 13:01:02 +0200 Subject: [PATCH 165/382] Events - Workaround for 2.7 Same as for Event 31 --- Moose Development/Moose/Core/Event.lua | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index c996d9bc7..7bb39a1b7 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -254,7 +254,7 @@ EVENTS = { -- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event: -- -- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. --- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ +-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ -- -- @type EVENTDATA -- @field #number id The identifier of the event. @@ -1115,13 +1115,25 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then + BASE:T({Event = Event}) + --[[ + Event.TgtDCSUnit = Event.target + Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() + Event.TgtCategory = Event.TgtDCSUnit:getDesc().category + Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + --]] + -- Same as for Event Initiator above 2.7 issue Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + local ID=Event.initiator.id_ + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + --Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.IniCoalition + Event.TgtCategory = Event.IniCategory + Event.TgtTypeName = "Ejected Pilot" end if Event.TgtObjectCategory == Object.Category.SCENERY then From b232a5da37c016ebafd2be3225d09bf0b7f7acbd Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 15 Apr 2021 13:02:59 +0200 Subject: [PATCH 166/382] Update Event.lua workaround for 2.7 Same as for Event 31 --- Moose Development/Moose/Core/Event.lua | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index c996d9bc7..7bb39a1b7 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -254,7 +254,7 @@ EVENTS = { -- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event: -- -- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. --- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ +-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ -- -- @type EVENTDATA -- @field #number id The identifier of the event. @@ -1115,13 +1115,25 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then + BASE:T({Event = Event}) + --[[ + Event.TgtDCSUnit = Event.target + Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() + Event.TgtCategory = Event.TgtDCSUnit:getDesc().category + Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + --]] + -- Same as for Event Initiator above 2.7 issue Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + local ID=Event.initiator.id_ + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + --Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.IniCoalition + Event.TgtCategory = Event.IniCategory + Event.TgtTypeName = "Ejected Pilot" end if Event.TgtObjectCategory == Object.Category.SCENERY then From 2693e76483a40deebc7b9df90a9aed2b7c1623cf Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 15 Apr 2021 21:13:35 +0200 Subject: [PATCH 167/382] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 041843d56..2109475a7 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6131,7 +6131,7 @@ function AIRBOSS:_ScanCarrierZone() local putintomarshal=false -- Get flight group. - local flight=_DATABASE:GetFlightGroup(groupname) + local flight=_DATABASE:GetOpsGroup(groupname) if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then if flight.ishelo then From c7ec1b4e5ebb83d3b7704eaa6471c39628ee555d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 16 Apr 2021 12:37:35 +0200 Subject: [PATCH 168/382] Update Group.lua Added code for EnableEmission --- Moose Development/Moose/Wrapper/Group.lua | 53 ++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9c6c0a79c..4ab573ec0 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2169,6 +2169,40 @@ function GROUP:GetThreatLevel() return threatlevelMax end +--- Get the unit in the group with the highest threat level, which is still alive. +-- @param #GROUP self +-- @return Wrapper.Unit#UNIT The most dangerous unit in the group. +-- @return #number Threat level of the unit. +function GROUP:GetHighestThreat() + + -- Get units of the group. + local units=self:GetUnits() + + if units then + + local threat=nil ; local maxtl=0 + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + + -- Threat level of group. + local tl=unit:GetThreatLevel() + + -- Check if greater the current threat. + if tl>maxtl then + maxtl=tl + threat=unit + end + end + end + + return threat, maxtl + end + + return nil, nil +end + --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self @@ -2551,6 +2585,23 @@ do -- Players end +--- GROUND - Switch on/off radar emissions +-- @param #GROUP self +-- @param #boolean switch +function GROUP:EnableEmission(switch) + self:F2( self.GroupName ) + local switch = switch or false + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. @@ -2641,4 +2692,4 @@ end -- -- -- ---end \ No newline at end of file +--end From 9eed35ace7fb2ecc92abf52f49dd915f3a2c8e8a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 16 Apr 2021 12:40:32 +0200 Subject: [PATCH 169/382] Update Unit.lua Added changes for EnableEmission --- Moose Development/Moose/Wrapper/Unit.lua | 30 +++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 1321cba5d..ff48dd7d1 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -63,7 +63,7 @@ -- -- The UNIT class provides methods to obtain the current point or position of the DCS Unit. -- The @{#UNIT.GetPointVec2}(), @{#UNIT.GetVec3}() will obtain the current **location** of the DCS Unit in a Vec2 (2D) or a **point** in a Vec3 (3D) vector respectively. --- If you want to obtain the complete **3D position** including ori�ntation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. +-- If you want to obtain the complete **3D position** including orientation and direction vectors, consult the @{#UNIT.GetPositionVec3}() method respectively. -- -- ## Test if alive -- @@ -527,8 +527,7 @@ end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self --- @return Wrapper.Group#GROUP The Group of the Unit. --- @return #nil The DCS Unit is not existing or alive. +-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. function UNIT:GetGroup() self:F2( self.UnitName ) @@ -1175,8 +1174,9 @@ end --- Returns true if the UNIT is in the air. -- @param #UNIT self +-- @param #boolean NoHeloCheck If true, no additonal checks for helos are performed. -- @return #boolean Return true if in the air or #nil if the UNIT is not existing or alive. -function UNIT:InAir() +function UNIT:InAir(NoHeloCheck) self:F2( self.UnitName ) -- Get DCS unit object. @@ -1186,14 +1186,14 @@ function UNIT:InAir() -- Get DCS result of whether unit is in air or not. local UnitInAir = DCSUnit:inAir() - + -- Get unit category. local UnitCategory = DCSUnit:getDesc().category -- If DCS says that it is in air, check if this is really the case, since we might have landed on a building where inAir()=true but actually is not. -- This is a workaround since DCS currently does not acknoledge that helos land on buildings. -- Note however, that the velocity check will fail if the ground is moving, e.g. on an aircraft carrier! - if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER then + if UnitInAir==true and UnitCategory == Unit.Category.HELICOPTER and (not NoHeloCheck) then local VelocityVec3 = DCSUnit:getVelocity() local Velocity = UTILS.VecNorm(VelocityVec3) local Coordinate = DCSUnit:getPoint() @@ -1393,3 +1393,21 @@ function UNIT:GetTemplateFuel() return nil end + +--- GROUND - Switch on/off radar emissions +-- @param #UNIT self +-- @param #boolean switch +function UNIT:EnableEmission(switch) + self:F2( self.UnitName ) + + local switch = switch or false + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + +end From e7d5fd6c1bd9602b2326588c650337dd7af93753 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 16 Apr 2021 12:46:45 +0200 Subject: [PATCH 170/382] Update DCS.lua enableEmissions --- Moose Development/Moose/DCS.lua | 70 ++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 15c73c29a..9fcac18cb 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -736,7 +736,66 @@ do -- Airbase end -- Airbase +do -- Spot + --- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot) + -- Represents a spot from laser or IR-pointer. + -- @type Spot + -- @field #Spot.Category Category enum that stores spot categories. + + --- Enum that stores spot categories. + -- @type Spot.Category + -- @field #string INFRA_RED + -- @field #string LASER + + + --- Creates a laser ray emanating from the given object to a point in 3d space. + -- @function [parent=#Spot] createLaser + -- @param DCS#Object Source The source object of the laser. + -- @param DCS#Vec3 LocalRef An optional 3D offset for the source. + -- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at. + -- @param #number LaserCode Any 4 digit number between 1111 and 1788. + -- @return #Spot + + --- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles. + -- @function [parent=#Spot] createInfraRed + -- @param DCS#Object Source Source position of the IR ray. + -- @param DCS#Vec3 LocalRef An optional 3D offset for the source. + -- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at. + -- @return #Spot + + --- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin. + -- @function [parent=#Spot] getPoint + -- @param #Spot self + -- @return DCS#Vec3 Point in 3D, where the beam is pointing at. + + --- Sets the destination point from which the source of the spot is drawn toward. + -- @function [parent=#Spot] setPoint + -- @param #Spot self + -- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at. + + --- Returns the number that is used to define the laser code for which laser designation can track. + -- @function [parent=#Spot] getCode + -- @param #Spot self + -- @return #number Code The laser code used. + + --- Sets the number that is used to define the laser code for which laser designation can track. + -- @function [parent=#Spot] setCode + -- @param #Spot self + -- @param #number Code The laser code. Default value is 1688. + + --- Destroys the spot. + -- @function [parent=#Spot] destroy + -- @param #Spot self + + --- Gets the category of the spot (laser or IR). + -- @function [parent=#Spot] getCategory + -- @param #Spot self + -- @return #string Category. + + Spot = {} --#Spot + +end -- Spot do -- Controller --- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. @@ -1093,6 +1152,10 @@ do -- Unit -- @param #Unit self -- @return #Unit.Desc + --- GROUND - Switch on/off radar emissions + -- @function [parent=#Unit] enableEmission + -- @param #Unit self + -- @param #boolean switch Unit = {} --#Unit @@ -1178,6 +1241,11 @@ do -- Group -- @param #Group self -- @return #Controller + --- GROUND - Switch on/off radar emissions + -- @function [parent=#Group] enableEmission + -- @param #Group self + -- @param #boolean switch + Group = {} --#Group end -- Group @@ -1393,4 +1461,4 @@ do -- AI AI = {} --#AI -end -- AI \ No newline at end of file +end -- AI From d040cc0d037d346045a8318d98a85345e576f7a0 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 16 Apr 2021 23:04:59 +0200 Subject: [PATCH 171/382] DCS 2.7 - Added COORDINATE:LineToAll and COORDINATE:CircleToAll - Added altitude parameter to CONTROLLABLE:TaskFireAtPoint - Added emission on/off functions to UNIT --- Moose Development/Moose/Core/Point.lua | 56 +++++++++++++++++++ .../Moose/Wrapper/Controllable.lua | 14 ++++- Moose Development/Moose/Wrapper/Unit.lua | 35 ++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0c722f9af..26aaa9dd7 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1978,6 +1978,62 @@ do -- COORDINATE function COORDINATE:RemoveMark( MarkID ) trigger.action.removeMark( MarkID ) end + + --- Line to all. + -- Creates a line on the F10 map from one point to another. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID which is a number. + function COORDINATE:LineToAll(Endpoint, Coalition, LineType, Color, Alpha, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + trigger.action.lineToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, LineType, ReadOnly, Text or "") + return MarkID + end + + --- Circle to all. + -- Creates a circle on the map with a given radius, color, fill color, and outline. + -- @param #COORDINATE self + -- @param #COORDINATE Center COORDIANTE of the center of the circle. + -- @param #numberr Radius Radius in meters. Default 1000 m. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number FillAlpha Transparency [0,1]. Default 0.5. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID which is a number. + function COORDINATE:CircleToAll(Radius, Coalition, LineType, Color, Alpha, FillColor, FillAlpha, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=self:GetVec3() + Radius=Radius or 1000 + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or {1,0,0} + FillColor[4]=FillAlpha or 0.5 + trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end end -- Markings diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 526a184a4..951342d97 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1431,16 +1431,22 @@ end -- @param DCS#Distance Radius The radius of the zone to deploy the fire at. -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). -- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag +-- @param #number Altitude (Optional) Altitude in meters. +-- @param #number ASL Altitude is above mean sea level. Default is above ground level. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) +function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL ) local DCSTask = { id = 'FireAtPoint', params = { point = Vec2, + x=Vec2.x, + y=Vec2.y, zoneRadius = Radius, + radius = Radius, expendQty = 100, -- dummy value expendQtyEnabled = false, + alt_type = ASL and 0 or 1 } } @@ -1448,10 +1454,16 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end + + if Altitude then + DCSTask.params.altitude=Altitude + end if WeaponType then DCSTask.params.weaponType=WeaponType end + + self:I(DCSTask) return DCSTask end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index ff48dd7d1..1d1df1b63 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -761,6 +761,41 @@ function UNIT:GetFuel() return nil end +--- Sets the passed group or unit objects radar emitters on or off. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. +-- @param #UNIT self +-- @param #boolean Switch If `true` or `nil`, emission is enabled. If `false`, emission is turned off. +-- @return #UNIT self +function UNIT:SetEmission(Switch) + + if Switch==nil then + Switch=true + end + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + DCSUnit:enableEmission(Switch) + end + + return self +end + +--- Sets the passed group or unit objects radar emitters ON. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. +-- @param #UNIT self +-- @return #UNIT self +function UNIT:EnableEmission() + self:SetEmission(true) + return self +end + +--- Sets the passed group or unit objects radar emitters OFF. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. +-- @param #UNIT self +-- @return #UNIT self +function UNIT:DisableEmission() + self:SetEmission(false) + return self +end + --- Returns a list of one @{Wrapper.Unit}. -- @param #UNIT self -- @return #list A list of one @{Wrapper.Unit}. From a7b444dfea6ccab899f7c635252771b0fada6825 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 09:29:55 +0200 Subject: [PATCH 172/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b8f52b52e..a7a75c28f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -340,6 +340,8 @@ OPSGROUP.version="0.7.1" -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. +-- TODO: EPLRS datalink. +-- TODO: Emission on/off. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From e198aae1dd851ee991d8b6481d2b2fb7add2b08d Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 10:28:18 +0200 Subject: [PATCH 173/382] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 001b43f1b..1532cd1b3 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -580,6 +580,7 @@ ATIS.version="0.9.1" -- DONE: Metric units. -- DONE: Set UTC correction. -- DONE: Set magnetic variation. +-- DONE: New DCS 2.7 weather presets. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From 41e0f92cc07413cc631ee2a5c95763a5237a4130 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 19:49:04 +0200 Subject: [PATCH 174/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 1d1df1b63..0a0d50ba3 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1429,7 +1429,7 @@ function UNIT:GetTemplateFuel() return nil end ---- GROUND - Switch on/off radar emissions +--- GROUND - Switch on/off radar emissions. -- @param #UNIT self -- @param #boolean switch function UNIT:EnableEmission(switch) From 38b40e2931eac9632576b65c41cf8f8a0ded9449 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 21:16:34 +0200 Subject: [PATCH 175/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 0a0d50ba3..38033821c 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1162,8 +1162,6 @@ end - - --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 42d253a9cacbe9094970da5f941f7fba6e0e3426 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 21:22:09 +0200 Subject: [PATCH 176/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 38033821c..00060a946 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1162,6 +1162,7 @@ end + --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 6519c30b3a2e81c6c206212d1119b82d9e3981d0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 21:26:26 +0200 Subject: [PATCH 177/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 00060a946..38033821c 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1162,7 +1162,6 @@ end - --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 40415ef93389dc9c931a09839d0dd7306c8864b0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 21:31:55 +0200 Subject: [PATCH 178/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 38033821c..00060a946 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1162,6 +1162,7 @@ end + --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 2289e16d91dbbcb7009eca406e750ee4d86da2e0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 21:45:17 +0200 Subject: [PATCH 179/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 00060a946..0a0d50ba3 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1163,6 +1163,7 @@ end + --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. From 3e470e557d25f86476ecdcddacbd0b8ad01d9435 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 22:19:27 +0200 Subject: [PATCH 180/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 0a0d50ba3..65199d97d 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -154,8 +154,8 @@ function UNIT:Name() return self.UnitName end - ---- @param #UNIT self +--- Get the DCS unit object. +-- @param #UNIT self -- @return DCS#Unit function UNIT:GetDCSObject() From 7106efbc049c17ce587abcf41dc4d021466c0a00 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 22:56:54 +0200 Subject: [PATCH 181/382] Update Unit.lua --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 65199d97d..41b6fed81 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -154,7 +154,7 @@ function UNIT:Name() return self.UnitName end ---- Get the DCS unit object. +--- Get the DCS unit object you want. -- @param #UNIT self -- @return DCS#Unit function UNIT:GetDCSObject() From 4b6aa3b56fa1e9da1eafc29a53c5e4b974153175 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 23:29:31 +0200 Subject: [PATCH 182/382] Update appveyor.yml --- .appveyor/appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 7fcd96a10..9cfb705f8 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -70,7 +70,7 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json + $RequestBody = @{ accountName = 'funkyfranky'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json # get project with last build details $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } From 5aeb48006986c98f04cfbffb637627e8505acad1 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 23:29:53 +0200 Subject: [PATCH 183/382] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 1532cd1b3..569497197 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -581,6 +581,7 @@ ATIS.version="0.9.1" -- DONE: Set UTC correction. -- DONE: Set magnetic variation. -- DONE: New DCS 2.7 weather presets. +-- DONE: ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From 6d53a30b1fef7c13bc0ba1ac97ac3d47239788a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 23:36:14 +0200 Subject: [PATCH 184/382] Update appveyor.yml --- .appveyor/appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 9cfb705f8..6b0baf00f 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -57,7 +57,7 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json + $RequestBody = @{ accountName = 'funkyfranky'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json # Generate the new version ... $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } From 1f949463aaa33c2ed0db2ba09d731ca96c77addc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 17 Apr 2021 23:36:49 +0200 Subject: [PATCH 185/382] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 569497197..91333138d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -581,7 +581,7 @@ ATIS.version="0.9.1" -- DONE: Set UTC correction. -- DONE: Set magnetic variation. -- DONE: New DCS 2.7 weather presets. --- DONE: +-- DONE: whatever ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From 3675ddaf0460c63fdba8282ea337e636ad1394ae Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 18 Apr 2021 12:09:37 +0200 Subject: [PATCH 186/382] Revert "Update Group.lua" --- Moose Development/Moose/Wrapper/Group.lua | 53 +---------------------- 1 file changed, 1 insertion(+), 52 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 4ab573ec0..9c6c0a79c 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2169,40 +2169,6 @@ function GROUP:GetThreatLevel() return threatlevelMax end ---- Get the unit in the group with the highest threat level, which is still alive. --- @param #GROUP self --- @return Wrapper.Unit#UNIT The most dangerous unit in the group. --- @return #number Threat level of the unit. -function GROUP:GetHighestThreat() - - -- Get units of the group. - local units=self:GetUnits() - - if units then - - local threat=nil ; local maxtl=0 - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - if unit and unit:IsAlive() then - - -- Threat level of group. - local tl=unit:GetThreatLevel() - - -- Check if greater the current threat. - if tl>maxtl then - maxtl=tl - threat=unit - end - end - end - - return threat, maxtl - end - - return nil, nil -end - --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self @@ -2585,23 +2551,6 @@ do -- Players end ---- GROUND - Switch on/off radar emissions --- @param #GROUP self --- @param #boolean switch -function GROUP:EnableEmission(switch) - self:F2( self.GroupName ) - local switch = switch or false - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - DCSUnit:enableEmission(switch) - - end - -end - --do -- Smoke -- ----- Signal a flare at the position of the GROUP. @@ -2692,4 +2641,4 @@ end -- -- -- ---end +--end \ No newline at end of file From 8ba3a30278e439aa1787337b792d139f889e4104 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Apr 2021 14:34:36 +0200 Subject: [PATCH 187/382] Update appveyor.yml --- .appveyor/appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 6b0baf00f..7fcd96a10 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -57,7 +57,7 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'funkyfranky'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json + $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json # Generate the new version ... $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } @@ -70,7 +70,7 @@ build_script: "Authorization" = "Bearer $token" "Content-type" = "application/json" } - $RequestBody = @{ accountName = 'funkyfranky'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json + $RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-docs'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json # get project with last build details $project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody } From 91b6da70afa3d7462bdaf435aa0e66ac80431953 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Apr 2021 14:49:01 +0200 Subject: [PATCH 188/382] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 7bb39a1b7..07ddcfdf1 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -254,7 +254,7 @@ EVENTS = { -- Note that at the beginning of each field description, there is an indication which field will be populated depending on the object type involved in the Event: -- -- * A (Object.Category.)UNIT : A UNIT object type is involved in the Event. --- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event.µ +-- * A (Object.Category.)STATIC : A STATIC object type is involved in the Event. -- -- @type EVENTDATA -- @field #number id The identifier of the event. From 9f095a1d280922516706c75e24a44a89dc396d07 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Apr 2021 22:14:51 +0200 Subject: [PATCH 189/382] Update appveyor.yml - Token for my appveyor account. --- .appveyor/appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 7fcd96a10..3649b6c8f 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -52,7 +52,7 @@ build_script: if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { $apiUrl = 'https://ci.appveyor.com/api' - $token = 'qts80b5kpq0ooj4x6vvw' + $token = 'v2.bg2hb0fnxm96dr5f8yb' $headers = @{ "Authorization" = "Bearer $token" "Content-type" = "application/json" @@ -65,7 +65,7 @@ build_script: if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { $apiUrl = 'https://ci.appveyor.com/api' - $token = 'qts80b5kpq0ooj4x6vvw' + $token = 'v2.bg2hb0fnxm96dr5f8yb' $headers = @{ "Authorization" = "Bearer $token" "Content-type" = "application/json" From 2d6545429aadb161ecbfd944b4495e61a5428686 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Apr 2021 22:16:46 +0200 Subject: [PATCH 190/382] Update Unit.lua - Appveyor test --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 41b6fed81..65199d97d 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -154,7 +154,7 @@ function UNIT:Name() return self.UnitName end ---- Get the DCS unit object you want. +--- Get the DCS unit object. -- @param #UNIT self -- @return DCS#Unit function UNIT:GetDCSObject() From 1d6be07cff0fd5172f1f7399f13d0ebfaee306c7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Apr 2021 22:59:20 +0200 Subject: [PATCH 191/382] Update appveyor.yml --- .appveyor/appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 3649b6c8f..687110cf4 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -51,6 +51,7 @@ build_script: - ps: | if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { + echo "Hello World!" $apiUrl = 'https://ci.appveyor.com/api' $token = 'v2.bg2hb0fnxm96dr5f8yb' $headers = @{ From 0577940d58658221262488a11d592cf7848e08fd Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:38:50 +0200 Subject: [PATCH 192/382] Update appveyor.yml --- .appveyor/appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 687110cf4..ad1246a3c 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -53,7 +53,7 @@ build_script: { echo "Hello World!" $apiUrl = 'https://ci.appveyor.com/api' - $token = 'v2.bg2hb0fnxm96dr5f8yb' + $token = 'v2.6hcv3ige78kg3yvg4ge8' $headers = @{ "Authorization" = "Bearer $token" "Content-type" = "application/json" @@ -66,7 +66,7 @@ build_script: if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' ) { $apiUrl = 'https://ci.appveyor.com/api' - $token = 'v2.bg2hb0fnxm96dr5f8yb' + $token = 'v2.6hcv3ige78kg3yvg4ge8' $headers = @{ "Authorization" = "Bearer $token" "Content-type" = "application/json" From 40700a387c273f87e6cbf932e5dc633b9b6dc6da Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:42:24 +0200 Subject: [PATCH 193/382] Update appveyor.yml --- .appveyor/appveyor.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index ad1246a3c..0ab5e1e66 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -27,23 +27,23 @@ init: install: # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. -# - call choco install 7zip.commandline -# - call choco install lua51 -# - call choco install luarocks -# - call refreshenv -# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" -# - cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin -# - cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua -# - cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll -# - call luarocks install luasrcdiet -# - call luarocks install checks -# - call luarocks install luadocumentor -# - call luarocks install luacheck +call choco install 7zip.commandline +call choco install lua51 +call choco install luarocks +call refreshenv +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" +cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin +cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua +cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll +call luarocks install luasrcdiet +call luarocks install checks +call luarocks install luadocumentor +call luarocks install luacheck #cache: -# - C:\ProgramData\chocolatey\lib -# - C:\ProgramData\chocolatey\bin +C:\ProgramData\chocolatey\lib +C:\ProgramData\chocolatey\bin From b2eba4911a2459f4250dbbc8525a57986bb22152 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:44:50 +0200 Subject: [PATCH 194/382] Update appveyor.yml --- .appveyor/appveyor.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 0ab5e1e66..f59be974d 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -27,18 +27,18 @@ init: install: # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. -call choco install 7zip.commandline -call choco install lua51 -call choco install luarocks -call refreshenv -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" +- call choco install 7zip.commandline +- call choco install lua51 +- call choco install luarocks +- call refreshenv +- call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll -call luarocks install luasrcdiet -call luarocks install checks -call luarocks install luadocumentor -call luarocks install luacheck +- call luarocks install luasrcdiet +- call luarocks install checks +- call luarocks install luadocumentor +- call luarocks install luacheck #cache: From 3749686882ef13c1c1e9d747c9f01459c732475c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:46:30 +0200 Subject: [PATCH 195/382] Update appveyor.yml --- .appveyor/appveyor.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index f59be974d..78c43a456 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -26,19 +26,20 @@ init: # - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) install: + - cmd: # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. -- call choco install 7zip.commandline -- call choco install lua51 -- call choco install luarocks -- call refreshenv -- call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" -cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin -cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua -cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll -- call luarocks install luasrcdiet -- call luarocks install checks -- call luarocks install luadocumentor -- call luarocks install luacheck +call choco install 7zip.commandline +# - call choco install lua51 +# - call choco install luarocks +# - call refreshenv +# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" +# cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin +# cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua +# cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll +# - call luarocks install luasrcdiet +# - call luarocks install checks +# - call luarocks install luadocumentor +# - call luarocks install luacheck #cache: From 82da7a8f24fc4c17bc2d4c16c3f60bde44fb8818 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:47:16 +0200 Subject: [PATCH 196/382] Update appveyor.yml --- .appveyor/appveyor.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index 78c43a456..a89bb8662 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -29,17 +29,17 @@ install: - cmd: # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. call choco install 7zip.commandline -# - call choco install lua51 -# - call choco install luarocks -# - call refreshenv -# - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" -# cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin -# cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua -# cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll -# - call luarocks install luasrcdiet -# - call luarocks install checks -# - call luarocks install luadocumentor -# - call luarocks install luacheck +call choco install lua51 +call choco install luarocks +call refreshenv +call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" +cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin +cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua +cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll +call luarocks install luasrcdiet +call luarocks install checks +call luarocks install luadocumentor +call luarocks install luacheck #cache: From 3b2af6ea07ecd68b0b4aa511cfb6ca360f8eb20d Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 19 Apr 2021 20:49:17 +0200 Subject: [PATCH 197/382] Update appveyor.yml --- .appveyor/appveyor.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.appveyor/appveyor.yml b/.appveyor/appveyor.yml index a89bb8662..2af7fd529 100644 --- a/.appveyor/appveyor.yml +++ b/.appveyor/appveyor.yml @@ -28,21 +28,21 @@ init: install: - cmd: # Outcomment if lua environment invalidates and needs to be reinstalled, otherwise all will run from the cache. -call choco install 7zip.commandline -call choco install lua51 -call choco install luarocks -call refreshenv -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" -cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin -cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua -cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll -call luarocks install luasrcdiet -call luarocks install checks -call luarocks install luadocumentor -call luarocks install luacheck + call choco install 7zip.commandline + call choco install lua51 + call choco install luarocks + call refreshenv + call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" + cmd: PATH = %PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\bin + cmd: set LUA_PATH = %LUA_PATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?.lua;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\share\lua\5.1\?\init.lua + cmd: set LUA_CPATH = %LUA_CPATH%;C:\ProgramData\chocolatey\lib\luarocks\luarocks-2.4.3-win32\systree\lib\lua\5.1\?.dll + call luarocks install luasrcdiet + call luarocks install checks + call luarocks install luadocumentor + call luarocks install luacheck -#cache: +cache: C:\ProgramData\chocolatey\lib C:\ProgramData\chocolatey\bin From 8ae6192a5feb5ff6e5c1ba8c47aa479e9d9a3b95 Mon Sep 17 00:00:00 2001 From: IdefixRC Date: Tue, 20 Apr 2021 15:43:45 +0800 Subject: [PATCH 198/382] Create Airbase.lua New Syria Airbases added to list: H4, Gaziantep, Rosh Pina, Sayqal, Shayrat, Tiyas, Tha'lah and Naqoura --- Moose Development/Moose/Wrapper/Airbase.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 317c3b448..df18bc9ef 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -361,6 +361,14 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur +-- * AIRBASE.Syria.H4 +-- * AIRBASE.Syria.Gaziantep +-- * AIRBASE.Syria.Rosh_Pina +-- * AIRBASE.Syria.Sayqal +-- * AIRBASE.Syria.Shayrat +-- * AIRBASE.Syria.Tiyas +-- * AIRBASE.Syria.Tha_lah +-- * AIRBASE.Syria.Naqoura -- -- @field Syria AIRBASE.Syria={ @@ -397,6 +405,14 @@ AIRBASE.Syria={ ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["An_Nasiriyah"]="An Nasiriyah", ["Abu_al_Duhur"]="Abu al-Duhur", + ["H4"]="H4", + ["Gaziantep"]="Gaziantep", + ["Rosh_Pina"]="Rosh Pina", + ["Sayqal"]="Sayqal", + ["Shayrat"]="Shayrat", + ["Tiyas"]="Tiyas", + ["Tha_lah"]="Tha'lah", + ["Naqoura"]="Naqoura", } From 2531a25c9e127059ef49306f6761a0dfba965707 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:36:59 +0200 Subject: [PATCH 199/382] Update Group.lua Add EnableEmissions --- Moose Development/Moose/Wrapper/Group.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9c6c0a79c..1a2f00fa8 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2551,6 +2551,23 @@ do -- Players end +--- GROUND - Switch on/off radar emissions +-- @param #GROUP self +-- @param #boolean switch +function GROUP:EnableEmission(switch) + self:F2( self.GroupName ) + local switch = switch or false + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. @@ -2641,4 +2658,4 @@ end -- -- -- ---end \ No newline at end of file +--end From a8549e2b7d4df7c5f89c1c9110cc8ff41c3c05e6 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:50:00 +0200 Subject: [PATCH 200/382] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index d015bbde9..2f3c1603e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -362,6 +362,14 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur +-- * AIRBASE.Syria.H4 +-- * AIRBASE.Syria.Gaziantep +-- * AIRBASE.Syria.Rosh_Pina +-- * AIRBASE.Syria.Sayqal +-- * AIRBASE.Syria.Shayrat +-- * AIRBASE.Syria.Tiyas +-- * AIRBASE.Syria.Tha_lah +-- * AIRBASE.Syria.Naqoura -- -- @field Syria AIRBASE.Syria={ @@ -398,6 +406,14 @@ AIRBASE.Syria={ ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["An_Nasiriyah"]="An Nasiriyah", ["Abu_al_Duhur"]="Abu al-Duhur", + ["H4"]="H4", + ["Gaziantep"]="Gaziantep", + ["Rosh_Pina"]="Rosh Pina", + ["Sayqal"]="Sayqal", + ["Shayrat"]="Shayrat", + ["Tiyas"]="Tiyas", + ["Tha_lah"]="Tha'lah", + ["Naqoura"]="Naqoura", } From 18fca6461bb6ff7d08b73403475c40689fda84ac Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 20 Apr 2021 17:27:48 +0200 Subject: [PATCH 201/382] Update Group.lua --- Moose Development/Moose/Wrapper/Group.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 9c6c0a79c..1a2f00fa8 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2551,6 +2551,23 @@ do -- Players end +--- GROUND - Switch on/off radar emissions +-- @param #GROUP self +-- @param #boolean switch +function GROUP:EnableEmission(switch) + self:F2( self.GroupName ) + local switch = switch or false + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. @@ -2641,4 +2658,4 @@ end -- -- -- ---end \ No newline at end of file +--end From 19eb9dca30a3c794c902e41ec3a6fe2200fdc004 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 20 Apr 2021 18:49:54 +0200 Subject: [PATCH 202/382] Update Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d53284871..1e9c2597b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -6,7 +6,7 @@ -- -- ### Contributions: -- --- * FlightControl : Rework to OO framework +-- * FlightControl : Rework to OO framework. -- -- @module Utils -- @image MOOSE.JPG From 62c42adebc4a98614174fdf8af1f1666a0ccc0b2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Apr 2021 15:48:54 +0200 Subject: [PATCH 203/382] Update DCS.lua --- Moose Development/Moose/DCS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 9fcac18cb..92052d5cf 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -1226,7 +1226,7 @@ do -- Group -- @param #Group self -- @return #number - --- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function. + --- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed; Initial size limits the unitNumber parameter for Group.getUnit() function. -- @function [parent=#Group] getInitialSize -- @param #Group self -- @return #number From f4f12f786cfb7afd9594a33c5d9eb1c2f1c2c077 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Apr 2021 18:52:44 +0200 Subject: [PATCH 204/382] Update Airboss.lua Escape error --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 12370b7a7..cf5057e81 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -17846,7 +17846,7 @@ function AIRBOSS:onbeforeLoad(From, Event, To, path, filename) -- Check default path. if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.") + self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end -- Set path or default. From a1a414925fb61d52f58d2728f7479cd2b2d71dd5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 08:19:56 +0200 Subject: [PATCH 205/382] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 041843d56..dc3de77a3 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -18037,7 +18037,7 @@ function AIRBOSS:onbeforeLoad(From, Event, To, path, filename) -- Check default path. if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.") + self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end -- Set path or default. From 9d6851adec13578a3fb968bdd98f4a14847917fe Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 08:30:19 +0200 Subject: [PATCH 206/382] Update Airboss.lua Escape error 17933 --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index dc3de77a3..38d04d4ff 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -17930,7 +17930,7 @@ function AIRBOSS:onbeforeSave(From, Event, To, path, filename) -- Check default path. if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.") + self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end return true From 2475f22937e63fc3e3f2b26080a922368a634038 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 10:08:57 +0200 Subject: [PATCH 207/382] Create .luacheckrc --- .luacheckrc | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .luacheckrc diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 000000000..b29cbbc22 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,54 @@ +ignore = { + "011", -- A syntax error. + "021", -- An invalid inline option. + "022", -- An unpaired inline push directive. + "023", -- An unpaired inline pop directive. + "111", -- Setting an undefined global variable. + "112", -- Mutating an undefined global variable. + "113", -- Accessing an undefined global variable. + "121", -- Setting a read-only global variable. + "122", -- Setting a read-only field of a global variable. + "131", -- Unused implicitly defined global variable. + "142", -- Setting an undefined field of a global variable. + "143", -- Accessing an undefined field of a global variable. + "211", -- Unused local variable. + "212", -- Unused argument. + "213", -- Unused loop variable. + "221", -- Local variable is accessed but never set. + "231", -- Local variable is set but never accessed. + "232", -- An argument is set but never accessed. + "233", -- Loop variable is set but never accessed. + "241", -- Local variable is mutated but never accessed. + "311", -- Value assigned to a local variable is unused. + "312", -- Value of an argument is unused. + "313", -- Value of a loop variable is unused. + "314", -- Value of a field in a table literal is unused. + "321", -- Accessing uninitialized local variable. + "331", -- Value assigned to a local variable is mutated but never accessed. + "341", -- Mutating uninitialized local variable. + "411", -- Redefining a local variable. + "412", -- Redefining an argument. + "413", -- Redefining a loop variable. + "421", -- Shadowing a local variable. + "422", -- Shadowing an argument. + "423", -- Shadowing a loop variable. + "431", -- Shadowing an upvalue. + "432", -- Shadowing an upvalue argument. + "433", -- Shadowing an upvalue loop variable. + "511", -- Unreachable code. + "512", -- Loop can be executed at most once. + "521", -- Unused label. + "531", -- Left-hand side of an assignment is too short. + "532", -- Left-hand side of an assignment is too long. + "541", -- An empty do end block. + "542", -- An empty if branch. + "551", -- An empty statement. + "561", -- Cyclomatic complexity of a function is too high. + "571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step. + "611", -- A line consists of nothing but whitespace. + "612", -- A line contains trailing whitespace. + "613", -- Trailing whitespace in a string. + "614", -- Trailing whitespace in a comment. + "621", -- Inconsistent indentation (SPACE followed by TAB). + "631", -- Line is too long. +} From 0259e41ce8f194737d4cddadd49bd1534bc152e7 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 10:10:59 +0200 Subject: [PATCH 208/382] Create .luacheckrc --- .luacheckrc | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .luacheckrc diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 000000000..b29cbbc22 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,54 @@ +ignore = { + "011", -- A syntax error. + "021", -- An invalid inline option. + "022", -- An unpaired inline push directive. + "023", -- An unpaired inline pop directive. + "111", -- Setting an undefined global variable. + "112", -- Mutating an undefined global variable. + "113", -- Accessing an undefined global variable. + "121", -- Setting a read-only global variable. + "122", -- Setting a read-only field of a global variable. + "131", -- Unused implicitly defined global variable. + "142", -- Setting an undefined field of a global variable. + "143", -- Accessing an undefined field of a global variable. + "211", -- Unused local variable. + "212", -- Unused argument. + "213", -- Unused loop variable. + "221", -- Local variable is accessed but never set. + "231", -- Local variable is set but never accessed. + "232", -- An argument is set but never accessed. + "233", -- Loop variable is set but never accessed. + "241", -- Local variable is mutated but never accessed. + "311", -- Value assigned to a local variable is unused. + "312", -- Value of an argument is unused. + "313", -- Value of a loop variable is unused. + "314", -- Value of a field in a table literal is unused. + "321", -- Accessing uninitialized local variable. + "331", -- Value assigned to a local variable is mutated but never accessed. + "341", -- Mutating uninitialized local variable. + "411", -- Redefining a local variable. + "412", -- Redefining an argument. + "413", -- Redefining a loop variable. + "421", -- Shadowing a local variable. + "422", -- Shadowing an argument. + "423", -- Shadowing a loop variable. + "431", -- Shadowing an upvalue. + "432", -- Shadowing an upvalue argument. + "433", -- Shadowing an upvalue loop variable. + "511", -- Unreachable code. + "512", -- Loop can be executed at most once. + "521", -- Unused label. + "531", -- Left-hand side of an assignment is too short. + "532", -- Left-hand side of an assignment is too long. + "541", -- An empty do end block. + "542", -- An empty if branch. + "551", -- An empty statement. + "561", -- Cyclomatic complexity of a function is too high. + "571", -- A numeric for loop goes from #(expr) down to 1 or less without negative step. + "611", -- A line consists of nothing but whitespace. + "612", -- A line contains trailing whitespace. + "613", -- Trailing whitespace in a string. + "614", -- Trailing whitespace in a comment. + "621", -- Inconsistent indentation (SPACE followed by TAB). + "631", -- Line is too long. +} From 5091ed7d6d564ba1764ef79ab59c1eb38f6e6198 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 10:26:02 +0200 Subject: [PATCH 209/382] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index cf5057e81..a6924682d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -17739,7 +17739,7 @@ function AIRBOSS:onbeforeSave(From, Event, To, path, filename) -- Check default path. if path==nil and not lfs then - self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\DCS\" folder.") + self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") end return true From aa13583aa7bfc4b817dc477e71746c8d56adca61 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:39:35 +0200 Subject: [PATCH 210/382] Update README.md added badge --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7ba71d91c..976a47f96 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build status](https://ci.appveyor.com/api/projects/status/1y8nfmx7lwsn33tt?svg=true)](https://ci.appveyor.com/project/Applevangelist/MOOSE) + # MOOSE framework MOOSE is a **M**ission **O**bject **O**riented **S**cripting **E**nvironment, and is meant for mission designers in DCS World. @@ -50,9 +52,7 @@ This repository contains all the demonstration missions in packed format (*.miz) This repository contains all the demonstration missions in unpacked format. That means that there is no .miz file included, but all the .miz contents are unpacked. - - - + ## [MOOSE Web Site](https://flightcontrol-master.github.io/MOOSE_DOCS/) Documentation on the MOOSE class hierarchy, usage guides and background information can be found here for normal users, beta testers and contributors. From 9c6c2ec5ca735bf315d2d299fe6614762806a7e9 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:46:37 +0200 Subject: [PATCH 211/382] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5e42c0c93..fe5b6b4f8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ local.properties # External tool builders .externalToolBuilders/ +# AppVeyor +.appveyor/ # CDT-specific .cproject From 459ff8038ba8ad2866a30a0006b0b91ad3e22c1a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 23 Apr 2021 09:51:58 +0200 Subject: [PATCH 212/382] Update Controllable.lua Some Docu updates --- Moose Development/Moose/Wrapper/Controllable.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 951342d97..b07d09046 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2910,7 +2910,7 @@ end --- Set option for Rules of Engagement (ROE). -- @param Wrapper.Controllable#CONTROLLABLE self -- @param #number ROEvalue ROE value. See ENUMS.ROE. --- @return Wrapper.Controllable#CONTROLLABLE self +-- @return #CONTROLLABLE self function CONTROLLABLE:OptionROE(ROEvalue) local DCSControllable = self:GetDCSObject() @@ -2952,8 +2952,8 @@ function CONTROLLABLE:OptionROEHoldFirePossible() end --- Weapons Hold: AI will hold fire under all circumstances. --- @param Wrapper.Controllable#CONTROLLABLE self --- @return Wrapper.Controllable#CONTROLLABLE self +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self function CONTROLLABLE:OptionROEHoldFire() self:F2( { self.ControllableName } ) @@ -3551,7 +3551,7 @@ end -- Note that when WayPointInitialize is called, the Mission of the controllable is RESTARTED! -- @param #CONTROLLABLE self -- @param #table WayPoints If WayPoints is given, then use the route. --- @return #CONTROLLABLE +-- @return #CONTROLLABLE self function CONTROLLABLE:WayPointInitialize( WayPoints ) self:F( { WayPoints } ) @@ -3582,7 +3582,7 @@ end -- @param #number WayPoint The waypoint number. Note that the start waypoint on the route is WayPoint 1! -- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. -- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. --- @return #CONTROLLABLE +-- @return #CONTROLLABLE self function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) @@ -3598,7 +3598,7 @@ end -- @param #CONTROLLABLE self -- @param #number WayPoint The WayPoint from where to execute the mission. -- @param #number WaitTime The amount seconds to wait before initiating the mission. --- @return #CONTROLLABLE +-- @return #CONTROLLABLE self function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) self:F( { WayPoint, WaitTime } ) @@ -3682,6 +3682,7 @@ end --- Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self -- @param #number range Defines the range +-- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack function CONTROLLABLE:OptionAAAttackRange(range) self:F2( { self.ControllableName } ) @@ -3733,6 +3734,7 @@ end -- @param #number radius Radius of the relocation zone, default 500 -- @param #boolean onroad If true, route on road (less problems with AI way finding), default true -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false +-- @return #CONTROLLABLE self function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) self:F2( { self.ControllableName } ) From 41274e7801710de87fdc1c50379f54f494b87190 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 23 Apr 2021 10:59:22 +0200 Subject: [PATCH 213/382] Update Controllable.lua --- .../Moose/Wrapper/Controllable.lua | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5db0632ab..dc004b4c1 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3753,3 +3753,24 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc return self end + +--- Defines how long a GROUND unit/group will move to avoid an ongoing attack. +-- @param #CONTROLLABLE self +-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionDisperseOnAttack(Seconds) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + local seconds = Seconds or 0 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + end + end + return self + end + return nil +end From bbb21749578b6692a44910e59e878196f82d1c5a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 23 Apr 2021 12:00:49 +0200 Subject: [PATCH 214/382] Update Controllable.lua added `OptionDisperseOnAttack` for GROUND units --- .../Moose/Wrapper/Controllable.lua | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b07d09046..55fd34f12 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3758,3 +3758,24 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc return self end + +--- Defines how long a GROUND unit/group will move to avoid an ongoing attack. +-- @param #CONTROLLABLE self +-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionDisperseOnAttack(Seconds) + self:F2( { self.ControllableName } ) + -- Set default if not specified. + local seconds = Seconds or 0 + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + if Controller then + if self:IsGround() then + self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + end + end + return self + end + return nil +end From dc64b98c59b733fc2e9db111581b0ba068c2bdf2 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 23 Apr 2021 13:27:24 +0200 Subject: [PATCH 215/382] Update Act_Route.lua - Fixed bug in ACT_GROUP:GetRouteText --- Moose Development/Moose/Actions/Act_Route.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Actions/Act_Route.lua b/Moose Development/Moose/Actions/Act_Route.lua index f2c5ebcda..7f075b420 100644 --- a/Moose Development/Moose/Actions/Act_Route.lua +++ b/Moose Development/Moose/Actions/Act_Route.lua @@ -190,7 +190,7 @@ do -- ACT_ROUTE self:F( { ZoneName = ZoneName } ) local Zone = Zone -- Core.Zone#ZONE local ZoneCoord = Zone:GetCoordinate() - local ZoneDistance = ZoneCoord:Get2DDistance( self.Coordinate ) + local ZoneDistance = ZoneCoord:Get2DDistance( Coordinate ) self:F( { ShortestDistance, ShortestReferenceName } ) if ShortestDistance == 0 or ZoneDistance < ShortestDistance then ShortestDistance = ZoneDistance From 57f6096aa1aa8418a934a65ea7cc2a9da74359dc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 25 Apr 2021 20:24:41 +0200 Subject: [PATCH 216/382] Update Controllable.lua (#1518) Error line 1466 --- Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 55fd34f12..e2d3edca6 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1463,7 +1463,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.weaponType=WeaponType end - self:I(DCSTask) + --self:I(DCSTask) return DCSTask end From a466365949414f17e26acea5c30961922bea4168 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 25 Apr 2021 20:31:02 +0200 Subject: [PATCH 217/382] Update Controllable.lua (#1519) Error line 1463 --- Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 0e98f49e4..165835229 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1460,7 +1460,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.weaponType=WeaponType end - self:I(DCSTask) + --self:I(DCSTask) return DCSTask end From 990f748e42863cf749d4cb1851eb42718d1ff31b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 27 Apr 2021 09:19:33 +0200 Subject: [PATCH 218/382] Update Database.lua --- Moose Development/Moose/Core/Database.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 35077c05b..ef3547338 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1544,7 +1544,7 @@ function DATABASE:_RegisterTemplates() if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) + self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) else From d5bf28f799e40a6285541513014e8b7f17badb38 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 29 Apr 2021 18:20:03 +0200 Subject: [PATCH 219/382] Update AirWing.lua (#1521) Markpoint honored for direct calls from #OPSGROUP --- Moose Development/Moose/Ops/AirWing.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 706133f4b..202eb631d 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -721,11 +721,13 @@ end --- Update marker of the patrol point. -- @param #AIRWING.PatrolData point Patrol point table. -function AIRWING.UpdatePatrolPointMarker(point) +function AIRWING:UpdatePatrolPointMarker(point) + if self.markpoints then -- sometimes there's a direct call from #OPSGROUP local text=string.format("%s Occupied=%d\nheading=%03d, leg=%d NM, alt=%d ft, speed=%d kts", point.type, point.noccupied, point.heading, point.leg, point.altitude, point.speed) point.marker:UpdateText(text, 1) + end end From 421005a3b6ed4a4eeebbfb1a2605e57bc6f019e8 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 30 Apr 2021 11:53:57 +0200 Subject: [PATCH 220/382] event backfixed --- Moose Development/Moose/Core/Event.lua | 6 ++++-- Moose Development/Moose/Ops/FlightGroup.lua | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..9b5251f46 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1114,9 +1114,10 @@ function EVENT:onEvent( Event ) Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() end + env.info("FF I am here first") if Event.TgtObjectCategory == Object.Category.STATIC then + env.info("FF I am here") BASE:T({Event = Event}) - --[[ Event.TgtDCSUnit = Event.target Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtUnitName = Event.TgtDCSUnitName @@ -1124,8 +1125,8 @@ function EVENT:onEvent( Event ) Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - --]] -- Same as for Event Initiator above 2.7 issue + --[[ Event.TgtDCSUnit = Event.target local ID=Event.initiator.id_ Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) @@ -1134,6 +1135,7 @@ function EVENT:onEvent( Event ) Event.TgtCoalition = Event.IniCoalition Event.TgtCategory = Event.IniCategory Event.TgtTypeName = "Ejected Pilot" + ]] end if Event.TgtObjectCategory == Object.Category.SCENERY then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ff0f49aa4..c4b916448 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -56,6 +56,7 @@ -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. +-- @field #number RTBRecallCount Number that counts RTB calls. -- -- @extends Ops.OpsGroup#OPSGROUP @@ -145,6 +146,7 @@ FLIGHTGROUP = { Twaiting = nil, menu = nil, isHelo = nil, + RTBRecallCount = 0, } From 2e66a854b1b849786d0490d80ece8d9d83dd8338 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 1 May 2021 00:55:43 +0200 Subject: [PATCH 221/382] Events and Templates --- Moose Development/Moose/Core/Event.lua | 74 +++++-- Moose Development/Moose/DCS.lua | 11 ++ Moose Development/Moose/Modules.lua | 1 + .../Moose/Utilities/Templates.lua | 183 ++++++++++++++++++ Moose Setup/Moose.files | 1 + 5 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 Moose Development/Moose/Utilities/Templates.lua diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..e243d2266 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -236,11 +236,11 @@ EVENTS = { RemoveUnit = world.event.S_EVENT_REMOVE_UNIT, PlayerEnterAircraft = world.event.S_EVENT_PLAYER_ENTER_AIRCRAFT, -- Added with DCS 2.5.6 - DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier - Kill = world.event.S_EVENT_KILL or -1, - Score = world.event.S_EVENT_SCORE or -1, - UnitLost = world.event.S_EVENT_UNIT_LOST or -1, - LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, + DetailedFailure = world.event.S_EVENT_DETAILED_FAILURE or -1, --We set this to -1 for backward compatibility to DCS 2.5.5 and earlier + Kill = world.event.S_EVENT_KILL or -1, + Score = world.event.S_EVENT_SCORE or -1, + UnitLost = world.event.S_EVENT_UNIT_LOST or -1, + LandingAfterEjection = 31, --world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, -- Added with DCS 2.7.0 ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1, DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1, @@ -524,7 +524,7 @@ local _EVENTMETA = { Event = "OnEventUnitLost", Text = "S_EVENT_UNIT_LOST" }, - [EVENTS.LandingAfterEjection] = { + [world.event.S_EVENT_LANDING_AFTER_EJECTION] = { Order = 1, Event = "OnEventLandingAfterEjection", Text = "S_EVENT_LANDING_AFTER_EJECTION" @@ -578,6 +578,10 @@ function EVENT:New() -- Add world event handler. self.EventHandler = world.addEventHandler(self) + for _,Event in pairs(self.Events) do + self:Init(Event,EventClass) + end + return self end @@ -588,7 +592,9 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:F3( { _EVENTMETA[EventID].Text, EventClass } ) + self:I( { _EVENTMETA[EventID].Text, EventClass } ) + + env.info("FF EVENT.Init ID="..EventID) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -983,6 +989,8 @@ end -- @param #EVENTDATA Event Event data table. function EVENT:onEvent( Event ) + env.info("FF some event") + local ErrorHandler = function( errmsg ) env.info( "Error in SCHEDULER function:" .. errmsg ) @@ -1000,17 +1008,37 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - if self and - self.Events and - self.Events[Event.id] and - self.MissionEnd == false and - ( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then + env.info("FF some event 100 ID="..tostring(Event.id)) + + if Event.id==31 then + env.info("FF got event 31") + local initiator=Event.initiator~=nil + env.info(string.format("FF got event 31 initiator=%s", tostring(initiator))) + if self then + env.info(string.format("FF got event 31 self=true")) + end + if self.Events then + env.info(string.format("FF got event 31 self.Events=true")) + end + if self.Events[Event.id] then + env.info(string.format("FF got event 31 self.Events[Event.id]=true")) + else + env.info(string.format("FF NO event 31 self.Events[Event.id]=FALSE!")) + end + end + + --if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true end - if Event.initiator then + env.info("FF some event 150") + + if Event.initiator then + + env.info("FF some event 200") Event.IniObjectCategory = Event.initiator:getCategory() @@ -1039,9 +1067,14 @@ function EVENT:onEvent( Event ) end if Event.IniObjectCategory == Object.Category.STATIC then + + env.info("FF some event 300") + if Event.id==31 then - --env.info("FF event 31") - -- Event.initiator is a Static object representing the pilot. But getName() error due to DCS bug. + + env.info("FF event 31") + + -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator local ID=Event.initiator.id_ Event.IniDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) @@ -1049,6 +1082,17 @@ function EVENT:onEvent( Event ) Event.IniCoalition = 0 Event.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" + + local static=Event.initiator + local vec3=static:getPoint() + + local template=TEMPLATE.GetGround(TEMPLATE.Ground.SoldierM4, "Ejected Pilot", country.id.USA, vec3) + local group=_DATABASE:Spawn(template) + Event.IniDCSGroup=group:GetDCSObject() + Event.IniCoalition=group:GetCoalition() + Event.IniCategory=group:GetCategory() + + else Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 92052d5cf..01661a678 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -295,6 +295,17 @@ do -- country -- @field QATAR -- @field OMAN -- @field UNITED_ARAB_EMIRATES + -- @field SOUTH_AFRICA + -- @field CUBA + -- @field PORTUGAL + -- @field GDR + -- @field LEBANON + -- @field CJTF_BLUE + -- @field CJTF_RED + -- @field UN_PEACEKEEPERS + -- @field Argentinia + -- @field Cyprus + -- @field Slovenia country = {} --#country diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 62d483052..c4bb9eb87 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -2,6 +2,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua new file mode 100644 index 000000000..51885c4a9 --- /dev/null +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -0,0 +1,183 @@ +--- **Utils** Templates +-- +-- DCS unit templates +-- +-- @module Utilities.Templates +-- @image MOOSE.JPG + +--- TEMPLATE class. +-- @type TEMPLATE +-- @field #string ClassName Name of the class. + +--- *Templates* +-- +-- === +-- +-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) +-- +-- Get DCS templates from thin air. +-- +-- # Ground Units +-- +-- Ground units. +-- +-- # Naval Units +-- +-- Ships are not implemented yet. +-- +-- # Aircraft +-- +-- ## Airplanes +-- +-- Airplanes are not implemented yet. +-- +-- ## Helicopters +-- +-- Helicopters are not implemented yet. +-- +-- @field #TEMPLATE +TEMPLATE = { + ClassName = "TEMPLATE", + Ground = {}, + Naval = {}, + Airplane = {}, + Helicopter = {}, +} + +--- Pattern steps. +-- @type TEMPLATE.Ground +-- @param #string InfantryAK +TEMPLATE.Ground={ + InfantryAK="Infantry AK", + ParatrooperAKS74="Paratrooper AKS-74", + ParatrooperRPG16="Paratrooper RPG-16", + SoldierWWIIUS="soldier_wwii_us", + InfantryM248="Infantry M249", + SoldierM4="Soldier M4", +} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start/Stop Profiler +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for ground units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 50 m. +-- @return #table Template Template table. +function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + local template=UTILS.DeepCopy(TEMPLATE.GenericGround) + + template.name=GroupName or "Ground-1" + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=country.id.USA + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + template.CategoryID=Unit.Category.GROUND_UNIT + + template.units[1].type=TypeName or "Infantry AK" + template.units[1].name=GroupName.."-1" + + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + return template +end + +TEMPLATE.GenericGround= +{ + ["visible"] = false, + ["tasks"] = {}, -- end of ["tasks"] + ["uncontrollable"] = false, + ["task"] = "Ground Nothing", + ["route"] = + { + ["spans"] = {}, -- end of ["spans"] + ["points"] = + { + [1] = + { + ["alt"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["alt_type"] = "BARO", + ["formation_template"] = "", + ["y"] = 0, + ["x"] = 0, + ["ETA_locked"] = true, + ["speed"] = 0, + ["action"] = "Off Road", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["transportable"] = + { + ["randomTransportable"] = false, + }, -- end of ["transportable"] + ["skill"] = "Average", + ["type"] = "Infantry AK", + ["unitId"] = nil, + ["y"] = 0, + ["x"] = 0, + ["name"] = "Infantry AK-47 Rus", + ["heading"] = 0, + ["playerCanDrive"] = false, + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Infantry AK-47 Rus", + ["start_time"] = 0, +} + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. +function TEMPLATE.SetPositionFromVec2(Template, Vec2) + + Template.x=Vec2.x + Template.y=Vec2.y + + for _,unit in pairs(Template.units) do + unit.x=Vec2.x + unit.y=Vec2.y + end + + Template.route.points[1].x=Vec2.x + Template.route.points[1].y=Vec2.y + Template.route.points[1].alt=0 --TODO: Use land height. + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec3 Vec3 Position vector of the group. +function TEMPLATE.SetPositionFromVec3(Template, Vec3) + + local Vec2={x=Vec3.x, y=Vec3.z} + + TEMPLATE.SetPositionFromVec2(Template, Vec2) + +end \ No newline at end of file diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index f64bb2ff3..79c9b120e 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -3,6 +3,7 @@ Utilities/Routines.lua Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua +Utilities/Templates.lua Core/Base.lua Core/UserFlag.lua From 7da1905ad3fe7cf0944c11755476920842d27d4e Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 14:22:01 +0200 Subject: [PATCH 222/382] Update Event.lua (#1525) Updates for Events 33 and 6 - makes scoring also work again for STATIC targets --- Moose Development/Moose/Core/Event.lua | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..9d10bdbbc 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1049,6 +1049,14 @@ function EVENT:onEvent( Event ) Event.IniCoalition = 0 Event.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" + elseif Event.id == 33 then -- ejection seat discarded + Event.IniDCSUnit = Event.initiator + local ID=Event.initiator.id_ + Event.IniDCSUnitName = string.format("Ejection Seat ID %s", tostring(ID)) + Event.IniUnitName = Event.IniDCSUnitName + Event.IniCoalition = 0 + Event.IniCategory = 0 + Event.IniTypeName = "Ejection Seat" else Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() @@ -1115,25 +1123,34 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then - BASE:T({Event = Event}) - --[[ - Event.TgtDCSUnit = Event.target + BASE:T({StaticTgtEvent = Event.id}) + -- get base data + Event.TgtDCSUnit = Event.target + if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - --]] - -- Same as for Event Initiator above 2.7 issue - Event.TgtDCSUnit = Event.target - local ID=Event.initiator.id_ - Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) - Event.TgtUnitName = Event.TgtDCSUnitName - --Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.IniCoalition - Event.TgtCategory = Event.IniCategory - Event.TgtTypeName = "Ejected Pilot" + else + Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = nil + Event.TgtCoalition = 0 + Event.TgtCategory = 0 + if Event.id == 6 then + Event.TgtTypeName = "Ejected Pilot" + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + elseif Event.id == 33 then + Event.TgtTypeName = "Ejection Seat" + Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + else + Event.TgtTypeName = "Static" + end + end end if Event.TgtObjectCategory == Object.Category.SCENERY then From 2bcc531e73c1abcad861577510a98b02272721cc Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 14:26:03 +0200 Subject: [PATCH 223/382] Update Event.lua (#1526) Updates for Event 33 and 6 - also makes Scoring work again --- Moose Development/Moose/Core/Event.lua | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 07ddcfdf1..9d10bdbbc 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1049,6 +1049,14 @@ function EVENT:onEvent( Event ) Event.IniCoalition = 0 Event.IniCategory = 0 Event.IniTypeName = "Ejected Pilot" + elseif Event.id == 33 then -- ejection seat discarded + Event.IniDCSUnit = Event.initiator + local ID=Event.initiator.id_ + Event.IniDCSUnitName = string.format("Ejection Seat ID %s", tostring(ID)) + Event.IniUnitName = Event.IniDCSUnitName + Event.IniCoalition = 0 + Event.IniCategory = 0 + Event.IniTypeName = "Ejection Seat" else Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() @@ -1115,25 +1123,34 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then - BASE:T({Event = Event}) - --[[ - Event.TgtDCSUnit = Event.target + BASE:T({StaticTgtEvent = Event.id}) + -- get base data + Event.TgtDCSUnit = Event.target + if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() Event.TgtCategory = Event.TgtDCSUnit:getDesc().category Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() - --]] - -- Same as for Event Initiator above 2.7 issue - Event.TgtDCSUnit = Event.target - local ID=Event.initiator.id_ - Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(ID)) - Event.TgtUnitName = Event.TgtDCSUnitName - --Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.IniCoalition - Event.TgtCategory = Event.IniCategory - Event.TgtTypeName = "Ejected Pilot" + else + Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = nil + Event.TgtCoalition = 0 + Event.TgtCategory = 0 + if Event.id == 6 then + Event.TgtTypeName = "Ejected Pilot" + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + elseif Event.id == 33 then + Event.TgtTypeName = "Ejection Seat" + Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + else + Event.TgtTypeName = "Static" + end + end end if Event.TgtObjectCategory == Object.Category.SCENERY then From 596eb4c7e4744ce27fa124227d977711e2385e11 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:13:07 +0200 Subject: [PATCH 224/382] Update Sead.lua Adjust to Event changes --- Moose Development/Moose/Functional/Sead.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 2c5abb451..420ec1a7e 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -109,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.5") + self:I("*** SEAD - Started Version 0.2.7") return self end @@ -205,15 +205,18 @@ function SEAD:OnEventShot( EventData ) SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired --]] if self:_CheckHarms(SEADWeaponName) then - + local _targetskill = "Random" + local _targetMimgroupName = "none" local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) -- Unit name - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted group - local _targetMimgroupName = _targetMimgroup:getName() -- group name - local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) + local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + if _targetUnit and _targetUnit:IsAlive() then + local _targetMimgroup = _targetUnit:GetGroup() + local _targetMimgroupName = _targetMimgroup:GetName() -- group name + --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + self:T( self.SEADGroupPrefixes ) + self:T( _targetMimgroupName ) + end -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do @@ -249,6 +252,7 @@ function SEAD:OnEventShot( EventData ) local range = self.EngagementRange -- Feature Request #1355 self:T(string.format("*** SEAD - Engagement Range is %d", range)) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + --id.groupName:enableEmission(true) id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 self.SuppressedGroups[id.groupName] = nil --delete group id from table when done end @@ -261,6 +265,7 @@ function SEAD:OnEventShot( EventData ) SuppressionEndTime = delay } Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --_targetMimgroup:enableEmission(false) timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function end end From 9a7df65faa5d82611a87aee03187052de41a21b2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:18:01 +0200 Subject: [PATCH 225/382] Update Shorad.lua Adjust to Event changes --- Moose Development/Moose/Functional/Shorad.lua | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 155a86829..e2f3991f8 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -18,7 +18,7 @@ -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- --- Date: Feb 2021 +-- Date: May 2021 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -38,7 +38,7 @@ -- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles -- @field #number DefenseLowProb Default 70, minimum detection limit -- @field #number DefenseHighProb Default 90, maximim detection limit --- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default). +-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green. -- @extends Core.Base#BASE --- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) @@ -96,7 +96,7 @@ SHORAD = { DefendMavs = true, DefenseLowProb = 70, DefenseHighProb = 90, - UseAIOnOff = false, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -176,8 +176,8 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseAIOnOff = false -- Decide if we are using AI on/off (true) or AlarmState red/green (default) - self:I("*** SHORAD - Started Version 0.1.0") + self.UseEmOnOff = false -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.1") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -192,8 +192,9 @@ do self:T({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do - if self.UseAIOnOff then - _group:SetAIOff() + if self.UseEmOnOff then + --_group:SetAIOff() + _group:EnableEmission(false) else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP end @@ -279,11 +280,11 @@ do self.Radius = radius end - --- Set using AI on/off instead of changing alarm state + --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state - function SHORAD:SetUsingAIOnOff(switch) - self.UseAIOnOff = switch or false + function SHORAD:SetUsingEmOnOff(switch) + self.UseEmOnOff = switch or false end --- Check if a HARM was fired @@ -410,8 +411,9 @@ do local function SleepShorad(group) local groupname = group:GetName() self.ActiveGroups[groupname] = nil - if self.UseAIOnOff then - group:SetAIOff() + if self.UseEmOnOff then + group:EnableEmission(false) + --group:SetAIOff() else group:OptionAlarmStateGreen() end @@ -425,8 +427,9 @@ do local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - if self.UseAIOnOff then + if self.UseEmOnOff then _group:SetAIOn() + _group:EnableEmission(true) end _group:OptionAlarmStateRed() local groupname = _group:GetName() @@ -461,23 +464,31 @@ do end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) self:T( text ) - local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) + local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target - local targetunitname = Unit.getName(targetdata) -- Unit name - local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group - local targetgroupname = targetgroup:getName() -- group name - -- check if we or a SAM site are the target - --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP - local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean - local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean - -- if being shot at, find closest SHORADs to activate - if shotatsams or shotatus then - self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) - end + local targetunit = UNIT:Find(targetdata) + --local targetunitname = Unit.getName(targetdata) -- Unit name + if targetunit and targetunit:IsAlive() then + local targetunitname = targetunit:GetName() + --local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group + local targetgroup = targetunit:GetGroup() + local targetgroupname = targetgroup:GetName() -- group name + local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) + self:T( text ) + local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) + -- check if we or a SAM site are the target + --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP + local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean + local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean + -- if being shot at, find closest SHORADs to activate + if shotatsams or shotatus then + self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + end + end end end end From 725e32f43940ca5d6652452293dee7306038b6be Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:19:10 +0200 Subject: [PATCH 226/382] Update Sead.lua Adjust to Event changes --- Moose Development/Moose/Functional/Sead.lua | 23 +++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 2c5abb451..be1ea4b9f 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -109,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.5") + self:I("*** SEAD - Started Version 0.2.7") return self end @@ -205,15 +205,18 @@ function SEAD:OnEventShot( EventData ) SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired --]] if self:_CheckHarms(SEADWeaponName) then - + local _targetskill = "Random" + local _targetMimgroupName = "none" local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetMimname = Unit.getName(_targetMim) -- Unit name - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted group - local _targetMimgroupName = _targetMimgroup:getName() -- group name - local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) + local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + if _targetUnit and _targetUnit:IsAlive() then + local _targetMimgroup = _targetUnit:GetGroup() + local _targetMimgroupName = _targetMimgroup:GetName() -- group name + --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + self:T( self.SEADGroupPrefixes ) + self:T( _targetMimgroupName ) + end -- see if we are shot at local SEADGroupFound = false for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do @@ -249,6 +252,7 @@ function SEAD:OnEventShot( EventData ) local range = self.EngagementRange -- Feature Request #1355 self:T(string.format("*** SEAD - Engagement Range is %d", range)) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + id.groupName:enableEmission(true) id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 self.SuppressedGroups[id.groupName] = nil --delete group id from table when done end @@ -260,7 +264,8 @@ function SEAD:OnEventShot( EventData ) self.SuppressedGroups[id.groupName] = { SuppressionEndTime = delay } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + _targetMimgroup:enableEmission(false) timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function end end From 32027026127e3acb6fff5c3afcebc7090d9ab103 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:20:35 +0200 Subject: [PATCH 227/382] Update Shorad.lua Adjustments for 2.7 --- Moose Development/Moose/Functional/Shorad.lua | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 155a86829..2e61d7743 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -18,7 +18,7 @@ -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- --- Date: Feb 2021 +-- Date: May 2021 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -38,7 +38,7 @@ -- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles -- @field #number DefenseLowProb Default 70, minimum detection limit -- @field #number DefenseHighProb Default 90, maximim detection limit --- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default). +-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green. -- @extends Core.Base#BASE --- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) @@ -96,7 +96,7 @@ SHORAD = { DefendMavs = true, DefenseLowProb = 70, DefenseHighProb = 90, - UseAIOnOff = false, + UseEmOnOff = true, } ----------------------------------------------------------------------- @@ -176,8 +176,8 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseAIOnOff = false -- Decide if we are using AI on/off (true) or AlarmState red/green (default) - self:I("*** SHORAD - Started Version 0.1.0") + self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.1") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -192,8 +192,9 @@ do self:T({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do - if self.UseAIOnOff then - _group:SetAIOff() + if self.UseEmOnOff then + --_group:SetAIOff() + _group:EnableEmission(false) else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP end @@ -279,11 +280,11 @@ do self.Radius = radius end - --- Set using AI on/off instead of changing alarm state + --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state - function SHORAD:SetUsingAIOnOff(switch) - self.UseAIOnOff = switch or false + function SHORAD:SetUsingEmOnOff(switch) + self.UseEmOnOff = switch or false end --- Check if a HARM was fired @@ -410,8 +411,9 @@ do local function SleepShorad(group) local groupname = group:GetName() self.ActiveGroups[groupname] = nil - if self.UseAIOnOff then - group:SetAIOff() + if self.UseEmOnOff then + group:EnableEmission(false) + --group:SetAIOff() else group:OptionAlarmStateGreen() end @@ -425,8 +427,9 @@ do local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) - if self.UseAIOnOff then + if self.UseEmOnOff then _group:SetAIOn() + _group:EnableEmission(true) end _group:OptionAlarmStateRed() local groupname = _group:GetName() @@ -461,23 +464,31 @@ do end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) self:T( text ) - local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) + local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target - local targetunitname = Unit.getName(targetdata) -- Unit name - local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group - local targetgroupname = targetgroup:getName() -- group name - -- check if we or a SAM site are the target - --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP - local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean - local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean - -- if being shot at, find closest SHORADs to activate - if shotatsams or shotatus then - self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) - end + local targetunit = UNIT:Find(targetdata) + --local targetunitname = Unit.getName(targetdata) -- Unit name + if targetunit and targetunit:IsAlive() then + local targetunitname = targetunit:GetName() + --local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group + local targetgroup = targetunit:GetGroup() + local targetgroupname = targetgroup:GetName() -- group name + local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) + self:T( text ) + local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) + -- check if we or a SAM site are the target + --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP + local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean + local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean + -- if being shot at, find closest SHORADs to activate + if shotatsams or shotatus then + self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + end + end end end end From a89a3b38ce51a867e1a016f6d525682f37dcff6b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:21:53 +0200 Subject: [PATCH 228/382] Update Mantis.lua Adjustments for 2.7 --- Moose Development/Moose/Functional/Mantis.lua | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4d6565262..23faabb3f 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -51,7 +51,7 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit --- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default) +-- @field #boolean UseEmOnOff Decide if we are using Emissions on/off (true) or AlarmState red/green (default) -- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range @@ -191,7 +191,7 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseAIOnOff = false, + UseEmOnOff = true, } ----------------------------------------------------------------------- @@ -208,7 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean AIOnOff Make MANTIS switch AI on and off instead of changing the alarm state between RED and GREEN (optional) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -230,7 +230,7 @@ do -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- - function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, AIOnOff) + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff) -- DONE: Create some user functions for these -- DONE: Make HQ useful @@ -264,7 +264,11 @@ do self.ShoradTime = 600 self.ShoradActDistance = 15000 -- TODO: add emissions on/off when available .... in 2 weeks - self.UseAIOnOff = AIOnOff or false + if EmOnOff then + if EmOnOff == false then + self.UseEmOnOff = false + end + end if type(awacs) == "string" then self.advAwacs = true @@ -304,7 +308,7 @@ do end -- @field #string version - self.version="0.4.0" + self.version="0.4.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self @@ -463,11 +467,11 @@ do end end - --- Set using AI on/off instead of changing alarm state + --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self - -- @param #boolean switch Decide if we are changing alarm state or AI state - function MANTIS:SetUsingAIOnOff(switch) - self.UseAIOnOff = switch or false + -- @param #boolean switch Decide if we are changing alarm state or Emission state + function MANTIS:SetUsingEmOnOff(switch) + self.UseEmOnOff = switch or false end --- [Internal] Function to check if HQ is alive @@ -714,8 +718,9 @@ do for _i,_group in pairs (SAM_Grps) do local group = _group -- TODO: add emissions on/off - if self.UseAIOnOff then - group:SetAIOff() + if self.UseEmOnOff then + group:EnableEmission(false) + --group:SetAIOff() else group:OptionAlarmStateGreen() -- AI off end @@ -822,9 +827,10 @@ do if IsInZone then --check any target in zone if samgroup:IsAlive() then -- switch on SAM - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOn() + --samgroup:SetAIOn() + samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() -- link in to SHORAD if available @@ -843,9 +849,10 @@ do else if samgroup:IsAlive() then -- switch off SAM - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOff() + samgroup:EnableEmission(false) + --samgroup:SetAIOff() else samgroup:OptionAlarmStateGreen() end @@ -883,9 +890,10 @@ do local name = _data[1] local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOn() + --samgroup:SetAIOn() + samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() end -- end alive From 39d3f85b712eb894e5b79ebec9f7d5011fe6731e Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 1 May 2021 17:23:17 +0200 Subject: [PATCH 229/382] Update Mantis.lua Adjustments for 2.7 --- Moose Development/Moose/Functional/Mantis.lua | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 4d6565262..23faabb3f 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -51,7 +51,7 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit --- @field #boolean UseAIOnOff Decide if we are using AI on/off (true) or AlarmState red/green (default) +-- @field #boolean UseEmOnOff Decide if we are using Emissions on/off (true) or AlarmState red/green (default) -- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range @@ -191,7 +191,7 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseAIOnOff = false, + UseEmOnOff = true, } ----------------------------------------------------------------------- @@ -208,7 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean AIOnOff Make MANTIS switch AI on and off instead of changing the alarm state between RED and GREEN (optional) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -230,7 +230,7 @@ do -- `mybluemantis = MANTIS:New("bluemantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")` -- `mybluemantis:Start()` -- - function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, AIOnOff) + function MANTIS:New(name,samprefix,ewrprefix,hq,coaltion,dynamic,awacs, EmOnOff) -- DONE: Create some user functions for these -- DONE: Make HQ useful @@ -264,7 +264,11 @@ do self.ShoradTime = 600 self.ShoradActDistance = 15000 -- TODO: add emissions on/off when available .... in 2 weeks - self.UseAIOnOff = AIOnOff or false + if EmOnOff then + if EmOnOff == false then + self.UseEmOnOff = false + end + end if type(awacs) == "string" then self.advAwacs = true @@ -304,7 +308,7 @@ do end -- @field #string version - self.version="0.4.0" + self.version="0.4.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self @@ -463,11 +467,11 @@ do end end - --- Set using AI on/off instead of changing alarm state + --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self - -- @param #boolean switch Decide if we are changing alarm state or AI state - function MANTIS:SetUsingAIOnOff(switch) - self.UseAIOnOff = switch or false + -- @param #boolean switch Decide if we are changing alarm state or Emission state + function MANTIS:SetUsingEmOnOff(switch) + self.UseEmOnOff = switch or false end --- [Internal] Function to check if HQ is alive @@ -714,8 +718,9 @@ do for _i,_group in pairs (SAM_Grps) do local group = _group -- TODO: add emissions on/off - if self.UseAIOnOff then - group:SetAIOff() + if self.UseEmOnOff then + group:EnableEmission(false) + --group:SetAIOff() else group:OptionAlarmStateGreen() -- AI off end @@ -822,9 +827,10 @@ do if IsInZone then --check any target in zone if samgroup:IsAlive() then -- switch on SAM - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOn() + --samgroup:SetAIOn() + samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() -- link in to SHORAD if available @@ -843,9 +849,10 @@ do else if samgroup:IsAlive() then -- switch off SAM - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOff() + samgroup:EnableEmission(false) + --samgroup:SetAIOff() else samgroup:OptionAlarmStateGreen() end @@ -883,9 +890,10 @@ do local name = _data[1] local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then - if self.UseAIOnOff then + if self.UseEmOnOff then -- TODO: add emissions on/off - samgroup:SetAIOn() + --samgroup:SetAIOn() + samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() end -- end alive From d1f3e3f4bbd654d6e4c990622a5e1e2b62afd9d2 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 May 2021 00:08:17 +0200 Subject: [PATCH 230/382] Update Templates.lua --- .../Moose/Utilities/Templates.lua | 220 +++++++++++++++--- 1 file changed, 194 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua index 51885c4a9..df7c3ff0b 100644 --- a/Moose Development/Moose/Utilities/Templates.lua +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -89,6 +89,42 @@ function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius return template end + + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. +function TEMPLATE.SetPositionFromVec2(Template, Vec2) + + Template.x=Vec2.x + Template.y=Vec2.y + + for _,unit in pairs(Template.units) do + unit.x=Vec2.x + unit.y=Vec2.y + end + + Template.route.points[1].x=Vec2.x + Template.route.points[1].y=Vec2.y + Template.route.points[1].alt=0 --TODO: Use land height. + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param DCS#Vec3 Vec3 Position vector of the group. +function TEMPLATE.SetPositionFromVec3(Template, Vec3) + + local Vec2={x=Vec3.x, y=Vec3.z} + + TEMPLATE.SetPositionFromVec2(Template, Vec2) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ground Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + TEMPLATE.GenericGround= { ["visible"] = false, @@ -152,32 +188,164 @@ TEMPLATE.GenericGround= ["start_time"] = 0, } ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec2 Vec2 2D Position vector with x and y components of the group. -function TEMPLATE.SetPositionFromVec2(Template, Vec2) +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ship Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Template.x=Vec2.x - Template.y=Vec2.y - - for _,unit in pairs(Template.units) do - unit.x=Vec2.x - unit.y=Vec2.y - end - - Template.route.points[1].x=Vec2.x - Template.route.points[1].y=Vec2.y - Template.route.points[1].alt=0 --TODO: Use land height. - -end +TEMPLATE.GenericNaval= +{ + ["visible"] = false, + ["tasks"] = {}, -- end of ["tasks"] + ["uncontrollable"] = false, + ["route"] = + { + ["points"] = + { + [1] = + { + ["alt"] = 0, + ["type"] = "Turning Point", + ["ETA"] = 0, + ["alt_type"] = "BARO", + ["formation_template"] = "", + ["y"] = 0, + ["x"] = 0, + ["ETA_locked"] = true, + ["speed"] = 0, + ["action"] = "Turning Point", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["transportable"] = + { + ["randomTransportable"] = false, + }, -- end of ["transportable"] + ["skill"] = "Average", + ["type"] = "TICONDEROG", + ["unitId"] = nil, + ["y"] = 0, + ["x"] = 0, + ["name"] = "Naval-1-1", + ["heading"] = 0, + ["modulation"] = 0, + ["frequency"] = 127500000, + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Naval-1", + ["start_time"] = 0, +} ---- Set the position of the template. --- @param #table Template The template to be modified. --- @param DCS#Vec3 Vec3 Position vector of the group. -function TEMPLATE.SetPositionFromVec3(Template, Vec3) +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Generic Ship Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +TEMPLATE.GenericHelicopter= +{ + ["modulation"] = 0, + ["tasks"] = {}, -- end of ["tasks"] + ["radioSet"] = false, + ["task"] = "Nothing", + ["uncontrolled"] = false, + ["taskSelected"] = true, + ["route"] = + { + ["points"] = + { + [1] = + { + ["alt"] = 10, + ["action"] = "From Parking Area", + ["alt_type"] = "BARO", + ["speed"] = 41.666666666667, + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + }, -- end of ["tasks"] + }, -- end of ["params"] + }, -- end of ["task"] + ["type"] = "TakeOffParking", + ["ETA"] = 0, + ["ETA_locked"] = true, + ["y"] = 618351.087765, + ["x"] = -356168.27327001, + ["formation_template"] = "", + ["airdromeId"] = 22, + ["speed_locked"] = true, + }, -- end of [1] + }, -- end of ["points"] + }, -- end of ["route"] + ["groupId"] = nil, + ["hidden"] = false, + ["units"] = + { + [1] = + { + ["alt"] = 10, + ["alt_type"] = "BARO", + ["livery_id"] = "USA X Black", + ["skill"] = "High", + ["parking"] = "4", + ["ropeLength"] = 15, + ["speed"] = 41.666666666667, + ["type"] = "AH-1W", + ["unitId"] = 8, + ["psi"] = 0, + ["parking_id"] = "10", + ["x"] = -356168.27327001, + ["name"] = "Rotary-1-1", + ["payload"] = + { + ["pylons"] = + { + }, -- end of ["pylons"] + ["fuel"] = "1250.0", + ["flare"] = 30, + ["chaff"] = 30, + ["gun"] = 100, + }, -- end of ["payload"] + ["y"] = 618351.087765, + ["heading"] = 0, + ["callsign"] = + { + [1] = 2, + [2] = 1, + [3] = 1, + ["name"] = "Springfield11", + }, -- end of ["callsign"] + ["onboard_num"] = "050", + }, -- end of [1] + }, -- end of ["units"] + ["y"] = 0, + ["x"] = 0, + ["name"] = "Rotary-1", + ["communication"] = true, + ["start_time"] = 0, + ["frequency"] = 127.5, +} +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - local Vec2={x=Vec3.x, y=Vec3.z} - - TEMPLATE.SetPositionFromVec2(Template, Vec2) - -end \ No newline at end of file From d0fb4d44efe19075b45e053c3355aeb4db3384b5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 2 May 2021 11:28:50 +0200 Subject: [PATCH 231/382] Update Sead.lua test GH Desktop --- Moose Development/Moose/Functional/Sead.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 420ec1a7e..be481e01d 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -16,9 +16,15 @@ -- === -- -- ### Authors: **FlightControl**, **applevangelist** +<<<<<<< Updated upstream -- -- Last Update: Feb 2021 -- +======= +-- +-- Last Update: April 2021 +-- +>>>>>>> Stashed changes -- === -- -- @module Functional.Sead From c045ba0c6d48769e183e5a5c8c60da6b925f8e02 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 2 May 2021 11:35:30 +0200 Subject: [PATCH 232/382] Update Sead.lua --- Moose Development/Moose/Functional/Sead.lua | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index be481e01d..03b8ea47b 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -16,15 +16,9 @@ -- === -- -- ### Authors: **FlightControl**, **applevangelist** -<<<<<<< Updated upstream -- --- Last Update: Feb 2021 --- -======= --- -- Last Update: April 2021 -- ->>>>>>> Stashed changes -- === -- -- @module Functional.Sead From 5034005f6bfe0d85d4be9ffa4c6a1db724fbb35a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 2 May 2021 15:29:42 +0200 Subject: [PATCH 233/382] slightly higher landing speed --- Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 79f664a12..3a83a48ae 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -277,7 +277,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) self:F( { Helicopter:GetName(), Height = Helicopter:GetHeight( true ), Velocity = Helicopter:GetVelocityKMH() } ) if self.RoutePickup == true then - if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 10 then + if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then --self:Load( Helicopter:GetPointVec2() ) self:Load( self.PickupZone ) self.RoutePickup = false @@ -285,7 +285,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) end if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 10 then + if Helicopter:GetHeight( true ) <= 5.5 and Helicopter:GetVelocityKMH() < 15 then self:Unload( self.DeployZone ) self.RouteDeploy = false end From b2d87a87804f39d04b87c57218bd587a459c29ac Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 3 May 2021 09:27:56 +0200 Subject: [PATCH 234/382] Update Set.lua correct typo in function name --- Moose Development/Moose/Core/Set.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c8cb5dd05..6aa72d67e 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3267,7 +3267,7 @@ do -- SET_STATIC -- @param #SET_STATIC self -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean - function SET_STATIC:IsPatriallyInZone( Zone ) + function SET_STATIC:IsPartiallyInZone( Zone ) local IsPartiallyInZone = false From a3decc33c9f3a7915e1a71e62c0a3e57d69e218c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 3 May 2021 17:30:18 +0200 Subject: [PATCH 235/382] Update Shorad.lua Add option to defend a SET_STATIC - e.g. group of bunkers or other static objects. --- Moose Development/Moose/Functional/Shorad.lua | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index e2f3991f8..5dc7e769f 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -159,7 +159,7 @@ do -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) local self = BASE:Inherit( self, BASE:New() ) - self:F({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() @@ -176,8 +176,8 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseEmOnOff = false -- Decide if we are using Emission on/off (default) or AlarmState red/green - self:I("*** SHORAD - Started Version 0.2.1") + self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.5") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() @@ -209,7 +209,7 @@ do -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) function SHORAD:SwitchDebug(debug) - self:F( { debug } ) + self:T( { debug } ) local onoff = debug or false if debug then self.debug = true @@ -226,7 +226,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:F( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff end @@ -235,7 +235,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:F( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff end @@ -245,7 +245,7 @@ do -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:F( { low, high } ) + self:T( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -292,7 +292,7 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:F( { WeaponName } ) + self:T( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -307,7 +307,7 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:F( { WeaponName } ) + self:T( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -352,6 +352,7 @@ do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1) then returnname = true + _groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive end end return returnname @@ -364,7 +365,8 @@ do function SHORAD:_CheckShotAtSams(TargetGroupName) local tgtgrp = TargetGroupName local shorad = self.Samset - local shoradset = shorad:GetAliveSet() --#table + --local shoradset = shorad:GetAliveSet() --#table + local shoradset = shorad:GetSet() --#table local returnname = false for _,_groups in pairs (shoradset) do local groupname = _groups:GetName() @@ -393,6 +395,7 @@ do -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active + -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC -- @usage Use this function to integrate with other systems, example -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() @@ -401,10 +404,21 @@ do -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() - function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) - self:F({TargetGroup, Radius, ActiveTimer}) - local targetgroup = GROUP:FindByName(TargetGroup) - local targetzone = ZONE_GROUP:New("Shorad",targetgroup,Radius) -- create a defense zone to check + function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) + self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + local targetcat = TargetCat or Object.Category.UNIT + local targetgroup = TargetGroup + local targetvec2 = nil + if targetcat == Object.Category.UNIT then + targetvec2 = GROUP:FindByName(targetgroup):GetVec2() + elseif targetcat == Object.Category.STATIC then + targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2() + else + local samset = self.Samset + local sam = samset:GetRandom() + targetvec2 = sam:GetVec2() + end + local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check local groupset = self.Groupset --Core.Set#SET_GROUP local shoradset = groupset:GetAliveSet() --#table -- local function to switch off shorad again @@ -446,7 +460,7 @@ do -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set function SHORAD:OnEventShot( EventData ) - self:F( { EventData } ) + self:T( { EventData } ) --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName @@ -469,13 +483,27 @@ do if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target - local targetunit = UNIT:Find(targetdata) + local targetcat = targetdata:getCategory() -- Identify category + self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + local targetunit = nil + if targetcat == Object.Category.UNIT then -- UNIT + targetunit = UNIT:Find(targetdata) + elseif targetcat == Object.Category.STATIC then -- STATIC + targetunit = STATIC:Find(targetdata) + end --local targetunitname = Unit.getName(targetdata) -- Unit name if targetunit and targetunit:IsAlive() then local targetunitname = targetunit:GetName() --local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group - local targetgroup = targetunit:GetGroup() - local targetgroupname = targetgroup:GetName() -- group name + local targetgroup = nil + local targetgroupname = "none" + if targetcat == Object.Category.UNIT then + targetgroup = targetunit:GetGroup() + targetgroupname = targetgroup:GetName() -- group name + elseif targetcat == Object.Category.STATIC then + targetgroup = targetunit + targetgroupname = targetunitname + end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) @@ -486,7 +514,7 @@ do -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end end end From ce7e152e138ffd364edf0893faca1fd4246d66a4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 4 May 2021 12:59:21 +0200 Subject: [PATCH 236/382] Updated Shorad and Set --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Functional/Shorad.lua | 184 ++++++++++-------- 2 files changed, 107 insertions(+), 79 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c8cb5dd05..6aa72d67e 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3267,7 +3267,7 @@ do -- SET_STATIC -- @param #SET_STATIC self -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean - function SET_STATIC:IsPatriallyInZone( Zone ) + function SET_STATIC:IsPartiallyInZone( Zone ) local IsPartiallyInZone = false diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 2d808a2ba..8a25cb198 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -1,20 +1,20 @@ --- **Functional** -- Short Range Air Defense System --- +-- -- === --- +-- -- **SHORAD** - Short Range Air Defense System -- Controls a network of short range air/missile defense groups. --- +-- -- === --- +-- -- ## Missions: -- -- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense) --- +-- -- === --- +-- -- ### Author : **applevangelist ** --- +-- -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- @@ -26,7 +26,7 @@ -- @field #string ClassName -- @field #string name Name of this Shorad -- @field #boolean debug Set the debug state --- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} +-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} -- @field #number Radius Shorad defense radius in meters -- @field Core.Set#SET_GROUP Groupset The set of Shorad groups -- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend @@ -41,10 +41,10 @@ -- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green. -- @extends Core.Base#BASE ---- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) --- +--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) +-- -- Simple Class for a more intelligent Short Range Air Defense System --- +-- -- #SHORAD -- Moose derived missile intercepting short range defense system. -- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy. @@ -52,26 +52,26 @@ -- -- ## Usage -- --- Set up a #SET_GROUP for the SAM sites to be protected: --- --- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` --- +-- Set up a #SET_GROUP for the SAM sites to be protected: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` +-- -- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%. --- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. --- +-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. +-- -- ### Start a new SHORAD system, parameters are: +-- +-- * Name: Name of this SHORAD. +-- * ShoradPrefix: Filter for the Shorad #SET_GROUP. +-- * Samset: The #SET_GROUP of SAM sites to defend. +-- * Radius: Defense radius in meters. +-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. +-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* +-- +-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` -- --- * Name: Name of this SHORAD. --- * ShoradPrefix: Filter for the Shorad #SET_GROUP. --- * Samset: The #SET_GROUP of SAM sites to defend. --- * Radius: Defense radius in meters. --- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. --- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* --- --- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` --- --- ## Customize options --- +-- ## Customize options +-- -- * SHORAD:SwitchDebug(debug) -- * SHORAD:SwitchHARMDefense(onoff) -- * SHORAD:SwitchAGMDefense(onoff) @@ -96,7 +96,7 @@ SHORAD = { DefendMavs = true, DefenseLowProb = 70, DefenseHighProb = 90, - UseEmOnOff = false, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -137,7 +137,7 @@ do ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- TODO complete list? -- @field Mavs SHORAD.Mavs = { @@ -148,7 +148,7 @@ do ["Kh31"] = "Kh31", ["Kh66"] = "Kh66", } - + --- Instantiates a new SHORAD object -- @param #SHORAD self -- @param #string Name Name of this SHORAD @@ -157,10 +157,10 @@ do -- @param #number Radius Defense radius in meters, used to switch on groups -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" - function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) local self = BASE:Inherit( self, BASE:New() ) - self:F({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) - + self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() self.name = Name or "MyShorad" @@ -176,14 +176,14 @@ do self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseEmOnOff = false -- Decide if we are using Emission on/off (default) or AlarmState red/green - self:I("*** SHORAD - Started Version 0.2.1") + self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.5") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() return self end - + --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() @@ -204,12 +204,12 @@ do math.random() end end - + --- Switch debug state -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) function SHORAD:SwitchDebug(debug) - self:F( { debug } ) + self:T( { debug } ) local onoff = debug or false if debug then self.debug = true @@ -221,31 +221,31 @@ do BASE:TraceOff() end end - + --- Switch defense for HARMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:F( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff end - + --- Switch defense for AGMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:F( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff end - + --- Set defense probability limits -- @param #SHORAD self -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:F( { low, high } ) + self:T( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -257,7 +257,7 @@ do self.DefenseLowProb = low self.DefenseHighProb = high end - + --- Set the number of seconds a SHORAD site will stay active -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active @@ -271,7 +271,7 @@ do --- Set the number of meters for the SHORAD defense zone -- @param #SHORAD self - -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active + -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) local radius = meters or 20000 if radius < 0 then @@ -279,20 +279,20 @@ do end self.Radius = radius end - + --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) self.UseEmOnOff = switch or false end - + --- Check if a HARM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:F( { WeaponName } ) + self:T( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -301,13 +301,13 @@ do end return hit end - + --- Check if an AGM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:F( { WeaponName } ) + self:T( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -316,7 +316,7 @@ do end return hit end - + --- Check the coalition of the attacker -- @param #SHORAD self -- @param #string Coalition name @@ -324,7 +324,7 @@ do function SHORAD:_CheckCoalition(Coalition) local owncoalition = self.Coalition local othercoalition = "" - if Coalition == 0 then + if Coalition == 0 then othercoalition = "neutral" elseif Coalition == 1 then othercoalition = "red" @@ -338,7 +338,7 @@ do return false end end - + --- Check if the missile is aimed at a SHORAD -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group @@ -352,11 +352,12 @@ do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1) then returnname = true + _groups:RelocateGroundRandomInRadius(7,125,false,false) -- be a bit evasive end end - return returnname + return returnname end - + --- Check if the missile is aimed at a SAM site -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group @@ -364,7 +365,8 @@ do function SHORAD:_CheckShotAtSams(TargetGroupName) local tgtgrp = TargetGroupName local shorad = self.Samset - local shoradset = shorad:GetAliveSet() --#table + --local shoradset = shorad:GetAliveSet() --#table + local shoradset = shorad:GetSet() --#table local returnname = false for _,_groups in pairs (shoradset) do local groupname = _groups:GetName() @@ -374,7 +376,7 @@ do end return returnname end - + --- Calculate if the missile shot is detected -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false @@ -387,24 +389,36 @@ do end return IsDetected end - + --- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds -- @param #SHORAD self -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active - -- @usage Use this function to integrate with other systems, example - -- + -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC + -- @usage Use this function to integrate with other systems, example + -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() -- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") -- myshorad:SwitchDebug(true) -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() - function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) - self:F({TargetGroup, Radius, ActiveTimer}) - local targetgroup = GROUP:FindByName(TargetGroup) - local targetzone = ZONE_GROUP:New("Shorad",targetgroup,Radius) -- create a defense zone to check + function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) + self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + local targetcat = TargetCat or Object.Category.UNIT + local targetgroup = TargetGroup + local targetvec2 = nil + if targetcat == Object.Category.UNIT then + targetvec2 = GROUP:FindByName(targetgroup):GetVec2() + elseif targetcat == Object.Category.STATIC then + targetvec2 = STATIC:FindByName(targetgroup,false):GetVec2() + else + local samset = self.Samset + local sam = samset:GetRandom() + targetvec2 = sam:GetVec2() + end + local targetzone = ZONE_RADIUS:New("Shorad",targetvec2,Radius) -- create a defense zone to check local groupset = self.Groupset --Core.Set#SET_GROUP local shoradset = groupset:GetAliveSet() --#table -- local function to switch off shorad again @@ -441,13 +455,13 @@ do end end end - + --- Main function - work on the EventData -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set function SHORAD:OnEventShot( EventData ) - self:F( { EventData } ) - + self:T( { EventData } ) + --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -459,7 +473,7 @@ do local IsDetected = self:_ShotIsDetected() -- convert to text local DetectedText = "false" - if IsDetected then + if IsDetected then DetectedText = "true" end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) @@ -469,31 +483,45 @@ do if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target - local targetunit = UNIT:Find(targetdata) + local targetcat = targetdata:getCategory() -- Identify category + self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + local targetunit = nil + if targetcat == Object.Category.UNIT then -- UNIT + targetunit = UNIT:Find(targetdata) + elseif targetcat == Object.Category.STATIC then -- STATIC + targetunit = STATIC:Find(targetdata) + end --local targetunitname = Unit.getName(targetdata) -- Unit name if targetunit and targetunit:IsAlive() then local targetunitname = targetunit:GetName() --local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group - local targetgroup = targetunit:GetGroup() - local targetgroupname = targetgroup:GetName() -- group name + local targetgroup = nil + local targetgroupname = "none" + if targetcat == Object.Category.UNIT then + targetgroup = targetunit:GetGroup() + targetgroupname = targetgroup:GetName() -- group name + elseif targetcat == Object.Category.STATIC then + targetgroup = targetunit + targetgroupname = targetunitname + end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) - -- check if we or a SAM site are the target + -- check if we or a SAM site are the target --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then self:T({shotatsams=shotatsams,shotatus=shotatus}) - self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end - end + end end end - end + end -- end ----------------------------------------------------------------------- -- SHORAD end ------------------------------------------------------------------------ +----------------------------------------------------------------------- \ No newline at end of file From 0046533d53028a0ba44e3ef18559e73894139e47 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 5 May 2021 11:44:17 +0200 Subject: [PATCH 237/382] Added base class for action based on marker additions, changes, deletions and text keywords --- .../Moose/Core/MarkerOps_Base.lua | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 Moose Development/Moose/Core/MarkerOps_Base.lua diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua new file mode 100644 index 000000000..aa6bc30b5 --- /dev/null +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -0,0 +1,253 @@ +--- **Core** - MarkerOps_Base. +-- +-- **Main Features:** +-- +-- * Create an easy way to tap into markers added to the F10 map by users. +-- * Recognize own tag and list of keywords. +-- * Matched keywords are handed down to functions. +-- +-- === +-- +-- ### Author: **Applevangelist** +-- +-- Date: May 2021 +-- +-- === +--- +-- @module Core.MarkerOps_Base +-- @image Wrapper_Marker.png + +-------------------------------------------------------------------------- +-- MARKEROPS_BASE Class Definition. +-------------------------------------------------------------------------- + +--- MARKEROPS_BASE class. +-- @type MARKEROPS_BASE +-- @field #string ClassName Name of the class. +-- @field #string Tag Tag to identify commands. +-- @field #table Keywords Table of keywords to recognize. +-- @field #string version Version of #MARKEROPS_BASE. +-- @extends Core.Fsm#FSM + +--- *Fiat lux.* -- Latin proverb. +-- +-- === +-- +-- ![Banner Image](..\Presentations\MARKER\Marker_Main.jpg) +-- +-- # The MARKEROPS_BASE Concept +-- +-- This class enable scripting text-based actions from markers. +-- +-- @field #MARKEROPS_BASE +MARKEROPS_BASE = { + ClassName = "MARKEROPS", + Tag = "mytag", + Keywords = {}, + version = "0.0.1", + debug = false, +} + +--- Function to instantiate a new #MARKEROPS_BASE object. +-- @param #MARKEROPS_BASE self +-- @param #string Tagname Name to identify us from the event text. +-- @param #table Keywords Table of keywords recognized from the event text. +-- @return #MARKEROPS_BASE self +function MARKEROPS_BASE:New(Tagname,Keywords) + -- Inherit FSM + local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE + + -- Set some string id for output to DCS.log file. + self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version)) + + self.Tag = Tagname or "mytag"-- #string + self.Keywords = Keywords or {} -- #table + self.debug = false + + ----------------------- + --- FSM Transitions --- + ----------------------- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start the FSM. + self:AddTransition("*", "MarkAdded", "*") -- Start the FSM. + self:AddTransition("*", "MarkChanged", "*") -- Start the FSM. + self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM. + self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM. + + self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark) + self:HandleEvent(EVENTS.MarkChange, self.OnEventMark) + self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark) + + -- start + self:I(self.lid..string.format("started for %s",self.Tag)) + self:__Start(1) + return self + + ------------------- + -- PSEUDO Functions + ------------------- + + --- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. + + --- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. + + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. + -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + + --- "Stop" trigger. Used to stop the function an unhandle events + -- @function [parent=#MARKEROPS_BASE] Stop + +end + +--- (internal) Handle events. +-- @param #MARKEROPS self +-- @param Core.Event#EVENTDATA Event +function MARKEROPS_BASE:OnEventMark(Event) + self:T({Event}) + if Event == nil or Event.idx == nil then + self:E("Skipping onEvent. Event or Event.idx unknown.") + return true + end + --position + local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z} + local coord=COORDINATE:NewFromVec3(vec3) + if self.debug then + local coordtext = coord:ToStringLLDDM() + local text = tostring(Event.text) + local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() + end + -- decision + if Event.id==world.event.S_EVENT_MARK_ADDED then + self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos}) + -- Handle event + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkAdded(Eventtext,matchtable,coord) + end + end + elseif Event.id==world.event.S_EVENT_MARK_CHANGE then + self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos}) + -- Handle event. + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + local matchtable = self:_MatchKeywords(Eventtext) + self:MarkChanged(Eventtext,matchtable,coord) + end + end + elseif Event.id==world.event.S_EVENT_MARK_REMOVED then + self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos}) + -- Hande event. + local Eventtext = tostring(Event.text) + if Eventtext~=nil then + if self:_MatchTag(Eventtext) then + self:MarkDeleted() + end + end + end +end + +--- (internal) Match tag. +-- @param #MARKEROPS self +-- @param #string Eventtext Text added to the marker. +-- @return #boolean +function MARKEROPS_BASE:_MatchTag(Eventtext) + local matches = false + local type = string.lower(self.Tag) -- #string + if string.find(string.lower(Eventtext),type) then + matches = true --event text contains tag + end + return matches +end + +--- (internal) Match keywords table. +-- @param #MARKEROPS self +-- @param #string Eventtext Text added to the marker. +-- @return #table +function MARKEROPS_BASE:_MatchKeywords(Eventtext) + local matchtable = {} + local keytable = self.Keywords + for _index,_word in pairs (keytable) do + if string.find(string.lower(Eventtext),string.lower(_word))then + table.insert(matchtable,_word) + end + end + return matchtable +end + +--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. +function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord) + self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end + +--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state + -- @param #string Text The text on the marker + -- @param #table Keywords Table of matching keywords found in the Event text + -- @param Core.Point#COORDINATE Coord Coordinate of the marker. +function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord) + self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) +end + +--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state +function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To) + self:T({self.lid,From,Event,To}) +end + +--- On enter "Stopped" event. Unsubscribe events. + -- @param #MARKEROPS_BASE self + -- @param #string From The From state + -- @param #string Event The Event called + -- @param #string To The To state +function MARKEROPS_BASE:onenterStopped(From,Event,To) + self:T({self.lid,From,Event,To}) + -- unsubscribe from events + self:UnHandleEvent(EVENTS.MarkAdded) + self:UnHandleEvent(EVENTS.MarkChange) + self:UnHandleEvent(EVENTS.MarkRemoved) +end + +-------------------------------------------------------------------------- +-- MARKEROPS_BASE Class Definition End. +-------------------------------------------------------------------------- \ No newline at end of file From 2046958bd6f1468b60ebfebf299fe8352384f73c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 5 May 2021 11:44:35 +0200 Subject: [PATCH 238/382] Added MarkerOps_Base.lua --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index decc5b306..9c84d7f15 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -29,6 +29,7 @@ __Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) +__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) From ad16c6d44fac4c3e28b5486caab008ebaf903ecb Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 5 May 2021 11:56:00 +0200 Subject: [PATCH 239/382] Include MarkerOps_Base (#1533) Include MarkerOps_Base --- Moose Development/Moose/Core/MarkerOps_Base.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index aa6bc30b5..4875e4481 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -10,7 +10,7 @@ -- -- ### Author: **Applevangelist** -- --- Date: May 2021 +-- Date: 5 May 2021 -- -- === --- @@ -250,4 +250,4 @@ end -------------------------------------------------------------------------- -- MARKEROPS_BASE Class Definition End. --------------------------------------------------------------------------- \ No newline at end of file +-------------------------------------------------------------------------- From e9d3c7c2e7fd0263899b9f3eceeee7fdfd3dfe34 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 5 May 2021 19:02:46 +0200 Subject: [PATCH 240/382] Update MarkerOps_Base.lua --- Moose Development/Moose/Core/MarkerOps_Base.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 4875e4481..f645f936f 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -61,7 +61,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords) self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version)) self.Tag = Tagname or "mytag"-- #string - self.Keywords = Keywords or {} -- #table + self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too self.debug = false ----------------------- From 9eba6d607c4f2c10c460c7bf9c71d88affa5ff6c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 6 May 2021 09:29:28 +0200 Subject: [PATCH 241/382] Updated doc images --- Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua | 2 +- Moose Development/Moose/AI/AI_Cargo_Ship.lua | 2 +- Moose Development/Moose/Core/MarkerOps_Base.lua | 4 +--- Moose Development/Moose/Wrapper/Marker.lua | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua index d20cca9eb..70f325f62 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -21,7 +21,7 @@ -- === -- -- @module AI.AI_Cargo_Dispatcher_Ship --- @image AI_Cargo_Dispatching_For_Ship.JPG +-- @image AI_Cargo_Dispatcher.JPG --- @type AI_CARGO_DISPATCHER_SHIP -- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua index e28e52aa2..37c7da57a 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -7,7 +7,7 @@ -- === -- -- @module AI.AI_Cargo_Ship --- @image AI_Cargo_Dispatching_For_Ship.JPG +-- @image AI_Cargo_Dispatcher.JPG --- @type AI_CARGO_SHIP -- @extends AI.AI_Cargo#AI_CARGO diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index f645f936f..eb5e17110 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -15,7 +15,7 @@ -- === --- -- @module Core.MarkerOps_Base --- @image Wrapper_Marker.png +-- @image MOOSE_Core.JPG -------------------------------------------------------------------------- -- MARKEROPS_BASE Class Definition. @@ -33,8 +33,6 @@ -- -- === -- --- ![Banner Image](..\Presentations\MARKER\Marker_Main.jpg) --- -- # The MARKEROPS_BASE Concept -- -- This class enable scripting text-based actions from markers. diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..c37b1b263 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -13,7 +13,7 @@ -- -- ### Author: **funkyfranky** -- @module Wrapper.Marker --- @image Wrapper_Marker.png +-- @image MOOSE_Core.JPG --- Marker class. From 3b0949de7237bf77c42ddd14e575b118e146d409 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 7 May 2021 21:46:03 +0200 Subject: [PATCH 242/382] Fixed Ground Speed Issues COORDINATE - Fixed issue with ground units not honoring set speed by setting `ETA_locked=false` in `COORDINATE:WaypointGround` function (used by other classes and routines in MOOSE). See #1484 - Also set `ETA_locked=false` for `COORINATE:WaypointAir` and `COORDINATE:WaypointNaval` --- Moose Development/Moose/Core/Point.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 26aaa9dd7..0dea38886 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1184,7 +1184,7 @@ do -- COORDINATE -- ETA. RoutePoint.ETA=0 - RoutePoint.ETA_locked=true + RoutePoint.ETA_locked=false -- Waypoint description. RoutePoint.name=description @@ -1333,7 +1333,7 @@ do -- COORDINATE RoutePoint.formation_template="" RoutePoint.ETA=0 - RoutePoint.ETA_locked=true + RoutePoint.ETA_locked=false RoutePoint.speed = ( Speed or 20 ) / 3.6 RoutePoint.speed_locked = true @@ -1368,7 +1368,7 @@ do -- COORDINATE RoutePoint.formation_template = "" RoutePoint.ETA=0 - RoutePoint.ETA_locked=true + RoutePoint.ETA_locked=false RoutePoint.speed = ( Speed or 20 ) / 3.6 RoutePoint.speed_locked = true From 8b4ba430fb3c8374cc2319b41bea6564dde135fe Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 7 May 2021 21:55:56 +0200 Subject: [PATCH 243/382] Update Airbase.lua - Fixed #1524 --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index df18bc9ef..453e4857b 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -608,7 +608,7 @@ end --- Get all airbase names of the current map. This includes ships and FARPS. -- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. --- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP +-- @param #number category (Optional) Return only airbases of a certain category, e.g. `Airbase.Category.HELIPAD`. -- @return #table Table containing all airbase names of the current map. function AIRBASE.GetAllAirbaseNames(coalition, category) From 1ce46c016c8ee9c86001c020f59e618a7bd7da7e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 00:14:07 +0200 Subject: [PATCH 244/382] Cargo --- Moose Development/Moose/Ops/OpsGroup.lua | 41 +++++++++-- Moose Development/Moose/Ops/OpsTransport.lua | 72 ++++++++++++++++++-- 2 files changed, 100 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 113bfff23..6f8412eb5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -435,13 +435,14 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.2" +OPSGROUP.version="0.7.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: AI on/off. +-- TODO: Emission on/off. -- TODO: Invisible/immortal. -- TODO: F10 menu. -- TODO: Add pseudo function. @@ -1112,7 +1113,7 @@ function OPSGROUP:GetVelocity(UnitName) return nil end ---- Get current heading of the group. +--- Get current heading of the group or (optionally) of a specific unit of the group. -- @param #OPSGROUP self -- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. -- @return #number Current heading of the group in degrees. @@ -1181,7 +1182,7 @@ function OPSGROUP:GetOrientation(UnitName) return nil end ---- Get current orientation of the first unit in the group. +--- Get current "X" orientation of the first unit in the group. -- @param #OPSGROUP self -- @param #string UnitName (Optional) Get orientation of a specific unit of the group. Default is the first existing unit of the group. -- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. @@ -1736,6 +1737,13 @@ function OPSGROUP:IsNotCarrier() return self.carrierStatus==OPSGROUP.CarrierStatus.NOTCARRIER end +--- Check if the group is a carrier. +-- @param #OPSGROUP self +-- @return #boolean If true, group is a carrier. +function OPSGROUP:IsCarrier() + return not self:IsNotCarrier() +end + --- Check if the group is picking up cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is picking up. @@ -1765,6 +1773,13 @@ function OPSGROUP:IsUnloading() end +--- Check if the group is assigned as cargo. +-- @param #OPSGROUP self +-- @return #boolean If true, group is cargo. +function OPSGROUP:IsCargo() + return not self:IsNotCargo() +end + --- Check if the group is **not** cargo. -- @param #OPSGROUP self -- @return #boolean If true, group is *not* cargo. @@ -4548,6 +4563,12 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + + if self:IsCarrier() then + if self.cargoTransport then + self.cargoTransport:CarrierGroupDead() + end + end end @@ -4693,7 +4714,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) - self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) + self:I(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Is dead now. self.isDead=true @@ -4708,11 +4729,17 @@ function OPSGROUP:onafterDead(From, Event, To) mission:GroupDead(self) end + + -- Inform all transports in the queue that this carrier group is dead now. + for i,_transport in pairs(self.cargoqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + transport:DeadCarrierGroup(self) + end -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil self.groupinitialized=false - + -- Stop in a sec. self:__Stop(-5) end @@ -5037,16 +5064,16 @@ function OPSGROUP:AddOpsTransport(OpsTransport) -- Add this group as carrier for the transport. OpsTransport:_AddCarrier(self) - --Add to cargo queue table.insert(self.cargoqueue, OpsTransport) + -- Debug message. self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) return self end ---- Delete a cargo transport assignment from the cargo queue +--- Delete a cargo transport assignment from the cargo queue. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport Cargo transport do be deleted. -- @return #OPSGROUP self diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 2c03b672f..92a9ff9ff 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -3,6 +3,9 @@ -- ## Main Features: -- -- * Transport troops from A to B. +-- * Supports ground, naval and airborne (airplanes and helicopters) units as carriers +-- * Use combined forces (ground, naval, air) to transport the troops. +-- * Additional FSM events to hook into and customize your mission design. -- -- === -- @@ -55,7 +58,8 @@ -- -- # The OPSTRANSPORT Concept -- --- Transport OPSGROUPS using carriers such as APCs, helicopters or airplanes. +-- This class simulates troop transport using carriers such as APCs, ships helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classed). +-- -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -97,14 +101,14 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.5" +OPSTRANSPORT.version="0.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DONE: Add start conditions. --- TODO: Check carrier(s) dead. +-- DONE: Check carrier(s) dead. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -164,6 +168,9 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) self:AddTransition("*", "Loaded", "*") self:AddTransition("*", "Unloaded", "*") + self:AddTransition("*", "DeadCarrierUnit", "*") + self:AddTransition("*", "DeadCarrierGroup", "*") + self:AddTransition("*", "DeadCarrierAll", "*") -- Call status update. self:__Status(-1) @@ -313,16 +320,19 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:_AddCarrier(CarrierGroup) + -- Check that this is not already an assigned carrier. if not self:IsCarrier(CarrierGroup) then -- Increase carrier count. self.Ncarrier=self.Ncarrier+1 - -- Set trans + -- Set transport status to SCHEDULED. self:SetCarrierTransportStatus(CarrierGroup, OPSTRANSPORT.Status.SCHEDULED) + -- Call scheduled event. self:Scheduled() + -- Add carrier to table. table.insert(self.carriers, CarrierGroup) end @@ -330,6 +340,32 @@ function OPSTRANSPORT:_AddCarrier(CarrierGroup) return self end +--- Remove group from the current carrier list/table. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:_DelCarrier(CarrierGroup) + + if self:IsCarrier(CarrierGroup) then + + for i,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier.groupname==CarrierGroup.groupname then + self:I(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) + table.remove(self.carriers, i) + end + end + + if #self.carriers==0 then + self:DeadCarrierAll() + end + + end + + return self +end + + --- Set transport start and stop time. -- @param #OPSTRANSPORT self -- @param #string ClockStart Time the transport is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. Default is 5 seconds after mission was added. @@ -560,7 +596,7 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local fsmstate=self:GetState() -- Info text. - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, self.Ncarrier) + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) text=text..string.format("\nCargos:") for _,_cargo in pairs(self.cargos) do @@ -648,6 +684,30 @@ function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) end +--- On after "DeadCarrierGroup" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup Carrier OPSGROUP that is dead. +function OPSTRANSPORT:onafterDeadCarrierGroup(From, Event, To, OpsGroup) + self:I(self.lid..string.format("Carrier OPSGROUP %s dead!", OpsGroup:GetName())) + -- Remove group from carrier list/table. + self:_DelCarrier(OpsGroup) +end + +--- On after "DeadCarrierAll" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) + self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) + if not self:IsDelivered() then + self:Planned() + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -776,7 +836,7 @@ function OPSTRANSPORT:_CreateCargoGroupData(group) return cargo end ---- Get an OPSGROUIP +--- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUUP, an OPSGROUP is created automatically. -- @param #OPSTRANSPORT self -- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP. -- @return Ops.OpsGroup#OPSGROUP Ops Group. From 20fe2ee5053ba72852e5f680c645f7b77fa1d138 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 08:33:15 +0200 Subject: [PATCH 245/382] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 30 +------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 17baca821..cac2433e2 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1008,38 +1008,14 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - env.info("FF some event 100 ID="..tostring(Event.id)) - - if Event.id==31 then - env.info("FF got event 31") - local initiator=Event.initiator~=nil - env.info(string.format("FF got event 31 initiator=%s", tostring(initiator))) - if self then - env.info(string.format("FF got event 31 self=true")) - end - if self.Events then - env.info(string.format("FF got event 31 self.Events=true")) - end - if self.Events[Event.id] then - env.info(string.format("FF got event 31 self.Events[Event.id]=true")) - else - env.info(string.format("FF NO event 31 self.Events[Event.id]=FALSE!")) - end - end - - --if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then - if self and self.Events and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true end - env.info("FF some event 150") - if Event.initiator then - env.info("FF some event 200") - Event.IniObjectCategory = Event.initiator:getCategory() if Event.IniObjectCategory == Object.Category.UNIT then @@ -1068,12 +1044,8 @@ function EVENT:onEvent( Event ) if Event.IniObjectCategory == Object.Category.STATIC then - env.info("FF some event 300") - if Event.id==31 then - env.info("FF event 31") - -- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug. Event.IniDCSUnit = Event.initiator local ID=Event.initiator.id_ From 59e4f487264fc402ce80b113f91492913d933f74 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 May 2021 17:47:42 +0200 Subject: [PATCH 246/382] COORDINATES and ZONES - Quad zones defined in the ME are now registered as ZONE_POLYGON_BASE - Added draw functions to COORDINATE (line, circle, arrow, rectangle, text) --- Moose Development/Moose/Core/Database.lua | 69 +++++- Moose Development/Moose/Core/Event.lua | 6 +- Moose Development/Moose/Core/Fsm.lua | 2 +- Moose Development/Moose/Core/Point.lua | 208 +++++++++++++++-- Moose Development/Moose/Core/Zone.lua | 239 +++++++++++++++++++- Moose Development/Moose/Utilities/Utils.lua | 11 + 6 files changed, 504 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index eafe78140..0d6d2bb35 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -298,24 +298,81 @@ do -- Zones -- @return #DATABASE self function DATABASE:_RegisterZones() - for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do + for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do local ZoneName = ZoneData.name + + -- Color + local color=ZoneData.color or {1, 0, 0, 0.15} + + -- Create new Zone + local Zone=nil --Core.Zone#ZONE_BASE + + if ZoneData.type==0 then + + --- + -- Circular zone + --- + + self:I(string.format("Register ZONE: %s (Circular)", ZoneName)) + + Zone=ZONE:New(ZoneName) + + else - self:I( { "Register ZONE:", Name = ZoneName } ) - local Zone = ZONE:New( ZoneName ) - self.ZONENAMES[ZoneName] = ZoneName - self:AddZone( ZoneName, Zone ) + --- + -- Quad-point zone + --- + + self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName)) + + Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) + + for i,vec2 in pairs(ZoneData.verticies) do + local coord=COORDINATE:NewFromVec2(vec2) + coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) + end + + end + + if Zone then + + -- Debug output. + --self:I({"Register ZONE: %s (", Name = ZoneName}) + + Zone.Color=color + + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + + end + end + -- Polygon zones defined by late activated groups. for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then + local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) - self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon)", ZoneName)) + + -- Create a new polygon zone. local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) + + -- Set color. + Zone_Polygon:SetColor({1, 0, 0}, 0.15) + + -- Store name in DB. self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index cac2433e2..85384ac02 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -592,9 +592,7 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:I( { _EVENTMETA[EventID].Text, EventClass } ) - - env.info("FF EVENT.Init ID="..EventID) + self:F( { _EVENTMETA[EventID].Text, EventClass } ) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -989,8 +987,6 @@ end -- @param #EVENTDATA Event Event data table. function EVENT:onEvent( Event ) - env.info("FF some event") - local ErrorHandler = function( errmsg ) env.info( "Error in SCHEDULER function:" .. errmsg ) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 80dfcb0c5..644a04d63 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -540,7 +540,7 @@ do -- FSM --- Returns a table with the scores defined. -- @param #FSM self - -- @param #table Scores. + -- @return #table Scores. function FSM:GetScores() return self._Scores or {} end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 26aaa9dd7..59465fdd2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -164,6 +164,7 @@ do -- COORDINATE -- -- * @{#COORDINATE.WaypointAir}(): Build an air route point. -- * @{#COORDINATE.WaypointGround}(): Build a ground route point. + -- * @{#COORDINATE.WaypointNaval}(): Build a naval route point. -- -- Route points can be used in the Route methods of the @{Wrapper.Group#GROUP} class. -- @@ -183,10 +184,18 @@ do -- COORDINATE -- -- ## 9) Coordinate text generation -- - -- -- * @{#COORDINATE.ToStringBR}(): Generates a Bearing & Range text in the format of DDD for DI where DDD is degrees and DI is distance. -- * @{#COORDINATE.ToStringLL}(): Generates a Latutude & Longutude text. -- + -- ## 10) Drawings on F10 map + -- + -- * @{#COORDINATE.CircleToAll}(): Draw a circle on the F10 map. + -- * @{#COORDINATE.LineToAll}(): Draw a line on the F10 map. + -- * @{#COORDINATE.RectToAll}(): Draw a rectangle on the F10 map. + -- * @{#COORDINATE.QuadToAll}(): Draw a shape with four points on the F10 map. + -- * @{#COORDINATE.TextToAll}(): Write some text on the F10 map. + -- * @{#COORDINATE.ArrowToAll}(): Draw an arrow on the F10 map. + -- -- @field #COORDINATE COORDINATE = { ClassName = "COORDINATE", @@ -1984,13 +1993,13 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE Endpoint COORDIANTE to where the line is drawn. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID which is a number. - function COORDINATE:LineToAll(Endpoint, Coalition, LineType, Color, Alpha, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:LineToAll(Endpoint, Coalition, Color, Alpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false @@ -2007,18 +2016,17 @@ do -- COORDINATE --- Circle to all. -- Creates a circle on the map with a given radius, color, fill color, and outline. -- @param #COORDINATE self - -- @param #COORDINATE Center COORDIANTE of the center of the circle. -- @param #numberr Radius Radius in meters. Default 1000 m. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. - -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. - -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red (default). - -- @param #number FillAlpha Transparency [0,1]. Default 0.5. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @param #string Text (Optional) Text displayed when mark is added. Default none. - -- @return #number The resulting Mark ID which is a number. - function COORDINATE:CircleToAll(Radius, Coalition, LineType, Color, Alpha, FillColor, FillAlpha, ReadOnly, Text) + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false @@ -2029,14 +2037,188 @@ do -- COORDINATE Color=Color or {1,0,0} Color[4]=Alpha or 1.0 LineType=LineType or 1 - FillColor=FillColor or {1,0,0} - FillColor[4]=FillAlpha or 0.5 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 trigger.action.circleToAll(Coalition, MarkID, vec3, Radius, Color, FillColor, LineType, ReadOnly, Text or "") return MarkID end end -- Markings - + + --- Rectangle to all. Creates a rectangle on the map from the COORDINATE in one corner to the end COORDINATE in the opposite corner. + -- Creates a line on the F10 map from one point to another. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDIANTE in the opposite corner. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:RectToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.rectToAll(Coalition, MarkID, self:GetVec3(), vec3, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end + + --- Creates a shape defined by 4 points on the F10 map. The first point is the current COORDINATE. The remaining three points need to be specified. + -- @param #COORDINATE self + -- @param #COORDINATE Coord2 Second COORDIANTE of the quad shape. + -- @param #COORDINATE Coord3 Third COORDIANTE of the quad shape. + -- @param #COORDINATE Coord4 Fourth COORDIANTE of the quad shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local point1=self:GetVec3() + local point2=Coord2:GetVec3() + local point3=Coord3:GetVec3() + local point4=Coord4:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + trigger.action.quadToAll(Coalition, MarkID, self:GetVec3(), point2, point3, point4, Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end + + --- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified. + -- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported. + -- @param #COORDINATE self + -- @param #table Coordinates Table of coordinates of the remaining points of the shape. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + + local vecs={} + table.insert(vecs, self:GetVec3()) + for _,coord in ipairs(Coordinates) do + table.insert(vecs, coord:GetVec3()) + end + + if #vecs<3 then + self:E("ERROR: A free form polygon needs at least three points!") + elseif #vecs==3 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==4 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==5 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==6 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==7 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==8 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==9 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "") + elseif #vecs==10 then + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "") + else + self:E("ERROR: Currently a free form polygon can only have 10 points in total!") + -- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :( + trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "") + end + + return MarkID + end + + --- Text to all. Creates a text imposed on the map at the COORDINATE. Text scales with the map. + -- @param #COORDINATE self + -- @param #string Text Text displayed on the F10 map. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number FontSize Font size. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + FontSize=FontSize or 12 + trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + return MarkID + end + + --- Arrow to all. Creates an arrow from the COORDINATE to the endpoint COORDINATE on the F10 map. There is no control over other dimensions of the arrow. + -- @param #COORDINATE self + -- @param #COORDINATE Endpoint COORDINATE where the tip of the arrow is pointing at. + -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. + -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). + -- @param #number Alpha Transparency [0,1]. Default 1. + -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. + -- @param #number FillAlpha Transparency [0,1]. Default 0.15. + -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. + -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. + -- @param #string Text (Optional) Text displayed when mark is added. Default none. + -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. + function COORDINATE:ArrowToAll(Endpoint, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() + if ReadOnly==nil then + ReadOnly=false + end + local vec3=Endpoint:GetVec3() + Coalition=Coalition or -1 + Color=Color or {1,0,0} + Color[4]=Alpha or 1.0 + LineType=LineType or 1 + FillColor=FillColor or Color + FillColor[4]=FillAlpha or 0.15 + --trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") + trigger.action.arrowToAll(Coalition, MarkID, vec3, self:GetVec3(), Color, FillColor, LineType, ReadOnly, Text or "") + return MarkID + end --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ef630eee..f14619959 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -56,6 +56,8 @@ --- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @field #number DrawID Unique ID of the drawn zone on the F10 map. +-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. -- @extends Core.Fsm#FSM @@ -104,7 +106,9 @@ ZONE_BASE = { ClassName = "ZONE_BASE", ZoneName = "", ZoneProbability = 1, - } + DrawID=nil, + Color={} +} --- The ZONE_BASE.BoundingSquare @@ -324,6 +328,76 @@ function ZONE_BASE:BoundZone() end + +--- Set color of zone. +-- @param #ZONE_BASE self +-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`. +-- @param #number Alpha Transparacy between 0 and 1. Default 0.15. +-- @return #ZONE_BASE self +function ZONE_BASE:SetColor(RGBcolor, Alpha) + + RGBcolor=RGBcolor or {1, 0, 0} + Alpha=Alpha or 0.15 + + self.Color={} + self.Color[1]=RGBcolor[1] + self.Color[2]=RGBcolor[2] + self.Color[3]=RGBcolor[3] + self.Color[4]=Alpha + + return self +end + +--- Get color table of the zone. +-- @param #ZONE_BASE self +-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value. +function ZONE_BASE:GetColor() + return self.Color +end + +--- Get RGB color of zone. +-- @param #ZONE_BASE self +-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code. +function ZONE_BASE:GetColorRGB() + local rgb={} + rgb[1]=self.Color[1] + rgb[2]=self.Color[2] + rgb[3]=self.Color[3] + return rgb +end + +--- Get transperency Alpha value of zone. +-- @param #ZONE_BASE self +-- @return #number Alpha value. +function ZONE_BASE:GetColorAlpha() + local alpha=self.Color[4] + return alpha +end + +--- Remove the drawing of the zone from the F10 map. +-- @param #ZONE_BASE self +-- @param #number Delay (Optional) Delay before the drawing is removed. +-- @return #ZONE_BASE self +function ZONE_BASE:UndrawZone(Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) + else + if self.DrawID then + UTILS.RemoveMark(self.DrawID) + end + end + return self +end + +--- Get ID of the zone object drawn on the F10 map. +-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. +-- @param #ZONE_BASE self +-- @return #number Unique ID of the +function ZONE_BASE:GetDrawID() + return self.DrawID +end + + --- Smokes the zone boundaries in a color. -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -469,6 +543,32 @@ function ZONE_RADIUS:MarkZone(Points) end +--- Draw the zone circle on the F10 map. +-- @param #ZONE_RADIUS self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + local coordinate=self:GetCoordinate() + + local Radius=self:GetRadius() + + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() + + self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + return self +end + --- Bounds the zone with tires. -- @param #ZONE_RADIUS self -- @param #number Points (optional) The amount of points in the circle. Default 360. @@ -1116,22 +1216,37 @@ ZONE = { } ---- Constructor of ZONE, taking the zone name. +--- Constructor of ZONE taking the zone name. -- @param #ZONE self -- @param #string ZoneName The name of the zone as defined within the mission editor. --- @return #ZONE +-- @return #ZONE self function ZONE:New( ZoneName ) + -- First try to find the zone in the DB. + local zone=_DATABASE:FindZone(ZoneName) + + if zone then + --env.info("FF found zone in DB") + return zone + end + + -- Get zone from DCS trigger function. local Zone = trigger.misc.getZone( ZoneName ) + -- Error! if not Zone then error( "Zone " .. ZoneName .. " does not exist." ) return nil end - local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, { x = Zone.point.x, y = Zone.point.z }, Zone.radius ) ) - self:F( ZoneName ) + -- Create a new ZONE_RADIUS. + local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) + self:F(ZoneName) + + -- Color of zone. + self.Color={1, 0, 0, 0.15} + -- DCS zone. self.Zone = Zone return self @@ -1425,7 +1540,7 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) end --- Returns the center location of the polygon. --- @param #ZONE_GROUP self +-- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) @@ -1435,6 +1550,78 @@ function ZONE_POLYGON_BASE:GetVec2() return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return DCS#Vec2 Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexVec2(Index) + return self._.Polygon[Index or 1] +end + +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return DCS#Vec3 Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexVec3(Index) + local vec2=self:GetVertexVec2(Index) + if vec2 then + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + return vec3 + end + return nil +end + +--- Get a vertex of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Index Index of the vertex. Default 1. +-- @return Core.Point#COORDINATE Vertex of the polygon. +function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) + local vec2=self:GetVertexVec2(Index) + if vec2 then + local coord=COORDINATE:NewFromVec2(vec2) + return coord + end + return nil +end + + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return List of DCS#Vec2 verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesVec2() + return self._.Polygon +end + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return #table List of DCS#Vec3 verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesVec3() + + local coords={} + + for i,vec2 in ipairs(self._.Polygon) do + local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} + table.insert(coords, vec3) + end + + return coords +end + +--- Get a list of verticies of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return #table List of COORDINATES verticies defining the edges of the polygon. +function ZONE_POLYGON_BASE:GetVerticiesCoordinates() + + local coords={} + + for i,vec2 in ipairs(self._.Polygon) do + local coord=COORDINATE:NewFromVec2(vec2) + table.insert(coords, coord) + end + + return coords +end + --- Flush polygon coordinates as a table in DCS.log. -- @param #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self @@ -1494,6 +1681,46 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) end +--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported! +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + + Color=Color or self:GetColorRGB() + Alpha=Alpha or 1 + FillColor=FillColor or Color + FillAlpha=FillAlpha or self:GetColorAlpha() + + + if #self._.Polygon==4 then + + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) + local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) + local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) + + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + else + + local Coordinates=self:GetVerticiesCoordinates() + table.remove(Coordinates, 1) + + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + + end + + + return self +end --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 693550719..db7d189d2 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -652,6 +652,17 @@ function UTILS.GetMarkID() end +--- Remove an object (marker, circle, arrow, text, quad, ...) on the F10 map. +-- @param #number MarkID Unique ID of the object. +-- @param #number Delay (Optional) Delay in seconds before the mark is removed. +function UTILS.RemoveMark(MarkID, Delay) + if Delay and Delay>0 then + TIMER:New(UTILS.RemoveMark, MarkID):Start(Delay) + else + trigger.action.removeMark(MarkID) + end +end + -- Test if a Vec2 is in a radius of another Vec2 function UTILS.IsInRadius( InVec2, Vec2, Radius ) From 7cd29501a9990f84762f203ce72a241097572856 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 00:21:50 +0200 Subject: [PATCH 247/382] Templates update --- Moose Development/Moose/Core/Event.lua | 55 ++- Moose Development/Moose/Core/Point.lua | 14 +- Moose Development/Moose/Core/Zone.lua | 12 +- .../Moose/Utilities/Templates.lua | 365 +++++++++++++++--- 4 files changed, 358 insertions(+), 88 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 85384ac02..e06b6c979 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -240,7 +240,7 @@ EVENTS = { Kill = world.event.S_EVENT_KILL or -1, Score = world.event.S_EVENT_SCORE or -1, UnitLost = world.event.S_EVENT_UNIT_LOST or -1, - LandingAfterEjection = 31, --world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, + LandingAfterEjection = world.event.S_EVENT_LANDING_AFTER_EJECTION or -1, -- Added with DCS 2.7.0 ParatrooperLanding = world.event.S_EVENT_PARATROOPER_LENDING or -1, DiscardChairAfterEjection = world.event.S_EVENT_DISCARD_CHAIR_AFTER_EJECTION or -1, @@ -524,7 +524,7 @@ local _EVENTMETA = { Event = "OnEventUnitLost", Text = "S_EVENT_UNIT_LOST" }, - [world.event.S_EVENT_LANDING_AFTER_EJECTION] = { + [EVENTS.LandingAfterEjection] = { Order = 1, Event = "OnEventLandingAfterEjection", Text = "S_EVENT_LANDING_AFTER_EJECTION" @@ -592,7 +592,7 @@ end -- @param Core.Base#BASE EventClass The class object for which events are handled. -- @return #EVENT.Events function EVENT:Init( EventID, EventClass ) - self:F( { _EVENTMETA[EventID].Text, EventClass } ) + self:F3( { _EVENTMETA[EventID].Text, EventClass } ) if not self.Events[EventID] then -- Create a WEAK table to ensure that the garbage collector is cleaning the event links when the object usage is cleaned. @@ -1124,34 +1124,33 @@ function EVENT:onEvent( Event ) end if Event.TgtObjectCategory == Object.Category.STATIC then - BASE:T({StaticTgtEvent = Event.id}) -- get base data - Event.TgtDCSUnit = Event.target - if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtDCSUnit = Event.target + if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object + Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) + Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() + Event.TgtCategory = Event.TgtDCSUnit:getDesc().category + Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + else + Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = nil + Event.TgtCoalition = 0 + Event.TgtCategory = 0 + if Event.id == 6 then + Event.TgtTypeName = "Ejected Pilot" + Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) + Event.TgtUnitName = Event.TgtDCSUnitName + elseif Event.id == 33 then + Event.TgtTypeName = "Ejection Seat" + Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) - Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() else - Event.TgtDCSUnitName = string.format("No target object for Event ID %s", tostring(Event.id)) - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = nil - Event.TgtCoalition = 0 - Event.TgtCategory = 0 - if Event.id == 6 then - Event.TgtTypeName = "Ejected Pilot" - Event.TgtDCSUnitName = string.format("Ejected Pilot ID %s", tostring(Event.IniDCSUnitName)) - Event.TgtUnitName = Event.TgtDCSUnitName - elseif Event.id == 33 then - Event.TgtTypeName = "Ejection Seat" - Event.TgtDCSUnitName = string.format("Ejection Seat ID %s", tostring(Event.IniDCSUnitName)) - Event.TgtUnitName = Event.TgtDCSUnitName - else - Event.TgtTypeName = "Static" - end - end + Event.TgtTypeName = "Static" + end + end end if Event.TgtObjectCategory == Object.Category.SCENERY then diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 59465fdd2..1df5bdc7b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -634,9 +634,9 @@ do -- COORDINATE --- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. -- @param #COORDINATE self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #COORDINATE + -- @param DCS#Distance OuterRadius Outer radius in meters. + -- @param DCS#Distance InnerRadius Inner radius in meters. + -- @return #COORDINATE self function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) @@ -2172,8 +2172,8 @@ do -- COORDINATE -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. -- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. - -- @param #number FillAlpha Transparency [0,1]. Default 0.15. - -- @param #number FontSize Font size. + -- @param #number FillAlpha Transparency [0,1]. Default 0.3. + -- @param #number FontSize Font size. Default 14. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. function COORDINATE:TextToAll(Text, Coalition, Color, Alpha, FillColor, FillAlpha, FontSize, ReadOnly) @@ -2185,8 +2185,8 @@ do -- COORDINATE Color=Color or {1,0,0} Color[4]=Alpha or 1.0 FillColor=FillColor or Color - FillColor[4]=FillAlpha or 0.15 - FontSize=FontSize or 12 + FillColor[4]=FillAlpha or 0.3 + FontSize=FontSize or 14 trigger.action.textToAll(Coalition, MarkID, self:GetVec3(), Color, FillColor, FontSize, ReadOnly, Text or "Hello World") return MarkID end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index f14619959..035d0e2a4 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -17,6 +17,7 @@ -- * Get zone properties. -- * Get zone bounding box. -- * Set/get zone name. +-- * Draw zones (circular and polygon) on the F10 map. -- -- -- There are essentially two core functions that zones accomodate: @@ -495,6 +496,10 @@ end -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- +-- ## Draw zone +-- +-- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map. +-- -- @field #ZONE_RADIUS ZONE_RADIUS = { ClassName="ZONE_RADIUS", @@ -1506,6 +1511,11 @@ end -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +-- +-- ## Draw zone +-- +-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- -- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { @@ -1938,7 +1948,7 @@ end -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, --- without much scripting overhead!!! +-- without much scripting overhead! -- -- @field #ZONE_POLYGON ZONE_POLYGON = { diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua index df7c3ff0b..182925689 100644 --- a/Moose Development/Moose/Utilities/Templates.lua +++ b/Moose Development/Moose/Utilities/Templates.lua @@ -44,10 +44,10 @@ TEMPLATE = { Helicopter = {}, } ---- Pattern steps. --- @type TEMPLATE.Ground +--- Ground unit type names. +-- @type TEMPLATE.TypeGround -- @param #string InfantryAK -TEMPLATE.Ground={ +TEMPLATE.TypeGround={ InfantryAK="Infantry AK", ParatrooperAKS74="Paratrooper AKS-74", ParatrooperRPG16="Paratrooper RPG-16", @@ -56,40 +56,227 @@ TEMPLATE.Ground={ SoldierM4="Soldier M4", } +--- Naval unit type names. +-- @type TEMPLATE.TypeNaval +-- @param #string Ticonderoga +TEMPLATE.TypeNaval={ + Ticonderoga="TICONDEROG", +} + +--- Rotary wing unit type names. +-- @type TEMPLATE.TypeAirplane +-- @param #string A10C +TEMPLATE.TypeAirplane={ + A10C="A-10C", +} + +--- Rotary wing unit type names. +-- @type TEMPLATE.TypeHelicopter +-- @param #string AH1W +TEMPLATE.TypeHelicopter={ + AH1W="AH-1W", +} + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start/Stop Profiler +-- Ground Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Get template for ground units. -- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. -- @param #string GroupName Name of the spawned group. **Must be unique!** -- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. --- @param DCS#Vec3 Vec3 Position of the group. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. -- @param #number Nunits Number of units. Default 1. -- @param #number Radius Spawn radius for additonal units in meters. Default 50 m. -- @return #table Template Template table. function TEMPLATE.GetGround(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeGround.SoldierM4 + GroupName=GroupName or "Ground-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 50 + + + -- Get generic template. local template=UTILS.DeepCopy(TEMPLATE.GenericGround) - - template.name=GroupName or "Ground-1" + + -- Set group name. + template.name=GroupName -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. - template.CountryID=country.id.USA + template.CountryID=CountryID template.CoalitionID=coalition.getCountryCoalition(template.CountryID) template.CategoryID=Unit.Category.GROUND_UNIT - template.units[1].type=TypeName or "Infantry AK" - template.units[1].name=GroupName.."-1" + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" if Vec3 then TEMPLATE.SetPositionFromVec3(template, Vec3) end + + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Naval Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for ground units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetNaval(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeNaval.Ticonderoga + GroupName=GroupName or "Naval-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 500 + + + -- Get generic template. + local template=UTILS.DeepCopy(TEMPLATE.GenericNaval) + + -- Set group name. + template.name=GroupName + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=CountryID + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + template.CategoryID=Unit.Category.SHIP + + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" + + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Aircraft Template +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get template for fixed wing units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetAirplane(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeAirplane.A10C + GroupName=GroupName or "Airplane-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=1000, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + local template=TEMPLATE._GetAircraft(true, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + return template +end + +--- Get template for fixed wing units. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE.GetHelicopter(TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName or TEMPLATE.TypeHelicopter.AH1W + GroupName=GroupName or "Helicopter-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=500, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + -- Limit unis to 4. + Nunits=math.min(Nunits, 4) + + local template=TEMPLATE._GetAircraft(false, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) return template end +--- Get template for aircraft units. +-- @param #boolean Airplane If true, this is a fixed wing. Else, rotary wing. +-- @param #string TypeName Type name of the unit(s) in the groups. See `TEMPLATE.Ground`. +-- @param #string GroupName Name of the spawned group. **Must be unique!** +-- @param #number CountryID Country ID. Default `country.id.USA`. Coalition is automatically determined by the one the country belongs to. +-- @param DCS#Vec3 Vec3 Position of the group and the first unit. +-- @param #number Nunits Number of units. Default 1. +-- @param #number Radius Spawn radius for additonal units in meters. Default 500 m. +-- @return #table Template Template table. +function TEMPLATE._GetAircraft(Airplane, TypeName, GroupName, CountryID, Vec3, Nunits, Radius) + + -- Defaults. + TypeName=TypeName + GroupName=GroupName or "Aircraft-1" + CountryID=CountryID or country.id.USA + Vec3=Vec3 or {x=0, y=0, z=0} + Nunits=Nunits or 1 + Radius=Radius or 100 + + -- Get generic template. + local template=UTILS.DeepCopy(TEMPLATE.GenericAircraft) + + -- Set group name. + template.name=GroupName + + -- These are additional entries required by the MOOSE _DATABASE:Spawn() function. + template.CountryID=CountryID + template.CoalitionID=coalition.getCountryCoalition(template.CountryID) + if Airplane then + template.CategoryID=Unit.Category.AIRPLANE + else + template.CategoryID=Unit.Category.HELICOPTER + end + + -- Set first unit. + template.units[1].type=TypeName + template.units[1].name=GroupName.."-1" + + -- Set position. + if Vec3 then + TEMPLATE.SetPositionFromVec3(template, Vec3) + end + + -- Set number of units. + TEMPLATE.SetUnits(template, Nunits, COORDINATE:NewFromVec3(Vec3), Radius) + + return template +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Set the position of the template. -- @param #table Template The template to be modified. @@ -121,6 +308,84 @@ function TEMPLATE.SetPositionFromVec3(Template, Vec3) end +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param #number N Total number of units in the group. +-- @param Core.Point#COORDINATE Coordinate Position of the first unit. +-- @param #number Radius Radius in meters to randomly place the additional units. +function TEMPLATE.SetUnits(Template, N, Coordinate, Radius) + + local units=Template.units + + local unit1=units[1] + + local Vec3=Coordinate:GetVec3() + + unit1.x=Vec3.x + unit1.y=Vec3.z + unit1.alt=Vec3.y + + for i=2,N do + units[i]=UTILS.DeepCopy(unit1) + end + + for i=1,N do + local unit=units[i] + unit.name=string.format("%s-%d", Template.name, i) + if i>1 then + local vec2=Coordinate:GetRandomCoordinateInRadius(Radius, 5):GetVec2() + unit.x=vec2.x + unit.y=vec2.y + unit.alt=unit1.alt + end + end + +end + +--- Set the position of the template. +-- @param #table Template The template to be modified. +-- @param Wrapper.Airbase#AIRBASE AirBase The airbase where the aircraft are spawned. +-- @param #table ParkingSpots List of parking spot IDs. Every unit needs one! +-- @param #boolean EngineOn If true, aircraft are spawned hot. +function TEMPLATE.SetAirbase(Template, AirBase, ParkingSpots, EngineOn) + + -- Airbase ID. + local AirbaseID=AirBase:GetID() + + -- Spawn point. + local point=Template.route.points[1] + + -- Set ID. + if AirBase:IsAirdrome() then + point.airdromeId=AirbaseID + else + point.helipadId=AirbaseID + point.linkUnit=AirbaseID + end + + if EngineOn then + point.action=COORDINATE.WaypointAction.FromParkingAreaHot + point.type=COORDINATE.WaypointType.TakeOffParkingHot + else + point.action=COORDINATE.WaypointAction.FromParkingArea + point.type=COORDINATE.WaypointType.TakeOffParking + end + + for i,unit in ipairs(Template.units) do + unit.parking_id=ParkingSpots[i] + end + +end + +--- Add a waypoint. +-- @param #table Template The template to be modified. +-- @param #table Waypoint Waypoint table. +function TEMPLATE.AddWaypoint(Template, Waypoint) + + table.insert(Template.route.points, Waypoint) + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Generic Ground Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -255,79 +520,82 @@ TEMPLATE.GenericNaval= } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Ship Template +-- Generic Aircraft Template ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -TEMPLATE.GenericHelicopter= +TEMPLATE.GenericAircraft= { - ["modulation"] = 0, - ["tasks"] = {}, -- end of ["tasks"] - ["radioSet"] = false, - ["task"] = "Nothing", + ["groupId"] = nil, + ["name"] = "Rotary-1", ["uncontrolled"] = false, - ["taskSelected"] = true, + ["hidden"] = false, + ["task"] = "Nothing", + ["y"] = 0, + ["x"] = 0, + ["start_time"] = 0, + ["communication"] = true, + ["radioSet"] = false, + ["frequency"] = 127.5, + ["modulation"] = 0, + ["taskSelected"] = true, + ["tasks"] = {}, -- end of ["tasks"] ["route"] = { ["points"] = { [1] = { - ["alt"] = 10, - ["action"] = "From Parking Area", - ["alt_type"] = "BARO", - ["speed"] = 41.666666666667, + ["y"] = 0, + ["x"] = 0, + ["alt"] = 1000, + ["alt_type"] = "BARO", + ["action"] = "Turning Point", + ["type"] = "Turning Point", + ["airdromeId"] = nil, ["task"] = { ["id"] = "ComboTask", ["params"] = { - ["tasks"] = - { - }, -- end of ["tasks"] + ["tasks"] = {}, -- end of ["tasks"] }, -- end of ["params"] }, -- end of ["task"] - ["type"] = "TakeOffParking", ["ETA"] = 0, ["ETA_locked"] = true, - ["y"] = 618351.087765, - ["x"] = -356168.27327001, + ["speed"] = 100, + ["speed_locked"] = true, ["formation_template"] = "", - ["airdromeId"] = 22, - ["speed_locked"] = true, }, -- end of [1] }, -- end of ["points"] }, -- end of ["route"] - ["groupId"] = nil, - ["hidden"] = false, ["units"] = { [1] = { - ["alt"] = 10, - ["alt_type"] = "BARO", + ["name"] = "Rotary-1-1", + ["unitId"] = nil, + ["type"] = "AH-1W", + ["onboard_num"] = "050", ["livery_id"] = "USA X Black", ["skill"] = "High", - ["parking"] = "4", ["ropeLength"] = 15, - ["speed"] = 41.666666666667, - ["type"] = "AH-1W", - ["unitId"] = 8, + ["speed"] = 0, + ["x"] = 0, + ["y"] = 0, + ["alt"] = 10, + ["alt_type"] = "BARO", + ["heading"] = 0, ["psi"] = 0, - ["parking_id"] = "10", - ["x"] = -356168.27327001, - ["name"] = "Rotary-1-1", + ["parking"] = nil, + ["parking_id"] = nil, ["payload"] = { - ["pylons"] = - { - }, -- end of ["pylons"] + ["pylons"] = {}, -- end of ["pylons"] ["fuel"] = "1250.0", ["flare"] = 30, ["chaff"] = 30, ["gun"] = 100, }, -- end of ["payload"] - ["y"] = 618351.087765, - ["heading"] = 0, ["callsign"] = { [1] = 2, @@ -335,15 +603,8 @@ TEMPLATE.GenericHelicopter= [3] = 1, ["name"] = "Springfield11", }, -- end of ["callsign"] - ["onboard_num"] = "050", }, -- end of [1] }, -- end of ["units"] - ["y"] = 0, - ["x"] = 0, - ["name"] = "Rotary-1", - ["communication"] = true, - ["start_time"] = 0, - ["frequency"] = 127.5, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 057e231a9d57133d422eec2d3b23e49ba9200e00 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:41:26 +0200 Subject: [PATCH 248/382] Emission --- Moose Development/Moose/Wrapper/Group.lua | 6 ++-- Moose Development/Moose/Wrapper/Unit.lua | 40 +++-------------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 1a2f00fa8..263b3a0ec 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2551,9 +2551,10 @@ do -- Players end ---- GROUND - Switch on/off radar emissions +--- GROUND - Switch on/off radar emissions for the group. -- @param #GROUP self --- @param #boolean switch +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self function GROUP:EnableEmission(switch) self:F2( self.GroupName ) local switch = switch or false @@ -2566,6 +2567,7 @@ function GROUP:EnableEmission(switch) end + return self end --do -- Smoke diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 65199d97d..eb6d472d8 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -761,40 +761,6 @@ function UNIT:GetFuel() return nil end ---- Sets the passed group or unit objects radar emitters on or off. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @param #boolean Switch If `true` or `nil`, emission is enabled. If `false`, emission is turned off. --- @return #UNIT self -function UNIT:SetEmission(Switch) - - if Switch==nil then - Switch=true - end - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - DCSUnit:enableEmission(Switch) - end - - return self -end - ---- Sets the passed group or unit objects radar emitters ON. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @return #UNIT self -function UNIT:EnableEmission() - self:SetEmission(true) - return self -end - ---- Sets the passed group or unit objects radar emitters OFF. Can be used on sam sites for example to shut down the radar without setting AI off or changing the alarm state. --- @param #UNIT self --- @return #UNIT self -function UNIT:DisableEmission() - self:SetEmission(false) - return self -end --- Returns a list of one @{Wrapper.Unit}. -- @param #UNIT self @@ -1429,9 +1395,10 @@ function UNIT:GetTemplateFuel() return nil end ---- GROUND - Switch on/off radar emissions. +--- GROUND - Switch on/off radar emissions of a unit. -- @param #UNIT self --- @param #boolean switch +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #UNIT self function UNIT:EnableEmission(switch) self:F2( self.UnitName ) @@ -1445,4 +1412,5 @@ function UNIT:EnableEmission(switch) end + return self end From b68f271fb726cf989bf6ad29f74ed5098de0ab91 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:51:25 +0200 Subject: [PATCH 249/382] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index e06b6c979..1b3952ac1 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -578,10 +578,6 @@ function EVENT:New() -- Add world event handler. self.EventHandler = world.addEventHandler(self) - for _,Event in pairs(self.Events) do - self:Init(Event,EventClass) - end - return self end From a893d46cb9684f9e286459a57f77880ed7104794 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 May 2021 11:54:27 +0200 Subject: [PATCH 250/382] Update Event.lua --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 1b3952ac1..02dab0afc 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1000,7 +1000,7 @@ function EVENT:onEvent( Event ) -- Check if this is a known event? if EventMeta then - if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.idss~=EVENTS.PlayerLeaveUnit)) then + if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit)) then if Event.id and Event.id == EVENTS.MissionEnd then self.MissionEnd = true From 1ae41319fa76bb001f8ff7d8218e5b60cb32d521 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 12 May 2021 09:18:31 +0200 Subject: [PATCH 251/382] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 976a47f96..b622326a8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework. ### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated -This repository contains the Moose.lua file to be included within your missions. +This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission. ### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use From e49020b905c69e92b7437579fa8241248839fd13 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 12 May 2021 09:19:07 +0200 Subject: [PATCH 252/382] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 976a47f96..b622326a8 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This repository contains the source lua code of the MOOSE framework. ### [MOOSE_INCLUDE](https://github.com/FlightControl-Master/MOOSE_INCLUDE) - For use and generated -This repository contains the Moose.lua file to be included within your missions. +This repository contains the Moose.lua file to be included within your missions. Note that the Moose\_.lua is technically the same as Moose.lua, but without any commentary or unnecessary whitespace in it. You only need to load **one** of those at the beginning of your mission. ### [MOOSE_DOCS](https://github.com/FlightControl-Master/MOOSE_DOCS) - Not for use From 0410ae6877a6ba3eff978128742f87194219ddba Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 May 2021 23:30:34 +0200 Subject: [PATCH 253/382] Mariana Islands --- Moose Development/Moose/Utilities/Utils.lua | 6 ++++++ Moose Development/Moose/Wrapper/Airbase.lua | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index db7d189d2..3fd6e9482 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -50,6 +50,7 @@ BIGSMOKEPRESET = { -- @field #string PersianGulf Persian Gulf map. -- @field #string TheChannel The Channel map. -- @field #string Syria Syria map. +-- @field #string MarianaIslands Mariana Islands map. DCSMAP = { Caucasus="Caucasus", NTTR="Nevada", @@ -57,6 +58,7 @@ DCSMAP = { PersianGulf="PersianGulf", TheChannel="TheChannel", Syria="Syria", + MarianaIslands="MarianaIslands" } @@ -1182,6 +1184,8 @@ function UTILS.GetMagneticDeclination(map) declination=-10 elseif map==DCSMAP.Syria then declination=5 + elseif map==DCSMAP.MarianaIslands then + declination=-2 else declination=0 end @@ -1310,6 +1314,8 @@ function UTILS.GMTToLocalTimeDifference() return 2 -- This map currently needs +2 elseif theatre==DCSMAP.Syria then return 3 -- Damascus is UTC+3 hours + elseif theatre==DCSMAP.MarianaIslands then + return 2 -- Guam is UTC+10 hours but is +2 for now. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 453e4857b..220e3a254 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -416,6 +416,25 @@ AIRBASE.Syria={ } +--- Airbases of the Mariana Islands map. +-- +-- * AIRBASE.MarianaIslands.Rota_International_Airport +-- * AIRBASE.MarianaIslands.Andersen +-- * AIRBASE.MarianaIslands.Northwest_Field +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_International_Airport +-- * AIRBASE.MarianaIslands.Saipan_International_Airport +-- * AIRBASE.MarianaIslands.Tinian_International_Airport +-- @field MarianaIslands +AIRBASE.MarianaIslands={ + ["Rota_International_Airport"]="Rota International Airport", + ["Andersen"]="Andersen", + ["Northwest_Field"]="Northwest_Field", + ["Antonio_B_Won_Pat_International_Airport"]="Antonio B. Won Pat International Airport", + ["Saipan_International_Airport"]="Saipan International Airport", + ["Tinian_International_Airport"]="Tinian International Airport", +} + + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 41b01a508d06dbf5a4ab11a260de77e7c778f246 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 14 May 2021 21:57:19 +0200 Subject: [PATCH 254/382] Removed debug coordinate marks for quad zones --- Moose Development/Moose/Core/Database.lua | 13 +++++-------- Moose Development/Moose/Core/Point.lua | 17 +++++++++++------ Moose Development/Moose/Core/Zone.lua | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 0d6d2bb35..b30329ecf 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -327,20 +327,17 @@ do -- Zones Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies) - for i,vec2 in pairs(ZoneData.verticies) do - local coord=COORDINATE:NewFromVec2(vec2) - coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) - end + --for i,vec2 in pairs(ZoneData.verticies) do + -- local coord=COORDINATE:NewFromVec2(vec2) + -- coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) + --end end if Zone then - -- Debug output. - --self:I({"Register ZONE: %s (", Name = ZoneName}) - + -- Store color of zone. Zone.Color=color - -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8c30032c0..f79d54593 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2121,23 +2121,28 @@ do -- COORDINATE -- @param #string Text (Optional) Text displayed when mark is added. Default none. -- @return #number The resulting Mark ID, which is a number. Can be used to remove the object again. function COORDINATE:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, Text) + local MarkID = UTILS.GetMarkID() if ReadOnly==nil then ReadOnly=false end + Coalition=Coalition or -1 + Color=Color or {1,0,0} Color[4]=Alpha or 1.0 + LineType=LineType or 1 - FillColor=FillColor or Color + + FillColor=FillColor or UTILS.DeepCopy(Color) FillColor[4]=FillAlpha or 0.15 local vecs={} - table.insert(vecs, self:GetVec3()) - for _,coord in ipairs(Coordinates) do - table.insert(vecs, coord:GetVec3()) + vecs[1]=self:GetVec3() + for i,coord in ipairs(Coordinates) do + vecs[i+1]=coord:GetVec3() end - + if #vecs<3 then self:E("ERROR: A free form polygon needs at least three points!") elseif #vecs==3 then @@ -2147,7 +2152,7 @@ do -- COORDINATE elseif #vecs==5 then trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], Color, FillColor, LineType, ReadOnly, Text or "") elseif #vecs==6 then - trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, ReadOnly, Text or "") + trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], Color, FillColor, LineType, Text or "") elseif #vecs==7 then trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], Color, FillColor, LineType, ReadOnly, Text or "") elseif #vecs==8 then diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 035d0e2a4..8ad723486 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1723,7 +1723,7 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph local Coordinates=self:GetVerticiesCoordinates() table.remove(Coordinates, 1) - + self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) end From e4aa23ce3d232294e8630493423ac72242c3c14e Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 15 May 2021 00:29:20 +0200 Subject: [PATCH 255/382] Update Point.lua --- Moose Development/Moose/Core/Point.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 7117ba484..94683b74f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1607,8 +1607,13 @@ do -- COORDINATE roadtype="railroads" end local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) - local vec2={ x = x, y = y } - return COORDINATE:NewFromVec2(vec2) + if x and y then + local vec2={ x = x, y = y } + local coord=COORDINATE:NewFromVec2(vec2) + return coord + else + return nil + end end From a2808163a7487eda99a2700ba103f81f9105dc0e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 20 May 2021 23:23:12 +0200 Subject: [PATCH 256/382] Boom and Probe --- Moose Development/Moose/Ops/AirWing.lua | 47 +++++++++++++++---------- Moose Development/Moose/Ops/Auftrag.lua | 2 +- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 202eb631d..60d412343 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -148,12 +148,13 @@ AIRWING = { -- @field #number heading Heading in degrees. -- @field #number leg Leg length in NM. -- @field #number speed Speed in knots. +-- @field #number refuelsystem Refueling system type: `0=Unit.RefuelingSystem.BOOM_AND_RECEPTACLE`, `1=Unit.RefuelingSystem.PROBE_AND_DROGUE`. -- @field #number noccupied Number of flights on this patrol point. -- @field Wrapper.Marker#MARKER marker F10 marker. --- AIRWING class version. -- @field #string version -AIRWING.version="0.5.1" +AIRWING.version="0.6.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -739,8 +740,9 @@ end -- @param #number Heading Heading in degrees. Default random (0, 360] degrees. -- @param #number LegLength Length of race-track orbit in NM. Default 15 NM. -- @param #number Speed Orbit speed in knots. Default 350 knots. +-- @param #number RefuelSystem Refueling system: 0=Boom, 1=Probe. Default nil=any. -- @return #AIRWING.PatrolData Patrol point table. -function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength) +function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" @@ -750,6 +752,7 @@ function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegL patrolpoint.altitude=Altitude or math.random(10,20)*1000 patrolpoint.speed=Speed or 350 patrolpoint.noccupied=0 + patrolpoint.refuelsystem=RefuelSystem if self.markpoints then patrolpoint.marker=MARKER:New(Coordinate, "New Patrol Point"):ToAll() @@ -783,10 +786,11 @@ end -- @param #number Speed Orbit speed in knots. -- @param #number Heading Heading in degrees. -- @param #number LegLength Length of race-track orbit in NM. +-- @param #number RefuelSystem Set refueling system of tanker: 0=boom, 1=probe. Default any (=nil). -- @return #AIRWING self -function AIRWING:AddPatrolPointTANKER(Coordinate, Altitude, Speed, Heading, LegLength) +function AIRWING:AddPatrolPointTANKER(Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) - local patrolpoint=self:NewPatrolPoint("Tanker", Coordinate, Altitude, Speed, Heading, LegLength) + local patrolpoint=self:NewPatrolPoint("Tanker", Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) table.insert(self.pointsTANKER, patrolpoint) @@ -919,11 +923,12 @@ function AIRWING:onafterStatus(From, Event, To) end ---- Get patrol data +--- Get patrol data. -- @param #AIRWING self -- @param #table PatrolPoints Patrol data points. --- @return #AIRWING.PatrolData -function AIRWING:_GetPatrolData(PatrolPoints) +-- @param #number RefuelSystem If provided, only return points with the specific refueling system. +-- @return #AIRWING.PatrolData Patrol point data table. +function AIRWING:_GetPatrolData(PatrolPoints, RefuelSystem) -- Sort wrt lowest number of flights on this point. local function sort(a,b) @@ -934,14 +939,18 @@ function AIRWING:_GetPatrolData(PatrolPoints) -- Sort data wrt number of flights at that point. table.sort(PatrolPoints, sort) - return PatrolPoints[1] - - else - return self:NewPatrolPoint() - + for _,_patrolpoint in pairs(PatrolPoints) do + local patrolpoint=_patrolpoint --#AIRWING.PatrolData + if (RefuelSystem and patrolpoint.refuelsystem and RefuelSystem==patrolpoint.refuelsystem) or RefuelSystem==nil or patrolpoint.refuelsystem==nil then + return patrolpoint + end + end + end + -- Return a new point. + return self:NewPatrolPoint() end --- Check how many CAP missions are assigned and add number of missing missions. @@ -980,28 +989,29 @@ function AIRWING:CheckTANKER() local Nboom=0 local Nprob=0 - -- Count tanker mission. + -- Count tanker missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER then - if mission.refuelSystem==0 then + if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then Nboom=Nboom+1 - elseif mission.refuelSystem==1 then + elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then Nprob=Nprob+1 end end end - + + -- Check missing boom tankers. for i=1,self.nflightsTANKERboom-Nboom do local patrol=self:_GetPatrolData(self.pointsTANKER) local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 1) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.BOOM_AND_RECEPTACLE) mission.patroldata=patrol @@ -1013,13 +1023,14 @@ function AIRWING:CheckTANKER() end + -- Check missing probe tankers. for i=1,self.nflightsTANKERprobe-Nprob do local patrol=self:_GetPatrolData(self.pointsTANKER) local altitude=patrol.altitude+1000*patrol.noccupied - local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, 0) + local mission=AUFTRAG:NewTANKER(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg, Unit.RefuelingSystem.PROBE_AND_DROGUE) mission.patroldata=patrol diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f285e461d..7a952cba8 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -688,7 +688,7 @@ end -- @param #number Speed Orbit speed in knots. Default 350 kts. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). -- @param #number Leg Length of race-track in NM. Default 10 NM. --- @param #number RefuelSystem Refueling system (1=boom, 0=probe). This info is *only* for AIRWINGs so they launch the right tanker type. +-- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type. -- @return #AUFTRAG self function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem) From 47d814e409996fbbbe4d5f3719c452cfd206f8f2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 21 May 2021 12:35:32 +0200 Subject: [PATCH 257/382] CONTROLLABLE:PatrolRouteRandom fix for Subs (#1536) * CONTROLLABLE:PatrolRouteRandom fix for Subs fixes issue #1535 * Update Controllable.lua * Update Controllable.lua --- .../Moose/Wrapper/Controllable.lua | 165 +++++++++++------- 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e2d3edca6..58e1a747e 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -175,11 +175,8 @@ -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- -- ## 5.5) Air-2-Air missile attack range: --- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. --- --- ## 5.6) GROUND units attack range: --- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically. --- +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -507,7 +504,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) tasks = DCSTasks } } - + return DCSTaskCombo end @@ -805,12 +802,12 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) - local CommandSetFrequency = { - id = 'SetFrequency', - params = { - frequency = Frequency*1000000, - modulation = Modulation or radio.modulation.AM, - } + local CommandSetFrequency = { + id = 'SetFrequency', + params = { + frequency = Frequency*1000000, + modulation = Modulation or radio.modulation.AM, + } } if Delay and Delay>0 then @@ -884,7 +881,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At groupId = AttackGroup:GetID(), weaponType = WeaponType or 1073741822, expend = WeaponExpend or "Auto", - attackQtyLimit = AttackQty and true or false, + attackQtyLimit = AttackQty and true or false, attackQty = AttackQty or 1, directionEnabled = Direction and true or false, direction = Direction and math.rad(Direction) or 0, @@ -924,7 +921,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta weaponType = WeaponType or 1073741822, } } - + return DCSTask end @@ -1088,7 +1085,7 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, -- Distribution --local distribution={} --distribution[id]=gids - + local groupID=self and self:GetID() local DCSTask = { @@ -1317,7 +1314,7 @@ function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration) duration = Duration, }, } - + return DCSTask end @@ -1432,7 +1429,7 @@ end -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). -- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag -- @param #number Altitude (Optional) Altitude in meters. --- @param #number ASL Altitude is above mean sea level. Default is above ground level. +-- @param #number ASL Altitude is above mean sea level. Default is above ground level. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL ) @@ -1454,7 +1451,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end - + if Altitude then DCSTask.params.altitude=Altitude end @@ -1462,7 +1459,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti if WeaponType then DCSTask.params.weaponType=WeaponType end - + --self:I(DCSTask) return DCSTask @@ -1482,6 +1479,7 @@ end --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. +-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators. -- @param #CONTROLLABLE self -- @param Wrapper.Group#GROUP AttackGroup Target GROUP object. -- @param #number WeaponType Bitmask of weapon types, which are allowed to use. @@ -1489,7 +1487,7 @@ end -- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz. -- @param #number Modulation Modulation of radio for communication. Default 0=AM. --- @param #number CallsignName Callsign enumerator name of the FAC. +-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units) -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber ) @@ -1811,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) -- DCS task. local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) - + return DCSTask end @@ -1853,8 +1851,27 @@ do -- Patrol methods -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - local From = FromCoord:WaypointGround( 120 ) - + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end + + + local Waypoint = Waypoints[1] + local Speed = Waypoint.speed or (20 / 3.6) + local From = FromCoord:WaypointGround( Speed ) + + if IsSub then + From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) + end + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) @@ -1894,7 +1911,16 @@ do -- Patrol methods if ToWaypoint then FromWaypoint = ToWaypoint end - + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Loop until a waypoint has been found that is not the same as the current waypoint. -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. @@ -1909,9 +1935,13 @@ do -- Patrol methods local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) @@ -1952,9 +1982,20 @@ do -- Patrol methods self:F( { PatrolGroup = PatrolGroup:GetName() } ) if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Select a random Zone and get the Coordinate of the new Zone. local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE @@ -1962,9 +2003,13 @@ do -- Patrol methods -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, depth ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax ) @@ -1988,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points ) id = 'Mission', params = { airborne = self:IsAir(), - route = {points = Points}, + route = {points = Points}, }, } @@ -2914,9 +2959,9 @@ end function CONTROLLABLE:OptionROE(ROEvalue) local DCSControllable = self:GetDCSObject() - + if DCSControllable then - + local Controller = self:_GetController() if self:IsAir() then @@ -3480,13 +3525,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) self:F2( { self.ControllableName } ) - + if Prohibit==nil then Prohibit=true end if self:IsAir() then - self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) + self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) end return self @@ -3497,9 +3542,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 0) + self:SetOption(AI.Option.Air.id.ECM_USING, 0) end return self @@ -3510,9 +3555,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_OnlyLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 1) + self:SetOption(AI.Option.Air.id.ECM_USING, 1) end return self @@ -3524,9 +3569,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_DetectedLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 2) + self:SetOption(AI.Option.Air.id.ECM_USING, 2) end return self @@ -3537,9 +3582,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_AlwaysOn() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 3) + self:SetOption(AI.Option.Air.id.ECM_USING, 3) end return self @@ -3681,22 +3726,22 @@ end --- Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self --- @param #number range Defines the range +-- @param #number range Defines the range -- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack function CONTROLLABLE:OptionAAAttackRange(range) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- defaults to 3 local range = range or 3 if range < 0 or range > 4 then range = 3 - end + end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsAir() then - self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) + self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) end end return self @@ -3709,7 +3754,7 @@ end -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionEngageRange(EngageRange) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. EngageRange=EngageRange or 100 if EngageRange < 0 or EngageRange > 100 then @@ -3718,9 +3763,9 @@ function CONTROLLABLE:OptionEngageRange(EngageRange) local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) + self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) end end return self @@ -3736,9 +3781,9 @@ end -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @return #CONTROLLABLE self function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) - local _coord = self:GetCoordinate() + local _coord = self:GetCoordinate() local _radius = radius or 500 local _speed = speed or 20 local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) @@ -3746,7 +3791,7 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc local _grptsk = {} local _candoroad = false local _shortcut = shortcut or false - + -- create a DCS Task an push it on the group -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) if onroad then @@ -3756,23 +3801,23 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") end - return self + return self end --- Defines how long a GROUND unit/group will move to avoid an ongoing attack. -- @param #CONTROLLABLE self --- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. +-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionDisperseOnAttack(Seconds) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. local seconds = Seconds or 0 local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) end end return self From d39074bf3e7d9bf56e044102fce6e54ccb0c44ec Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 May 2021 00:35:00 +0200 Subject: [PATCH 258/382] Sound --- Moose Development/Moose/AddOns/SRS.lua | 151 +++++++++++++++++++++ Moose Development/Moose/Core/SoundFile.lua | 110 +++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 Moose Development/Moose/AddOns/SRS.lua create mode 100644 Moose Development/Moose/Core/SoundFile.lua diff --git a/Moose Development/Moose/AddOns/SRS.lua b/Moose Development/Moose/AddOns/SRS.lua new file mode 100644 index 000000000..681b3dc2a --- /dev/null +++ b/Moose Development/Moose/AddOns/SRS.lua @@ -0,0 +1,151 @@ +--- **AddOn** - Simple Radio +-- +-- === +-- +-- **Main Features:** +-- +-- * SRS integration +-- +-- === +-- +-- ## Youtube Videos: +-- +-- * None +-- +-- === +-- +-- ## Missions: Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20ATIS) +-- +-- === +-- +-- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- +-- === +-- +-- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. +-- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Addons.SRS +-- @image Addons_SRS.png + + +--- MSRS class. +-- @type MSRS +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @extends Core.Fsm#FSM + +--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde +-- +-- === +-- +-- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png) +-- +-- # The MSRS Concept +-- +-- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. +-- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- +-- @field #MSRS +MSRS = { + ClassName = "MSRS", + lid = nil, +} + +--- ATIS class version. +-- @field #string version +MSRS.version="0.9.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ATIS class object for a specific aircraft carrier unit. +-- @param #MSRS self +-- @param #string airbasename Name of the airbase. +-- @param #number frequency Radio frequency in MHz. Default 143.00 MHz. +-- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators +-- @return #MSRS self +function MSRS:New(airbasename, frequency, modulation) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #ATIS + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set sound files folder within miz file. +-- @param #MSRS self +-- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @return #MSRS self +function MSRS:SetSoundfilesPath(path) + self.soundpath=tostring(path or "ATIS Soundfiles/") + self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) + return self +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start ATIS FSM. +-- @param #MSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterStart(From, Event, To) + +end + +--- Update status. +-- @param #MSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterStatus(From, Event, To) + + -- Get FSM state. + local fsmstate=self:GetState() + + self:__Status(-60) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if radio queue is empty. If so, start broadcasting the message again. +-- @param #MRSRS self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function MSRS:onafterCheckQueue(From, Event, To) + + -- Check back in 5 seconds. + self:__CheckQueue(-5) +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Core/SoundFile.lua b/Moose Development/Moose/Core/SoundFile.lua new file mode 100644 index 000000000..85e7745ac --- /dev/null +++ b/Moose Development/Moose/Core/SoundFile.lua @@ -0,0 +1,110 @@ +--- **Core** - Sound file +-- +-- === +-- +-- ## Features: +-- +-- * Add a sound file to the +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- +-- @module Core.UserFlag +-- @image Core_Userflag.JPG +-- + +do -- Sound File + + --- @type SOUNDFILE + -- @field #string ClassName Name of the class + -- @field #string Name Name of the flag. + -- @extends Core.Base#BASE + + + --- Management of DCS User Flags. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDFILE + SOUNDFILE={ + ClassName = "SOUNDFILE", + Name = nil, + } + + --- Constructor. + -- @param #SOUNDFILE self + -- @param #string UserFlagName The name of the userflag, which is a free text string. + -- @return #SOUNDFILE + function SOUNDFILE:New( UserFlagName ) --R2.3 + + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + self.UserFlagName = UserFlagName + + return self + end + + --- Get the userflag name. + -- @param #USERFLAG self + -- @return #string Name of the user flag. + function USERFLAG:GetName() + return self.UserFlagName + end + + --- Set the userflag to a given Number. + -- @param #USERFLAG self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @param #number Delay Delay in seconds, before the flag is set. + -- @return #USERFLAG The userflag instance. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. + -- + function USERFLAG:Set( Number, Delay ) --R2.3 + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) + else + --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) + trigger.action.setUserFlag( self.UserFlagName, Number ) + end + + return self + end + + + --- Get the userflag Number. + -- @param #USERFLAG self + -- @return #number Number The number value to be checked if it is the same as the userflag. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. + -- + function USERFLAG:Get() --R2.3 + + return trigger.misc.getUserFlag( self.UserFlagName ) + end + + + + --- Check if the userflag has a value of Number. + -- @param #USERFLAG self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @return #boolean true if the Number is the value of the userflag. + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- if BlueVictory:Is( 1 ) then + -- return "Blue has won" + -- end + function USERFLAG:Is( Number ) --R2.3 + + return trigger.misc.getUserFlag( self.UserFlagName ) == Number + + end + +end \ No newline at end of file From 7a3fb9585110359225766c57e4f995ab39c8a8b6 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 May 2021 23:26:18 +0200 Subject: [PATCH 259/382] Sound Update 1 - Moved all related files to new Moose folder "Sound/" --- Moose Development/Moose/Core/SoundFile.lua | 110 -------------- Moose Development/Moose/Modules.lua | 11 +- .../Moose/{Core => Sound}/Radio.lua | 0 .../Moose/{Core => Sound}/RadioQueue.lua | 33 +++-- .../Moose/{Core => Sound}/RadioSpeech.lua | 0 .../Moose/{AddOns => Sound}/SRS.lua | 0 Moose Development/Moose/Sound/SoundFile.lua | 138 ++++++++++++++++++ .../Moose/{Core => Sound}/UserSound.lua | 0 Moose Development/Moose/Utilities/Utils.lua | 20 ++- Moose Setup/Moose.files | 11 +- 10 files changed, 195 insertions(+), 128 deletions(-) delete mode 100644 Moose Development/Moose/Core/SoundFile.lua rename Moose Development/Moose/{Core => Sound}/Radio.lua (100%) rename Moose Development/Moose/{Core => Sound}/RadioQueue.lua (93%) rename Moose Development/Moose/{Core => Sound}/RadioSpeech.lua (100%) rename Moose Development/Moose/{AddOns => Sound}/SRS.lua (100%) create mode 100644 Moose Development/Moose/Sound/SoundFile.lua rename Moose Development/Moose/{Core => Sound}/UserSound.lua (100%) diff --git a/Moose Development/Moose/Core/SoundFile.lua b/Moose Development/Moose/Core/SoundFile.lua deleted file mode 100644 index 85e7745ac..000000000 --- a/Moose Development/Moose/Core/SoundFile.lua +++ /dev/null @@ -1,110 +0,0 @@ ---- **Core** - Sound file --- --- === --- --- ## Features: --- --- * Add a sound file to the --- --- === --- --- ### Author: **funkyfranky** --- --- === --- --- @module Core.UserFlag --- @image Core_Userflag.JPG --- - -do -- Sound File - - --- @type SOUNDFILE - -- @field #string ClassName Name of the class - -- @field #string Name Name of the flag. - -- @extends Core.Base#BASE - - - --- Management of DCS User Flags. - -- - -- # 1. USERFLAG constructor - -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. - -- - -- @field #SOUNDFILE - SOUNDFILE={ - ClassName = "SOUNDFILE", - Name = nil, - } - - --- Constructor. - -- @param #SOUNDFILE self - -- @param #string UserFlagName The name of the userflag, which is a free text string. - -- @return #SOUNDFILE - function SOUNDFILE:New( UserFlagName ) --R2.3 - - local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE - - self.UserFlagName = UserFlagName - - return self - end - - --- Get the userflag name. - -- @param #USERFLAG self - -- @return #string Name of the user flag. - function USERFLAG:GetName() - return self.UserFlagName - end - - --- Set the userflag to a given Number. - -- @param #USERFLAG self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @param #number Delay Delay in seconds, before the flag is set. - -- @return #USERFLAG The userflag instance. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. - -- - function USERFLAG:Set( Number, Delay ) --R2.3 - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) - else - --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) - trigger.action.setUserFlag( self.UserFlagName, Number ) - end - - return self - end - - - --- Get the userflag Number. - -- @param #USERFLAG self - -- @return #number Number The number value to be checked if it is the same as the userflag. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- local BlueVictoryValue = BlueVictory:Get() -- Get the UserFlag VictoryBlue value. - -- - function USERFLAG:Get() --R2.3 - - return trigger.misc.getUserFlag( self.UserFlagName ) - end - - - - --- Check if the userflag has a value of Number. - -- @param #USERFLAG self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @return #boolean true if the Number is the value of the userflag. - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- if BlueVictory:Is( 1 ) then - -- return "Blue has won" - -- end - function USERFLAG:Is( Number ) --R2.3 - - return trigger.misc.getUserFlag( self.UserFlagName ) == Number - - end - -end \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index c4bb9eb87..06f53d723 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -6,7 +6,6 @@ __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) -__Moose.Include( 'Scripts/Moose/Core/UserSound.lua' ) __Moose.Include( 'Scripts/Moose/Core/Report.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) __Moose.Include( 'Scripts/Moose/Core/ScheduleDispatcher.lua' ) @@ -21,9 +20,6 @@ __Moose.Include( 'Scripts/Moose/Core/Point.lua' ) __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) -__Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) -__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) -__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) @@ -113,6 +109,13 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SoundFile.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/Radio.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SRS.lua' ) + __Moose.Include( 'Scripts/Moose/Tasking/CommandCenter.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Mission.lua' ) __Moose.Include( 'Scripts/Moose/Tasking/Task.lua' ) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Sound/Radio.lua similarity index 100% rename from Moose Development/Moose/Core/Radio.lua rename to Moose Development/Moose/Sound/Radio.lua diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua similarity index 93% rename from Moose Development/Moose/Core/RadioQueue.lua rename to Moose Development/Moose/Sound/RadioQueue.lua index a63677a98..3e6afcd51 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -1,4 +1,4 @@ ---- **Core** - Queues Radio Transmissions. +--- **Sound** - Queues Radio Transmissions. -- -- === -- @@ -10,8 +10,8 @@ -- -- ### Authors: funkyfranky -- --- @module Core.RadioQueue --- @image Core_Radio.JPG +-- @module Sound.RadioQueue +-- @image Sound_Radio.JPG --- Manages radio transmissions. -- @@ -125,9 +125,9 @@ function RADIOQUEUE:Start(delay, dt) -- Start Scheduler. if self.schedonce then - self:_CheckRadioQueueDelayed(delay) + self:_CheckRadioQueueDelayed(self.delay) else - self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, delay, dt) + self.RQid=self.scheduler:Schedule(nil, RADIOQUEUE._CheckRadioQueue, {self}, self.delay, self.dt) end return self @@ -202,7 +202,7 @@ end --- Add a transmission to the radio queue. -- @param #RADIOQUEUE self -- @param #RADIOQUEUE.Transmission transmission The transmission data table. --- @return #RADIOQUEUE self The RADIOQUEUE object. +-- @return #RADIOQUEUE self function RADIOQUEUE:AddTransmission(transmission) self:F({transmission=transmission}) @@ -221,7 +221,7 @@ function RADIOQUEUE:AddTransmission(transmission) return self end ---- Add a transmission to the radio queue. +--- Create a new transmission and add it to the radio queue. -- @param #RADIOQUEUE self -- @param #string filename Name of the sound file. Usually an ogg or wav file type. -- @param #number duration Duration in seconds the file lasts. @@ -233,6 +233,8 @@ end -- @return #RADIOQUEUE self The RADIOQUEUE object. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) + env.info("FF new transmission.") + -- Sanity checks. if not filename then self:E(self.lid.."ERROR: No filename specified.") @@ -272,6 +274,19 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, return self end +--- Create a new transmission and add it to the radio queue. +-- @param #RADIOQUEUE self +-- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added. +-- @param #number tstart Start time (abs) seconds. Default now. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @return #RADIOQUEUE self +function RADIOQUEUE:AddSoundfile(soundfile, tstart, interval) + env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) + self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) + + return self +end + --- Convert a number (as string) into a radio transmission. -- E.g. for board number or headings. -- @param #RADIOQUEUE self @@ -292,7 +307,7 @@ function RADIOQUEUE:Number2Transmission(number, delay, interval) end -- Split string into characters. - local numbers=_split(number) + local numbers=UTILS.GetCharacters(number) --l_split(number) local wait=0 for i=1,#numbers do @@ -547,7 +562,7 @@ function RADIOQUEUE:_GetRadioSender() return nil end ---- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. +--- Get unit from which we want to transmit a radio message. This has to be an aircraft or ground unit for subtitles to work. -- @param #RADIOQUEUE self -- @return DCS#Vec3 Vector 3D. function RADIOQUEUE:_GetRadioSenderCoord() diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua similarity index 100% rename from Moose Development/Moose/Core/RadioSpeech.lua rename to Moose Development/Moose/Sound/RadioSpeech.lua diff --git a/Moose Development/Moose/AddOns/SRS.lua b/Moose Development/Moose/Sound/SRS.lua similarity index 100% rename from Moose Development/Moose/AddOns/SRS.lua rename to Moose Development/Moose/Sound/SRS.lua diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua new file mode 100644 index 000000000..f1a759cbb --- /dev/null +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -0,0 +1,138 @@ +--- **Sound** - Sound file management. +-- +-- === +-- +-- ## Features: +-- +-- * Add a sound file to the +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- +-- @module Sound.Soundfile +-- @image Sound_Soundfile.png +-- + +do -- Sound File + + --- @type SOUNDFILE + -- @field #string ClassName Name of the class + -- @field #string filename Name of the flag. + -- @field #string path Directory path, where the sound file is located. + -- @field #string duration Duration of the sound file in seconds. + -- @field #string subtitle Subtitle of the transmission. + -- @field #number subduration Duration in seconds how long the subtitle is displayed. + -- @field #boolean insideMiz If true (default), the sound file is located inside the mission .miz file. + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDFILE + SOUNDFILE={ + ClassName = "SOUNDFILE", + filename = nil, + path = "l10n/DEFAULT", + duration = 3, + subtitle = nil, + subduration = 0, + insideMiz = true, + } + + --- Constructor to create a new SOUNDFILE object. + -- @param #SOUNDFILE self + -- @param #string filename The name of the sound file, e.g. "Hello World.ogg". + -- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file. + -- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds. + -- @return #SOUNDFILE self + function SOUNDFILE:New(filename, Path, Duration) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + -- Set file name. + self.filename=filename or "Hallo World.ogg" + + -- Set path + self.path=Path or "l10n/DEFAULT/" + + self.duration=Duration or 3 + + -- Debug info: + self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) + + return self + end + + --- Set path, where the . + -- @param #SOUNDFILE self + -- @param #string Path Path to the directory, where the sound file is located. + -- @return self + function SOUNDFILE:SetPath(Path) + + self.path=Path or "l10n/DEFAULT/" + + while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do + self.path=self.path:sub(1,-1) + end + + return self + end + + + --- Get the sound file name. + -- @param #SOUNDFILE self + -- @return #string Name of the soud file. This does *not* include its path. + function SOUNDFILE:GetFileName() + return self.filename + end + + --- Get path of the directory, where the sound file is located. + -- @param #SOUNDFILE self + -- @return #string Path. + function SOUNDFILE:GetPath() + local path=self.path or "l10n/DEFAULT" + path=path.."/" + return path + end + + --- Get the complete sound file name inlcuding its path. + -- @param #SOUNDFILE self + -- @return #string Name of the sound file. + function SOUNDFILE:GetName() + local filename=self:GetFileName() + local path=self:GetPath() + local name=string.format("%s/%s", path, filename) + return name + end + + + --- Set the userflag to a given Number. + -- @param #SOUNDFILE self + -- @param #number Number The number value to be checked if it is the same as the userflag. + -- @param #number Delay Delay in seconds, before the flag is set. + -- @return #SOUNDFILE self + -- @usage + -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) + -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. + -- + function SOUNDFILE:Set( Number, Delay ) --R2.3 + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) + else + --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) + trigger.action.setUserFlag( self.UserFlagName, Number ) + end + + return self + end + +end \ No newline at end of file diff --git a/Moose Development/Moose/Core/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua similarity index 100% rename from Moose Development/Moose/Core/UserSound.lua rename to Moose Development/Moose/Sound/UserSound.lua diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3fd6e9482..af7642406 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -682,7 +682,10 @@ function UTILS.IsInSphere( InVec3, Vec3, Radius ) return InSphere end --- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s. +--- Beaufort scale: returns Beaufort number and wind description as a function of wind speed in m/s. +-- @param #number speed Wind speed in m/s. +-- @return #number Beaufort number. +-- @return #string Beauford wind description. function UTILS.BeaufortScale(speed) local bn=nil local bd=nil @@ -742,6 +745,21 @@ function UTILS.Split(str, sep) return result end +--- Get a table of all characters in a string. +-- @param #string str Sting. +-- @return #table Individual characters. +function UTILS.GetCharacters(str) + + local chars={} + + for i=1,#str do + local c=str:sub(i,i) + table.insert(chars, c) + end + + return chars +end + --- Convert time in seconds to hours, minutes and seconds. -- @param #number seconds Time in seconds, e.g. from timer.getAbsTime() function. -- @param #boolean short (Optional) If true, use short output, i.e. (HH:)MM:SS without day. diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 79c9b120e..939bf7770 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -7,7 +7,6 @@ Utilities/Templates.lua Core/Base.lua Core/UserFlag.lua -Core/UserSound.lua Core/Report.lua Core/Scheduler.lua Core/ScheduleDispatcher.lua @@ -22,9 +21,6 @@ Core/Point.lua 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 @@ -114,6 +110,13 @@ Actions/Act_Route.lua Actions/Act_Account.lua Actions/Act_Assist.lua +Sound/UserSound.lua +Sound/SoundFile.lua +Sound/Radio.lua +Sound/RadioQueue.lua +Sound/RadioSpeech.lua +Sound/SRS.lua + Tasking/CommandCenter.lua Tasking/Mission.lua Tasking/Task.lua From e55bb21401520d1fc4bc684ea3250fe00f025103 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 May 2021 23:32:26 +0200 Subject: [PATCH 260/382] Sound 2 --- Moose Development/Moose/Sound/SRS.lua | 106 ++++++++++---------- Moose Development/Moose/Sound/SoundFile.lua | 2 +- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 681b3dc2a..1cad9d407 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1,10 +1,11 @@ ---- **AddOn** - Simple Radio +--- **Sound** - Simple Radio Standalone Integration -- -- === -- -- **Main Features:** -- --- * SRS integration +-- * Play sound files via SRS +-- * Play text-to-speach via SRS -- -- === -- @@ -31,12 +32,11 @@ -- @module Addons.SRS -- @image Addons_SRS.png - --- MSRS class. -- @type MSRS -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. --- @extends Core.Fsm#FSM +-- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde -- @@ -46,8 +46,11 @@ -- -- # The MSRS Concept -- --- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. --- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS). +-- +-- # Prerequisites +-- +-- This script needs SRS version >= 0.9.6. -- -- @field #MSRS MSRS = { @@ -57,7 +60,7 @@ MSRS = { --- ATIS class version. -- @field #string version -MSRS.version="0.9.1" +MSRS.version="0.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -69,16 +72,22 @@ MSRS.version="0.9.1" -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new ATIS class object for a specific aircraft carrier unit. +--- Create a new MSRS object. -- @param #MSRS self --- @param #string airbasename Name of the airbase. --- @param #number frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators +-- @param #string PathToSRS Path to the directory, where SRS is located. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. +-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. -- @return #MSRS self -function MSRS:New(airbasename, frequency, modulation) +function MSRS:New(PathToSRS, Frequency, Modulation) -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #ATIS + local self=BASE:Inherit(self, FSM:New()) -- #MSRS + + self.path=self:SetPath(PathToSRS) + + self.frequency=Frequency or 143 + + self.modulation=Modulation or radio.modulation.AM return self @@ -88,56 +97,51 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set sound files folder within miz file. + +--- Set path, where the sound file is located. -- @param #MSRS self --- @param #string path Path for sound files. Default "ATIS Soundfiles/". Mind the slash "/" at the end! +-- @param #string Path Path to the directory, where the sound file is located. -- @return #MSRS self -function MSRS:SetSoundfilesPath(path) - self.soundpath=tostring(path or "ATIS Soundfiles/") - self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) +function MSRS:SetPath(Path) + + if Path==nil then + return nil + end + + self.path=Path + + -- Remove (back)slashes. + while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do + self.path=self.path:sub(1,-1) + end + return self end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start & Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Start ATIS FSM. +--- Get path to SRS directory. -- @param #MSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterStart(From, Event, To) +-- @return #string +function MSRS:GetPath() + return self.path +end + +--- Set path, where the sound file is located. +-- @param #MSRS self +-- @param #string Path Path to the directory, where the sound file is located. +-- @return #MSRS self +function MSRS:PlaySoundfile(Soundfile) + + end ---- Update status. +--- Set path, where the sound file is located. -- @param #MSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterStatus(From, Event, To) +-- @param #string Message Text message. +-- @return #MSRS self +function MSRS:PlayText(Message) - -- Get FSM state. - local fsmstate=self:GetState() - self:__Status(-60) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FSM Events -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check if radio queue is empty. If so, start broadcasting the message again. --- @param #MRSRS self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function MSRS:onafterCheckQueue(From, Event, To) - - -- Check back in 5 seconds. - self:__CheckQueue(-5) end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index f1a759cbb..564fe1306 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -71,7 +71,7 @@ do -- Sound File return self end - --- Set path, where the . + --- Set path, where the sound file is located. -- @param #SOUNDFILE self -- @param #string Path Path to the directory, where the sound file is located. -- @return self From 85fef96d0038f3573c4baba3ba4489ca02e5f777 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 24 May 2021 10:00:16 +0200 Subject: [PATCH 261/382] ZONE_POLYGON_BASE:Boundary added (#1537) * ZONE_POLYGON_BASE:Boundary added ZONE_POLYGON_BASE:Boundary added * Update Zone.lua Change Radius default value --- Moose Development/Moose/Core/Zone.lua | 749 +++++++++++++++----------- 1 file changed, 446 insertions(+), 303 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ad723486..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1,9 +1,9 @@ --- **Core** - Define zones within your mission of various forms, with various capabilities. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Create radius zones. -- * Create trigger zones. -- * Create polygon zones. @@ -18,24 +18,24 @@ -- * Get zone bounding box. -- * Set/get zone name. -- * Draw zones (circular and polygon) on the F10 map. --- --- +-- +-- -- There are essentially two core functions that zones accomodate: --- +-- -- * Test if an object is within the zone boundaries. -- * Provide the zone behaviour. Some zones are static, while others are moveable. --- +-- -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- +-- -- * Test if completely within the zone. -- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. -- * ... --- +-- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- +-- -- * @{#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. -- * @{#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. -- * @{#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. @@ -43,15 +43,15 @@ -- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. -- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- -- === --- +-- +-- ### Author: **FlightControl** +-- ### Contributions: +-- +-- === +-- -- @module Core.Zone --- @image Core_Zones.JPG +-- @image Core_Zones.JPG --- @type ZONE_BASE @@ -63,28 +63,28 @@ --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Each zone has a name: --- +-- -- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. -- * @{#ZONE_BASE.SetName}(): Sets the name of the zone. --- --- +-- +-- -- ## Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: --- +-- -- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a 2D vector is within the zone. -- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a 3D vector is within the zone. -- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a 2D point vector is within the zone. -- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a 3D point vector is within the zone. --- +-- -- ## A zone has a probability factor that can be set to randomize a selection between zones: --- +-- -- * @{#ZONE_BASE.SetZoneProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. --- +-- -- ## A zone manages vectors: --- +-- -- * @{#ZONE_BASE.GetVec2}(): Returns the 2D vector coordinate of the zone. -- * @{#ZONE_BASE.GetVec3}(): Returns the 3D vector coordinate of the zone. -- * @{#ZONE_BASE.GetPointVec2}(): Returns the 2D point vector coordinate of the zone. @@ -92,16 +92,16 @@ -- * @{#ZONE_BASE.GetRandomVec2}(): Define a random 2D vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec2}(): Define a random 2D point vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec3}(): Define a random 3D point vector within the zone. --- +-- -- ## A zone has a bounding square: --- +-- -- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. --- --- ## A zone can be marked: --- +-- +-- ## A zone can be marked: +-- -- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. -- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. --- +-- -- @field #ZONE_BASE ZONE_BASE = { ClassName = "ZONE_BASE", @@ -129,7 +129,7 @@ function ZONE_BASE:New( ZoneName ) self:F( ZoneName ) self.ZoneName = ZoneName - + return self end @@ -206,7 +206,7 @@ end -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() - return nil + return nil end --- Returns a @{Core.Point#POINT_VEC2} of the zone. @@ -215,30 +215,14 @@ end -- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. function ZONE_BASE:GetPointVec2() self:F2( self.ZoneName ) - + local Vec2 = self:GetVec2() local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) self:T2( { PointVec2 } ) - - return PointVec2 -end - ---- Returns a @{Core.Point#COORDINATE} of the zone. --- @param #ZONE_BASE self --- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local Coordinate = COORDINATE:NewFromVec2( Vec2 ) - - self:T2( { Coordinate } ) - - return Coordinate + return PointVec2 end @@ -248,16 +232,16 @@ end -- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- Returns a @{Core.Point#POINT_VEC3} of the zone. @@ -266,14 +250,14 @@ end -- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) self:F2( self.ZoneName ) - + local Vec3 = self:GetVec3( Height ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2( { PointVec3 } ) - - return PointVec3 + + return PointVec3 end --- Returns a @{Core.Point#COORDINATE} of the zone. @@ -281,15 +265,27 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2( self.ZoneName ) - + self:F2(self.ZoneName) + local Vec3 = self:GetVec3( Height ) - local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) + if self.Coordinate then - self:T2( { PointVec3 } ) - - return PointVec3 + -- Update coordinates. + self.Coordinate.x=Vec3.x + self.Coordinate.y=Vec3.y + self.Coordinate.z=Vec3.z + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + else + + -- Create a new coordinate object. + self.Coordinate=COORDINATE:NewFromVec3(Vec3) + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + end + + return self.Coordinate end @@ -339,7 +335,7 @@ function ZONE_BASE:SetColor(RGBcolor, Alpha) RGBcolor=RGBcolor or {1, 0, 0} Alpha=Alpha or 0.15 - + self.Color={} self.Color[1]=RGBcolor[1] self.Color[2]=RGBcolor[2] @@ -375,7 +371,7 @@ function ZONE_BASE:GetColorAlpha() return alpha end ---- Remove the drawing of the zone from the F10 map. +--- Remove the drawing of the zone from the F10 map. -- @param #ZONE_BASE self -- @param #number Delay (Optional) Delay before the drawing is removed. -- @return #ZONE_BASE self @@ -391,7 +387,7 @@ function ZONE_BASE:UndrawZone(Delay) end --- Get ID of the zone object drawn on the F10 map. --- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. +-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. -- @param #ZONE_BASE self -- @return #number Unique ID of the function ZONE_BASE:GetDrawID() @@ -404,7 +400,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. @@ -413,7 +409,7 @@ end -- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) - + self.ZoneProbability = ZoneProbability or 1 return self end @@ -422,8 +418,8 @@ 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 @@ -432,15 +428,15 @@ end -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. -- @return #nil The zone is not selected taking into account the randomization probability factor. -- @usage --- +-- -- local ZoneArray = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- +-- -- -- We set a zone probability of 70% to the first zone and 30% to the second zone. -- ZoneArray[1]:SetZoneProbability( 0.5 ) -- ZoneArray[2]:SetZoneProbability( 0.5 ) --- +-- -- local ZoneSelected = nil --- +-- -- while ZoneSelected == nil do -- for _, Zone in pairs( ZoneArray ) do -- ZoneSelected = Zone:GetZoneMaybe() @@ -449,12 +445,12 @@ end -- end -- end -- end --- +-- -- -- The result should be that Zone1 would be more probable selected than Zone2. --- +-- function ZONE_BASE:GetZoneMaybe() self:F2() - + local Randomization = math.random() if Randomization <= self.ZoneProbability then return self @@ -472,34 +468,34 @@ end --- The ZONE_RADIUS class defined by a zone name, a location and a radius. -- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. --- +-- -- ## ZONE_RADIUS constructor --- +-- -- * @{#ZONE_RADIUS.New}(): Constructor. --- +-- -- ## Manage the radius of the zone --- +-- -- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone. -- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone. --- +-- -- ## Manage the location of the zone --- +-- -- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCS#Vec3} of the zone, taking an additional height parameter. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. --- +-- -- ## Draw zone --- +-- -- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map. --- +-- -- @field #ZONE_RADIUS ZONE_RADIUS = { ClassName="ZONE_RADIUS", @@ -512,15 +508,54 @@ ZONE_RADIUS = { -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 - + + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + return self end +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec2 Vec2 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) + + -- New center of the zone. + self.Vec2=Vec2 + + if Radius then + self.Radius=Radius + end + + return self +end + +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec3 Vec3 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) + + -- New center of the zone. + self.Vec2.x=Vec3.x + self.Vec2.y=Vec3.z + + if Radius then + self.Radius=Radius + end + + return self +end + --- Mark the zone with markers on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. @@ -534,21 +569,21 @@ function ZONE_RADIUS:MarkZone(Points) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, (360 / Points ) do - + local Radial = Angle * RadialBase / 360 - + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) end - + end ---- Draw the zone circle on the F10 map. +--- Draw the zone circle on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. @@ -561,16 +596,16 @@ end function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) local coordinate=self:GetCoordinate() - + local Radius=self:GetRadius() - + Color=Color or self:GetColorRGB() Alpha=Alpha or 1 FillColor=FillColor or Color FillAlpha=FillAlpha or self:GetColorAlpha() self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + return self end @@ -589,17 +624,17 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Angle local RadialBase = math.pi*2 - + -- for Angle = 0, 360, (360 / Points ) do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - + local Tire = { - ["country"] = CountryName, + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -633,7 +668,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 AngleOffset = AngleOffset or 0 @@ -641,7 +676,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -665,14 +700,14 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 - + Points = Points and Points or 360 local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -714,8 +749,8 @@ function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Sets the @{DCS#Vec2} of the zone. @@ -724,12 +759,12 @@ end -- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) - + self.Vec2 = Vec2 self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Returns the @{DCS#Vec3} of the ZONE_RADIUS. @@ -745,8 +780,8 @@ function ZONE_RADIUS:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end @@ -755,7 +790,7 @@ end --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that after a zone has been scanned, the zone can be evaluated by: --- +-- -- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. -- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. -- * @{ZONE_RADIUS.IsSomeInZoneOfCoalition}(): Scan if there is some presence of units in the zone of the given coalition. @@ -777,7 +812,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -790,18 +825,18 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 - if ZoneObject then - + if ZoneObject then + local ObjectCategory = ZoneObject:getCategory() - + --local name=ZoneObject:getName() --env.info(string.format("Zone object %s", tostring(name))) --self:E(ZoneObject) - + if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + local Include = false if not UnitCategories then -- Anythink found is included. @@ -809,29 +844,29 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) else -- Check if found object is in specified categories. local CategoryDCSUnit = ZoneObject:getDesc().category - + for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do if UnitCategory == CategoryDCSUnit then Include = true break end end - + end - + if Include then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + -- This coalition is inside the zone. self.ScanData.Coalitions[CoalitionDCSUnit] = true - + self.ScanData.Units[ZoneObject] = ZoneObject - + self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end - + if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() @@ -839,15 +874,15 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end - + end - + return true end -- Search objects. world.searchObjects( ObjectCategories, SphereSearch, EvaluateZone ) - + end --- Count the number of different coalitions inside the zone. @@ -886,6 +921,32 @@ function ZONE_RADIUS:GetScannedSetUnit() return SetUnit end +--- Get a set of scanned units. +-- @param #ZONE_RADIUS self +-- @return Core.Set#SET_GROUP Set of groups. +function ZONE_RADIUS:GetScannedSetGroup() + + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + + self.ScanSetGroup.Set={} + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + + local FoundUnit=UNIT:FindByName(UnitObject:getName()) + if FoundUnit then + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) + end + end + end + end + + return self.ScanSetGroup +end + --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self @@ -893,11 +954,11 @@ end function ZONE_RADIUS:CountScannedCoalitions() local Count = 0 - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 end - + return Count end @@ -925,16 +986,16 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition ) else local Count = 0 local ReturnCoalition = nil - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 ReturnCoalition = CoalitionID end - + if Count ~= 1 then ReturnCoalition = nil end - + return ReturnCoalition end end @@ -1045,7 +1106,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -1057,8 +1118,8 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) } local function EvaluateZone( ZoneDCSUnit ) - - + + local ZoneUnit = UNIT:Find( ZoneDCSUnit ) return EvaluateFunction( ZoneUnit ) @@ -1074,15 +1135,15 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - + local ZoneVec2 = self:GetVec2() - + if ZoneVec2 then if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then return true end end - + return false end @@ -1114,9 +1175,9 @@ function ZONE_RADIUS:GetRandomVec2( inner, outer ) local angle = math.random() * math.pi * 2; Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - + self:T( { Point } ) - + return Point end @@ -1131,7 +1192,7 @@ function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1146,7 +1207,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) local Vec2 = self:GetRandomVec2( inner, outer ) self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) - + return { x = Vec2.x, y = self.y, z = Vec2.y } end @@ -1162,7 +1223,7 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec3 } ) - + return PointVec3 end @@ -1178,7 +1239,7 @@ function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) self:T3( { Coordinate = Coordinate } ) - + return Coordinate end @@ -1190,32 +1251,32 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## ZONE constructor --- +-- -- * @{#ZONE.New}(): Constructor. This will search for a trigger zone with the name given, and will return for you a ZONE object. --- +-- -- ## Declare a ZONE directly in the DCS mission editor! --- +-- -- You can declare a ZONE using the DCS mission editor by adding a trigger zone in the mission editor. --- +-- -- Then during mission startup, when loading Moose.lua, this trigger zone will be detected as a ZONE declaration. -- Within the background, a ZONE object will be created within the @{Core.Database}. -- The ZONE name will be the trigger zone name. --- +-- -- So, you can search yourself for the ZONE object by using the @{#ZONE.FindByName}() method. -- In this example, `local TriggerZone = ZONE:FindByName( "DefenseZone" )` would return the ZONE object --- that was created at mission startup, and reference it into the `TriggerZone` local object. --- +-- that was created at mission startup, and reference it into the `TriggerZone` local object. +-- -- Refer to mission `ZON-110` for a demonstration. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE object `DefenseZone` as part of the zone collection, --- without much scripting overhead!!! --- --- --- @field #ZONE +-- without much scripting overhead!!! +-- +-- +-- @field #ZONE ZONE = { ClassName="ZONE", } @@ -1229,7 +1290,7 @@ function ZONE:New( ZoneName ) -- First try to find the zone in the DB. local zone=_DATABASE:FindZone(ZoneName) - + if zone then --env.info("FF found zone in DB") return zone @@ -1237,7 +1298,7 @@ function ZONE:New( ZoneName ) -- Get zone from DCS trigger function. local Zone = trigger.misc.getZone( ZoneName ) - + -- Error! if not Zone then error( "Zone " .. ZoneName .. " does not exist." ) @@ -1247,13 +1308,13 @@ function ZONE:New( ZoneName ) -- Create a new ZONE_RADIUS. local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) self:F(ZoneName) - + -- Color of zone. self.Color={1, 0, 0, 0.15} -- DCS zone. self.Zone = Zone - + return self end @@ -1262,7 +1323,7 @@ end -- @param #string ZoneName The name of the zone. -- @return #ZONE_BASE self function ZONE:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -1275,15 +1336,15 @@ end --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} --- +-- -- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_UNIT ZONE_UNIT = { ClassName="ZONE_UNIT", } - + --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius and optional offsets in X and Y directions. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. @@ -1298,30 +1359,30 @@ ZONE_UNIT = { -- dx, dy OR rho, theta may be used, not both. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) - + if Offset then - -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. + -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then - error("Cannot use (dx, dy) with (rho, theta)") + error("Cannot use (dx, dy) with (rho, theta)") end - + self.dy = Offset.dy or 0.0 self.dx = Offset.dx or 0.0 self.rho = Offset.rho or 0.0 self.theta = (Offset.theta or 0.0) * math.pi / 180.0 self.relative_to_unit = Offset.relative_to_unit or false end - + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1331,32 +1392,32 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. function ZONE_UNIT:GetVec2() self:F2( self.ZoneName ) - + 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 heading = 0.0 end - + -- update the zone position with the offsets. if (self.dx or self.dy) then - + -- use heading to rotate offset relative to unit using rotation matrix in 2D. -- see: https://en.wikipedia.org/wiki/Rotation_matrix - ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) - ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) + ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) + ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) end - + -- if using the polar coordinates - if (self.rho or self.theta) then + if (self.rho or self.theta) then ZoneVec2.x = ZoneVec2.x + self.rho * math.cos( self.theta + heading ) ZoneVec2.y = ZoneVec2.y + self.rho * math.sin( self.theta + heading ) end - + self.LastVec2 = ZoneVec2 return ZoneVec2 else @@ -1365,7 +1426,7 @@ function ZONE_UNIT:GetVec2() self:T2( { ZoneVec2 } ) - return nil + return nil end --- Returns a random location within the zone. @@ -1377,7 +1438,7 @@ function ZONE_UNIT:GetRandomVec2() local RandomVec2 = {} --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 end @@ -1385,9 +1446,9 @@ function ZONE_UNIT:GetRandomVec2() local angle = math.random() * math.pi*2; RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { RandomVec2 } ) - + return RandomVec2 end @@ -1397,16 +1458,16 @@ end -- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- @type ZONE_GROUP @@ -1415,12 +1476,12 @@ end --- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_GROUP ZONE_GROUP = { ClassName="ZONE_GROUP", } - + --- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. @@ -1436,7 +1497,7 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1446,9 +1507,9 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneGROUP:IsAlive() then ZoneVec2 = self._.ZoneGROUP:GetVec2() self._.ZoneVec2Cache = ZoneVec2 @@ -1457,7 +1518,7 @@ function ZONE_GROUP:GetVec2() end self:T( { ZoneVec2 } ) - + return ZoneVec2 end @@ -1473,9 +1534,9 @@ function ZONE_GROUP:GetRandomVec2() local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end @@ -1490,7 +1551,7 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1503,47 +1564,90 @@ end --- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone --- --- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. -- --- +-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. +-- +-- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } ---- A points array. +--- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list Table of 2D vectors. + +--- A 3D points array. +-- @type ZONE_POLYGON_BASE.ListVec3 +-- @list Table of 3D vectors. --- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) self:F( { ZoneName, PointsArray } ) - local i = 0 - + if PointsArray then + + self._.Polygon = {} + + for i = 1, #PointsArray do + self._.Polygon[i] = {} + self._.Polygon[i].x = PointsArray[i].x + self._.Polygon[i].y = PointsArray[i].y + end + + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec2}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) + self._.Polygon = {} - - for i = 1, #PointsArray do + + for i=1,#Vec2Array do self._.Polygon[i] = {} - self._.Polygon[i].x = PointsArray[i].x - self._.Polygon[i].y = PointsArray[i].y + self._.Polygon[i].x=Vec2Array[i].x + self._.Polygon[i].y=Vec2Array[i].y + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec3}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) + + self._.Polygon = {} + + for i=1,#Vec3Array do + self._.Polygon[i] = {} + self._.Polygon[i].x=Vec3Array[i].x + self._.Polygon[i].y=Vec3Array[i].z end return self @@ -1556,13 +1660,13 @@ function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) local Bounds = self:GetBoundingSquare() - - return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } + + return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec2 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec2(Index) return self._.Polygon[Index or 1] @@ -1570,7 +1674,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec3 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec3(Index) local vec2=self:GetVertexVec2(Index) @@ -1583,7 +1687,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return Core.Point#COORDINATE Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) local vec2=self:GetVertexVec2(Index) @@ -1613,7 +1717,7 @@ function ZONE_POLYGON_BASE:GetVerticiesVec3() local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} table.insert(coords, vec3) end - + return coords end @@ -1628,7 +1732,7 @@ function ZONE_POLYGON_BASE:GetVerticiesCoordinates() local coord=COORDINATE:NewFromVec2(vec2) table.insert(coords, coord) end - + return coords end @@ -1649,24 +1753,24 @@ end -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:BoundZone( UnBound ) - local i - local j + local i + local j local Segments = 10 - + i = 1 j = #self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) local Tire = { - ["country"] = "USA", + ["country"] = "USA", ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -1676,12 +1780,12 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), ["heading"] = 0, } -- end of ["group"] - + local Group = coalition.addStaticObject( country.id.USA, Tire ) if UnBound and UnBound == true then Group:destroy() end - + end j = i i = i + 1 @@ -1712,23 +1816,23 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph if #self._.Polygon==4 then - + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + else - + local Coordinates=self:GetVerticiesCoordinates() table.remove(Coordinates, 1) self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + end - - + + return self end @@ -1741,16 +1845,16 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) self:F2( SmokeColor ) Segments=Segments or 10 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1775,18 +1879,18 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) self:F2(FlareColor) Segments=Segments or 10 - + AddHeight = AddHeight or 0 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1810,17 +1914,17 @@ end function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - local Next - local Prev + local Next + local Prev local InPolygon = false - + Next = 1 Prev = #self._.Polygon - + while Next <= #self._.Polygon do self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) + ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) ) then InPolygon = not InPolygon end @@ -1843,9 +1947,9 @@ function ZONE_POLYGON_BASE:GetRandomVec2() local Vec2Found = false local Vec2 local BS = self:GetBoundingSquare() - + self:T2( BS ) - + while Vec2Found == false do Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } self:T2( Vec2 ) @@ -1853,7 +1957,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2() Vec2Found = true end end - + self:T2( Vec2 ) return Vec2 @@ -1866,7 +1970,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2() self:F2() local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec2 ) return PointVec2 @@ -1879,7 +1983,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3() self:F2() local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec3 ) return PointVec3 @@ -1893,7 +1997,7 @@ function ZONE_POLYGON_BASE:GetRandomCoordinate() self:F2() local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - + self:T2( Coordinate ) return Coordinate @@ -1909,19 +2013,58 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() local y1 = self._.Polygon[1].y local x2 = self._.Polygon[1].x local y2 = self._.Polygon[1].y - + for i = 2, #self._.Polygon do self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 - + end return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Draw a frontier on the F10 map with small filled circles. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) + local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE @@ -1929,27 +2072,27 @@ end --- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! --- +-- -- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name. --- +-- -- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`. -- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration. -- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group. -- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag. --- +-- -- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method. -- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object -- that was created at mission startup, and reference it into the `PolygonZone` local object. --- +-- -- Mission `ZON-510` shows a demonstration of this feature or method. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- without much scripting overhead! --- +-- -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", @@ -2001,7 +2144,7 @@ end -- @param #string ZoneName The name of the polygon zone. -- @return #ZONE_POLYGON self function ZONE_POLYGON:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -2010,85 +2153,85 @@ do -- ZONE_AIRBASE --- @type ZONE_AIRBASE -- @extends #ZONE_RADIUS - - + + --- The ZONE_AIRBASE class defines by a zone around a @{Wrapper.Airbase#AIRBASE} with a radius. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. - -- + -- -- @field #ZONE_AIRBASE ZONE_AIRBASE = { ClassName="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 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, Radius ) - + Radius=Radius or 4000 - + local Airbase = AIRBASE:FindByName( AirbaseName ) - + local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) ) - + self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end - + --- Get the airbase as part of the ZONE_AIRBASE object. -- @param #ZONE_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The airbase. function ZONE_AIRBASE:GetAirbase() return self._.ZoneAirbase - end - + end + --- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneAirbase:IsAlive() then ZoneVec2 = self._.ZoneAirbase:GetVec2() self._.ZoneVec2Cache = ZoneVec2 else ZoneVec2 = self._.ZoneVec2Cache end - + self:T( { ZoneVec2 } ) - + return ZoneVec2 end - + --- Returns a random location within the zone of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetRandomVec2() self:F( self.ZoneName ) - + local Point = {} local Vec2 = self._.ZoneAirbase:GetVec2() - + local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end - + --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -2096,11 +2239,11 @@ do -- ZONE_AIRBASE -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) - + local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T3( { PointVec2 } ) - + return PointVec2 end From 75849fbfb55a21cce177dea0d87d22951e00b010 Mon Sep 17 00:00:00 2001 From: Shadowze <30597073+shadowze@users.noreply.github.com> Date: Tue, 25 May 2021 22:30:16 +0100 Subject: [PATCH 262/382] Update Positionable.lua function added to work out if a unit is a submarine --- .../Moose/Wrapper/Positionable.lua | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 1a12d7206..ac4e49ad9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -684,6 +684,27 @@ function POSITIONABLE:IsShip() end +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end + + --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -817,8 +838,7 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - local velmps=self:GetVelocityMPS() - return UTILS.MpsToKnots(velmps) + return UTILS.MpsToKnots(self:GetVelocityMPS()) end --- Returns the Angle of Attack of a positionable. From 6b747e924b8df7d9812decb2d008d9e05053fa98 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 May 2021 23:32:54 +0200 Subject: [PATCH 263/382] Sound update 3 --- Moose Development/Moose/Sound/SRS.lua | 41 ++++++++++++++++----- Moose Development/Moose/Sound/SoundFile.lua | 2 + 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 1cad9d407..f5e2e5470 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -9,17 +9,15 @@ -- -- === -- --- ## Youtube Videos: --- --- * None +-- ## Youtube Videos: None yet -- -- === -- --- ## Missions: Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20ATIS) +-- ## Missions: None yet -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- ## Sound files: None yet. -- -- === -- @@ -110,11 +108,18 @@ function MSRS:SetPath(Path) self.path=Path - -- Remove (back)slashes. - while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do - self.path=self.path:sub(1,-1) + -- Remove (back)slashes. + local nmax=1000 + local n=1 + while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do + env.info(string.format("FF SRS path=%s (before)", self.path)) + self.path=self.path:sub(1,#self.path-1) + env.info(string.format("FF SRS path=%s (after)", self.path)) + n=n+1 end + env.info(string.format("FF SRS path=%s (final)", self.path)) + return self end @@ -127,11 +132,27 @@ end --- Set path, where the sound file is located. -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. +-- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. +-- @param #number Delay Delay in seconds, before the sound file is played. -- @return #MSRS self -function MSRS:PlaySoundfile(Soundfile) +function MSRS:PlaySoundfile(Soundfile, Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlaySoundfile, Soundfile, 0) + else + + local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" + local soundfile=Soundfile:GetName() + + env.info(string.format("FF PlaySoundfile soundfile=%s", soundfile)) + + local command=string.format("%s --file %s --freqs %d --modulations %d --coalition %d", exe, soundfile, self.frequency, 0) + + env.info(string.format("FF PlaySoundfile command=%s", command)) + + end + -- TODO: execute! end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 564fe1306..12e63e665 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -60,6 +60,8 @@ do -- Sound File -- Set file name. self.filename=filename or "Hallo World.ogg" + --TODO: check that sound file is .ogg or .mp3 + -- Set path self.path=Path or "l10n/DEFAULT/" From 6e37300d9b6cb7039cd307e65f4f2fa409de98c3 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 08:44:30 +0200 Subject: [PATCH 264/382] Update Controllable.lua Added function `IsSubmarine()`removed type from `OptionDisperseOnAttack()` --- .../Moose/Wrapper/Controllable.lua | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..7f2f1f795 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3817,10 +3817,30 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self end return nil end + +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end From dab486ec99391b6364fda3f0bcd3ceb118b3cc48 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 09:16:26 +0200 Subject: [PATCH 265/382] Update Controllable.lua Typo in `OptionDisperseOnAttack()` --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..5100f51a2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1832,7 +1832,7 @@ end do -- Patrol methods - --- (GROUND) Patrol iteratively using the waypoints the for the (parent) group. + --- (GROUND) Patrol iteratively using the waypoints of the (parent) group. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRoute() @@ -3817,7 +3817,7 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self From cb14961dcd88726816ee4b826a1b5e6db692e418 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 15:54:20 +0200 Subject: [PATCH 266/382] Update Zone.lua (#1541) added function `ZONE_POLYGON_BASE:Boundary()` --- Moose Development/Moose/Core/Zone.lua | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 70d087b4b..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1576,6 +1576,7 @@ end -- ## Draw zone -- -- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. -- -- -- @field #ZONE_POLYGON_BASE @@ -2025,6 +2026,45 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Draw a frontier on the F10 map with small filled circles. +-- @param #ZONE_POLYGON_BASE self +-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) + local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE From 3129c8c8ea0ad537bf7045d486c6c19f5a439855 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 27 May 2021 23:19:40 +0200 Subject: [PATCH 267/382] Sound update 4 --- Moose Development/Moose/Sound/SRS.lua | 137 +++++++++++++++----- Moose Development/Moose/Sound/SoundFile.lua | 42 ++---- 2 files changed, 120 insertions(+), 59 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index f5e2e5470..35ad41c96 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -34,6 +34,14 @@ -- @type MSRS -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. +-- @field #table frequencies Frequencies used in the transmissions. +-- @field #table modulations Modulations used in the transmissions. +-- @field #number coalition Coalition of the transmission. +-- @field #number port Port. Default 5002. +-- @field #string name Name. Default "DCS-STTS". +-- @field #number volume Volume between 0 (min) and 1 (max). Default 1. +-- @field #string culture Culture. Default "en-GB". +-- @field #string path Path to the SRS exe. -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -52,11 +60,24 @@ -- -- @field #MSRS MSRS = { - ClassName = "MSRS", - lid = nil, + ClassName = "MSRS", + lid = nil, + frequencies = {}, + modulations = {}, + coalition = 0, + speed = 1, + port = 5002, + name = "DCS-STTS", + volume = 1, + culture = "en-GB", + gender = "female", + voice = nil, + latitude = nil, + longitude = nil, + altitude = nil, } ---- ATIS class version. +--- MSRS class version. -- @field #string version MSRS.version="0.0.1" @@ -78,15 +99,16 @@ MSRS.version="0.0.1" -- @return #MSRS self function MSRS:New(PathToSRS, Frequency, Modulation) - -- Inherit everything from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #MSRS - - self.path=self:SetPath(PathToSRS) - - self.frequency=Frequency or 143 - - self.modulation=Modulation or radio.modulation.AM + -- Defaults. + Frequency =Frequency or 143 + Modulation= Modulation or radio.modulation.AM + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, BASE:New()) -- #MSRS + + self:SetPath(PathToSRS) + self:SetFrequencies(Frequency) + self:SetModulations(Modulation) return self end @@ -96,7 +118,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set path, where the sound file is located. +--- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self -- @param #string Path Path to the directory, where the sound file is located. -- @return #MSRS self @@ -106,31 +128,62 @@ function MSRS:SetPath(Path) return nil end + -- Set path. self.path=Path -- Remove (back)slashes. - local nmax=1000 - local n=1 + local n=1 ; local nmax=1000 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do - env.info(string.format("FF SRS path=%s (before)", self.path)) self.path=self.path:sub(1,#self.path-1) - env.info(string.format("FF SRS path=%s (after)", self.path)) n=n+1 end - env.info(string.format("FF SRS path=%s (final)", self.path)) + self:I(string.format("SRS path=%s", self:GetPath())) return self end --- Get path to SRS directory. -- @param #MSRS self --- @return #string +-- @return #string Path to the directory. function MSRS:GetPath() return self.path end ---- Set path, where the sound file is located. +--- Set frequencies. +-- @param #MSRS self +-- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. +-- @return #MSRS self +function MSRS:SetFrequencies(Frequencies) + + -- Ensure table. + if type(Frequencies)~="table" then + Frequencies={Frequencies} + end + + self.frequencies=Frequencies + + return self +end + + +--- Set modulations. +-- @param #MSRS self +-- @param #table Modulations Modulations. Can also be given as a #number if only one modulation should be used. +-- @return #MSRS self +function MSRS:SetModulations(Modulations) + + -- Ensure table. + if type(Modulations)~="table" then + Modulations={Modulations} + end + + self.modulations=Modulations + + return self +end + +--- Play sound file (ogg or mp3) via SRS. -- @param #MSRS self -- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. -- @param #number Delay Delay in seconds, before the sound file is played. @@ -138,31 +191,57 @@ end function MSRS:PlaySoundfile(Soundfile, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlaySoundfile, Soundfile, 0) + self:ScheduleOnce(Delay, MSRS.PlaySoundfile, self, Soundfile, 0) else local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" local soundfile=Soundfile:GetName() + local freq=table.concat(self.frequencies, " ") + local modu=table.concat(self.modulations, " ") + local coal=self.coalition + local port=self.port - env.info(string.format("FF PlaySoundfile soundfile=%s", soundfile)) - - local command=string.format("%s --file %s --freqs %d --modulations %d --coalition %d", exe, soundfile, self.frequency, 0) + local command=string.format("%s --file %s --freqs %s --modulations %s --coalition %d --port %d -h", exe, soundfile, freq, modu, coal, port) env.info(string.format("FF PlaySoundfile command=%s", command)) - - end - - -- TODO: execute! + -- Execute SRS command. + os.execute(command) + + end + + return self end ---- Set path, where the sound file is located. +--- Play text message via STTS. -- @param #MSRS self -- @param #string Message Text message. +-- @param #number Delay Delay in seconds, before the message is played. -- @return #MSRS self -function MSRS:PlayText(Message) +function MSRS:PlayText(Message, Delay) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlayText, self, Message, 0) + else + local text=string.format("\"%s\"", Message) + local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" + local freq=table.concat(self.frequencies, " ") + local modu=table.concat(self.modulations, " ") + local coal=self.coalition + local port=self.port + local gender="male" + + local command=string.format("%s -h --text=%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s", exe, text, freq, modu, coal, port, gender) + + env.info(string.format("FF Text command=%s", command)) + + -- Execute SRS command. + os.execute(command) + + end + + return self end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 12e63e665..4b65bffc1 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -48,17 +48,17 @@ do -- Sound File --- Constructor to create a new SOUNDFILE object. -- @param #SOUNDFILE self - -- @param #string filename The name of the sound file, e.g. "Hello World.ogg". + -- @param #string FileName The name of the sound file, e.g. "Hello World.ogg". -- @param #string Path The path of the directory, where the sound file is located. Default is "l10n/DEFAULT/" within the miz file. -- @param #number Duration Duration in seconds, how long it takes to play the sound file. Default is 3 seconds. -- @return #SOUNDFILE self - function SOUNDFILE:New(filename, Path, Duration) + function SOUNDFILE:New(FileName, Path, Duration) -- Inherit BASE. local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE -- Set file name. - self.filename=filename or "Hallo World.ogg" + self.filename=FileName or "Hallo World.ogg" --TODO: check that sound file is .ogg or .mp3 @@ -76,15 +76,19 @@ do -- Sound File --- Set path, where the sound file is located. -- @param #SOUNDFILE self -- @param #string Path Path to the directory, where the sound file is located. - -- @return self + -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) self.path=Path or "l10n/DEFAULT/" - - while self.path:sub(-1)=="/" or self.path:sub(-1)=="\\" do - self.path=self.path:sub(1,-1) + + -- Remove (back)slashes. + local nmax=1000 + local n=1 + while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do + self.path=self.path:sub(1,#self.path-1) + n=n+1 end - + return self end @@ -115,26 +119,4 @@ do -- Sound File return name end - - --- Set the userflag to a given Number. - -- @param #SOUNDFILE self - -- @param #number Number The number value to be checked if it is the same as the userflag. - -- @param #number Delay Delay in seconds, before the flag is set. - -- @return #SOUNDFILE self - -- @usage - -- local BlueVictory = USERFLAG:New( "VictoryBlue" ) - -- BlueVictory:Set( 100 ) -- Set the UserFlag VictoryBlue to 100. - -- - function SOUNDFILE:Set( Number, Delay ) --R2.3 - - if Delay and Delay>0 then - self:ScheduleOnce(Delay, USERFLAG.Set, self, Number) - else - --env.info(string.format("Setting flag \"%s\" to %d at T=%.1f", self.UserFlagName, Number, timer.getTime())) - trigger.action.setUserFlag( self.UserFlagName, Number ) - end - - return self - end - end \ No newline at end of file From 4827b73bb14c256dd29323c89d7e9acb71b6b75d Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 28 May 2021 23:00:43 +0200 Subject: [PATCH 268/382] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 10 +- Moose Development/Moose/Sound/RadioQueue.lua | 52 +++-- Moose Development/Moose/Sound/SRS.lua | 181 ++++++++++++++--- Moose Development/Moose/Sound/SoundFile.lua | 199 +++++++++++++++++-- 4 files changed, 373 insertions(+), 69 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 91333138d..96353d11d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -59,7 +59,7 @@ -- @field #number frequency Radio frequency in MHz. -- @field #number modulation Radio modulation 0=AM or 1=FM. -- @field #number power Radio power in Watts. Default 100 W. --- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. +-- @field Sound.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. -- @field #string soundpath Path to sound files. -- @field #string relayunitname Name of the radio relay unit. -- @field #table towerfrequency Table with tower frequencies. @@ -2102,6 +2102,14 @@ end -- @param #string Text Report text. function ATIS:onafterReport(From, Event, To, Text) self:T(self.lid..string.format("Report:\n%s", Text)) + + -- Remove line breaks + local text=string.gsub(Text, "[\r\n]", "") + env.info("FF: "..text) + + local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) + msrs:PlayText(text) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 3e6afcd51..1f5e9cd07 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -35,6 +35,7 @@ -- @field #table numbers Table of number transmission parameters. -- @field #boolean checking Scheduler is checking the radio queue. -- @field #boolean schedonce Call ScheduleOnce instead of normal scheduler. +-- @field Sound.SRS#MSRS msrs Moose SRS class. -- @extends Core.Base#BASE RADIOQUEUE = { ClassName = "RADIOQUEUE", @@ -69,6 +70,8 @@ RADIOQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. +-- @field Sound.SoundFile#SOUNDFILE soundfile Sound file object to play via SRS. +-- @field Sound.SoundFile#SOUNDTEXT soundtext Sound TTS object to play via SRS. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -170,6 +173,15 @@ function RADIOQUEUE:SetRadioPower(power) return self end +--- Set SRS. +-- @param #RADIOQUEUE self +-- @param #string PathToSRS Path to SRS. +-- @return #RADIOQUEUE self The RADIOQUEUE object. +function RADIOQUEUE:SetSRS(PathToSRS) + self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) + return self +end + --- Set parameters of a digit. -- @param #RADIOQUEUE self -- @param #number digit The digit 0-9. @@ -230,7 +242,7 @@ end -- @param #number interval Interval in seconds after the last transmission finished. -- @param #string subtitle Subtitle of the transmission. -- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec. --- @return #RADIOQUEUE self The RADIOQUEUE object. +-- @return #RADIOQUEUE.Transmission Radio transmission table. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) env.info("FF new transmission.") @@ -271,7 +283,7 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, -- Add transmission to queue. self:AddTransmission(transmission) - return self + return transmission end --- Create a new transmission and add it to the radio queue. @@ -280,10 +292,10 @@ end -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self -function RADIOQUEUE:AddSoundfile(soundfile, tstart, interval) +function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval) env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) - self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) - + local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) + transmission.soundfile=soundfile return self end @@ -295,19 +307,9 @@ end -- @param #number interval Interval between the next call. -- @return #number Duration of the call in seconds. function RADIOQUEUE:Number2Transmission(number, delay, interval) - - --- Split string into characters. - local function _split(str) - local chars={} - for i=1,#str do - local c=str:sub(i,i) - table.insert(chars, c) - end - return chars - end -- Split string into characters. - local numbers=UTILS.GetCharacters(number) --l_split(number) + local numbers=UTILS.GetCharacters(number) local wait=0 for i=1,#numbers do @@ -340,6 +342,11 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) + if (transmission.soundfile or transmission.soundtext) and self.msrs then + self:_BroadcastSRS(transmission) + return + end + -- Get unit sending the transmission. local sender=self:_GetRadioSender() @@ -431,6 +438,19 @@ function RADIOQUEUE:Broadcast(transmission) end end +--- Broadcast radio message. +-- @param #RADIOQUEUE self +-- @param #RADIOQUEUE.Transmission transmission The transmission. +function RADIOQUEUE:_BroadcastSRS(transmission) + + if transmission.soundfile then + self.msrs:PlaySoundFile(transmission.soundfile) + elseif transmission.soundtext then + self.msrs:PlaySoundText(transmission.soundtext) + end + +end + --- Start checking the radio queue. -- @param #RADIOQUEUE self -- @param #number delay Delay in seconds before checking. diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 35ad41c96..c4fc31c15 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -1,4 +1,4 @@ ---- **Sound** - Simple Radio Standalone Integration +--- **Sound** - Simple Radio Standalone (SRS) Integration. -- -- === -- @@ -41,7 +41,10 @@ -- @field #string name Name. Default "DCS-STTS". -- @field #number volume Volume between 0 (min) and 1 (max). Default 1. -- @field #string culture Culture. Default "en-GB". --- @field #string path Path to the SRS exe. +-- @field #string gender Gender. Default "female". +-- @field #string voice Specifc voce. +-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. +-- @field #string path Path to the SRS exe. This includes the final slash "/". -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -62,16 +65,17 @@ MSRS = { ClassName = "MSRS", lid = nil, + port = 5002, + name = "MSRS", frequencies = {}, modulations = {}, coalition = 0, - speed = 1, - port = 5002, - name = "DCS-STTS", - volume = 1, - culture = "en-GB", gender = "female", + culture = "en-GB", voice = nil, + volume = 1, + speed = 1, + coordinate = nil, latitude = nil, longitude = nil, altitude = nil, @@ -79,7 +83,7 @@ MSRS = { --- MSRS class version. -- @field #string version -MSRS.version="0.0.1" +MSRS.version="0.0.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -94,8 +98,8 @@ MSRS.version="0.0.1" --- Create a new MSRS object. -- @param #MSRS self -- @param #string PathToSRS Path to the directory, where SRS is located. --- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies. +-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations. -- @return #MSRS self function MSRS:New(PathToSRS, Frequency, Modulation) @@ -107,9 +111,11 @@ function MSRS:New(PathToSRS, Frequency, Modulation) local self=BASE:Inherit(self, BASE:New()) -- #MSRS self:SetPath(PathToSRS) + self:SetPort() self:SetFrequencies(Frequency) self:SetModulations(Modulation) - + self:SetGender() + return self end @@ -117,7 +123,6 @@ end -- User Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self -- @param #string Path Path to the directory, where the sound file is located. @@ -125,6 +130,7 @@ end function MSRS:SetPath(Path) if Path==nil then + self:E("ERROR: No path to SRS directory specified!") return nil end @@ -138,6 +144,8 @@ function MSRS:SetPath(Path) n=n+1 end + self.path=self.path.."/" + self:I(string.format("SRS path=%s", self:GetPath())) return self @@ -145,11 +153,26 @@ end --- Get path to SRS directory. -- @param #MSRS self --- @return #string Path to the directory. +-- @return #string Path to the directory. This includes the final slash "/". function MSRS:GetPath() return self.path end +--- Set port. +-- @param #MSRS self +-- @param #number Port Port. Default 5002. +-- @return #MSRS self +function MSRS:SetPort(Port) + self.port=Port or 5002 +end + +--- Get port. +-- @param #MSRS self +-- @return #number Port. +function MSRS:GetPort() + return self.port +end + --- Set frequencies. -- @param #MSRS self -- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. @@ -166,6 +189,13 @@ function MSRS:SetFrequencies(Frequencies) return self end +--- Get frequencies. +-- @param #MSRS self +-- @param #table Frequencies in MHz. +function MSRS:GetFrequencies() + return self.frequencies +end + --- Set modulations. -- @param #MSRS self @@ -183,25 +213,73 @@ function MSRS:SetModulations(Modulations) return self end +--- Get modulations. +-- @param #MSRS self +-- @param #table Modulations. +function MSRS:GetModulations() + return self.modulations +end + +--- Set gender. +-- @param #MSRS self +-- @param #string Gender Gender: "male" or "female" (default). +-- @return #MSRS self +function MSRS:SetGender(Gender) + + Gender=Gender or "female" + + Gender=Gender:lower() + + self.gender=Gender + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Transmission Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Play sound file (ogg or mp3) via SRS. -- @param #MSRS self -- @param Sound.SoundFile#SOUNDFILE Soundfile Sound file to play. -- @param #number Delay Delay in seconds, before the sound file is played. -- @return #MSRS self -function MSRS:PlaySoundfile(Soundfile, Delay) +function MSRS:PlaySoundFile(Soundfile, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlaySoundfile, self, Soundfile, 0) + self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0) else - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" local soundfile=Soundfile:GetName() - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port + + local command=self:_GetCommand() - local command=string.format("%s --file %s --freqs %s --modulations %s --coalition %d --port %d -h", exe, soundfile, freq, modu, coal, port) + command=command.." --file="..tostring(soundfile) + + env.info(string.format("FF PlaySoundfile command=%s", command)) + + -- Execute SRS command. + os.execute(command) + + end + + return self +end + +--- Play a SOUNDTEXT text-to-speech object. +-- @param #MSRS self +-- @param Sound.SoundFile#SOUNDTEXT SoundText Sound text. +-- @param #number Delay Delay in seconds, before the sound file is played. +-- @return #MSRS self +function MSRS:PlaySoundText(SoundText, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) + else + + local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed) + + command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) env.info(string.format("FF PlaySoundfile command=%s", command)) @@ -215,29 +293,26 @@ end --- Play text message via STTS. -- @param #MSRS self --- @param #string Message Text message. +-- @param #string Text Text message. -- @param #number Delay Delay in seconds, before the message is played. -- @return #MSRS self -function MSRS:PlayText(Message, Delay) +function MSRS:PlayText(Text, Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlayText, self, Message, 0) + self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0) else - local text=string.format("\"%s\"", Message) - local exe=self:GetPath().."/".."DCS-SR-ExternalAudio.exe" - local freq=table.concat(self.frequencies, " ") - local modu=table.concat(self.modulations, " ") - local coal=self.coalition - local port=self.port - local gender="male" + local text=string.format("\"%s\"", Text) - local command=string.format("%s -h --text=%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s", exe, text, freq, modu, coal, port, gender) + local command=self:_GetCommand() + + command=command..string.format(" --text=\"%s\"", tostring(Text)) env.info(string.format("FF Text command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) + env.info(x) end @@ -249,6 +324,46 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. +-- @param #MSRS self +-- @param #table freqs Frequencies in MHz. +-- @param #table modus Modulations. +-- @param #number coal Coalition. +-- @param #string gender Gender. +-- @param #string voice Voice. +-- @param #string culture Culture. +-- @param #number volume Volume. +-- @param #number speed Speed. +-- @param #number port Port. +-- @return #string Command. +function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) + + + local exe=self:GetPath().."DCS-SR-ExternalAudio.exe" + freqs=table.concat(freqs or self.frequencies, ",") + modus=table.concat(modus or self.modulations, ",") + coal=coal or self.coalition + gender=gender or self.gender + voice=voice or self.voice + culture=culture or self.culture + volume=volume or self.volume + speed=speed or self.speed + port=port or self.port + + local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed) + + if voice then + command=command..string.format(" --voice=\"%s\"", tostring(voice)) + end + + if culture then + command=command.." --culture="..tostring(culture) + end + + env.info("FF command="..command) + + return command +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundFile.lua index 4b65bffc1..884eeab6a 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundFile.lua @@ -1,4 +1,4 @@ ---- **Sound** - Sound file management. +--- **Sound** - Sound output classes. -- -- === -- @@ -16,12 +16,46 @@ -- @image Sound_Soundfile.png -- +do -- Sound Base + + --- @type SOUNDBASE + -- @field #string ClassName Name of the class + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDBASE + SOUNDBASE={ + ClassName = "SOUNDBASE", + } + + --- Constructor to create a new SOUNDBASE object. + -- @param #SOUNDBASE self + -- @return #SOUNDBASE self + function SOUNDBASE:New() + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + + + + return self + end + +end + + do -- Sound File --- @type SOUNDFILE -- @field #string ClassName Name of the class -- @field #string filename Name of the flag. - -- @field #string path Directory path, where the sound file is located. + -- @field #string path Directory path, where the sound file is located. This includes the final slash "/". -- @field #string duration Duration of the sound file in seconds. -- @field #string subtitle Subtitle of the transmission. -- @field #number subduration Duration in seconds how long the subtitle is displayed. @@ -39,7 +73,7 @@ do -- Sound File SOUNDFILE={ ClassName = "SOUNDFILE", filename = nil, - path = "l10n/DEFAULT", + path = "l10n/DEFAULT/", duration = 3, subtitle = nil, subduration = 0, @@ -58,12 +92,10 @@ do -- Sound File local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE -- Set file name. - self.filename=FileName or "Hallo World.ogg" - - --TODO: check that sound file is .ogg or .mp3 + self:SetFileName(FileName) -- Set path - self.path=Path or "l10n/DEFAULT/" + self:SetPath(Path) self.duration=Duration or 3 @@ -79,19 +111,39 @@ do -- Sound File -- @return #SOUNDFILE self function SOUNDFILE:SetPath(Path) + -- Init path. self.path=Path or "l10n/DEFAULT/" -- Remove (back)slashes. - local nmax=1000 - local n=1 + local nmax=1000 ; local n=1 while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do self.path=self.path:sub(1,#self.path-1) n=n+1 end + + -- Append slash. + self.path=self.path.."/" return self end - + + --- Get path of the directory, where the sound file is located. + -- @param #SOUNDFILE self + -- @return #string Path. + function SOUNDFILE:GetPath() + local path=self.path or "l10n/DEFAULT/" + return path + end + + --- Set sound file name. This must be a .ogg or .mp3 file! + -- @param #SOUNDFILE self + -- @param #string FileName Name of the file. + -- @return #SOUNDFILE self + function SOUNDFILE:SetFileName(FileName) + --TODO: check that sound file is really .ogg or .mp3 + self.filename=FileName or "HelloWorld.mp3" + return self + end --- Get the sound file name. -- @param #SOUNDFILE self @@ -99,24 +151,133 @@ do -- Sound File function SOUNDFILE:GetFileName() return self.filename end - - --- Get path of the directory, where the sound file is located. + + + --- Set duration how long it takes to play the sound file. -- @param #SOUNDFILE self - -- @return #string Path. - function SOUNDFILE:GetPath() - local path=self.path or "l10n/DEFAULT" - path=path.."/" - return path + -- @param #string Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDFILE self + function SOUNDFILE:SetDuration(Duration) + self.duration=Duration or 3 + return self + end + + --- Get duration how long the sound file takes to play. + -- @param #SOUNDFILE self + -- @return #number Duration in seconds. + function SOUNDFILE:GetDuration() + return self.duration or 3 end --- Get the complete sound file name inlcuding its path. -- @param #SOUNDFILE self -- @return #string Name of the sound file. function SOUNDFILE:GetName() - local filename=self:GetFileName() local path=self:GetPath() - local name=string.format("%s/%s", path, filename) + local filename=self:GetFileName() + local name=string.format("%s%s", path, filename) return name end +end + +do -- Text-To-Speech + + --- @type SOUNDTEXT + -- @field #string ClassName Name of the class + -- @field #string text Text to speak. + -- @field #number duration Duration in seconds. + -- @field #string gender Gender. + -- @field #string voice Voice. + -- @field #string culture Culture. + -- @extends Core.Base#BASE + + + --- Sound files used by other classes. + -- + -- # 1. USERFLAG constructor + -- + -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- + -- @field #SOUNDTEXT + SOUNDTEXT={ + ClassName = "SOUNDTEXT", + } + + --- Constructor to create a new SOUNDTEXT object. + -- @param #SOUNDTEXT self + -- @param #string Text The text to speak. + -- @param #number Duration Duration in seconds, how long it takes to play the text. Default is 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:New(Text, Duration) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT + + self:SetText(Text) + self:SetDuration(Duration) + self:SetGender() + self:SetCulture() + + -- Debug info: + self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) + + return self + end + + --- Set text. + -- @param #SOUNDTEXT self + -- @param #string Text Text to speak. Default "Hello World!". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetText(Text) + + self.text=Text or "Hello World!" + + return self + end + + --- Set duration, how long it takes to speak the text. + -- @param #SOUNDTEXT self + -- @param #number Duration Duration in seconds. Default 3 seconds. + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetDuration(Duration) + + self.duration=Duration or 3 + + return self + end + + --- Set gender. + -- @param #SOUNDTEXT self + -- @param #string Gender Gender: "male" or "female" (default). + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetGender(Gender) + + self.gender=Gender or "female" + + return self + end + + --- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices + -- @param #SOUNDTEXT self + -- @param #string Voice + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetVoice(Voice) + + self.voice=Voice + + return self + end + + --- Set TTS culture - local for the voice. + -- @param #SOUNDTEXT self + -- @param #string Culture TTS culture. Default "en-GB". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetCulture(Culture) + + self.culture=Culture or "en-GB" + + return self + end + end \ No newline at end of file From 4c3d44a63bd44fd14efb4a70c554f836e00695fe Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 01:36:22 +0200 Subject: [PATCH 269/382] Sound update --- Moose Development/Moose/Core/Beacon.lua | 442 ++++++++++++++ Moose Development/Moose/Core/Settings.lua | 1 + Moose Development/Moose/Globals.lua | 32 +- Moose Development/Moose/Modules.lua | 4 +- Moose Development/Moose/Ops/ATIS.lua | 546 ++++++++++-------- Moose Development/Moose/Sound/Radio.lua | 462 +-------------- Moose Development/Moose/Sound/RadioQueue.lua | 6 +- Moose Development/Moose/Sound/SRS.lua | 160 ++++- .../Sound/{SoundFile.lua => SoundOutput.lua} | 58 +- Moose Development/Moose/Utilities/STTS.lua | 256 ++++++++ Moose Setup/Moose.files | 4 +- 11 files changed, 1235 insertions(+), 736 deletions(-) create mode 100644 Moose Development/Moose/Core/Beacon.lua rename Moose Development/Moose/Sound/{SoundFile.lua => SoundOutput.lua} (72%) create mode 100644 Moose Development/Moose/Utilities/STTS.lua diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua new file mode 100644 index 000000000..37dba8c76 --- /dev/null +++ b/Moose Development/Moose/Core/Beacon.lua @@ -0,0 +1,442 @@ +--- **Core** - TACAN and other beacons. +-- +-- === +-- +-- ## Features: +-- +-- * Provide beacon functionality to assist pilots. +-- +-- === +-- +-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky +-- +-- @module Core.Beacon +-- @image Core_Radio.JPG + +--- *In order for the light to shine so brightly, the darkness must be present.* -- Francis Bacon +-- +-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. +-- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. +-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is +-- attach to a cargo crate, for exemple. +-- +-- ## AA TACAN Beacon usage +-- +-- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. +-- Use @#BEACON:StopAATACAN}() to stop it. +-- +-- ## General Purpose Radio Beacon usage +-- +-- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with +-- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. +-- Use @{#BEACON:StopRadioBeacon}() to stop it. +-- +-- @type BEACON +-- @field #string ClassName Name of the class "BEACON". +-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. +-- @extends Core.Base#BASE +BEACON = { + ClassName = "BEACON", + Positionable = nil, + name = nil, +} + +--- Beacon types supported by DCS. +-- @type BEACON.Type +-- @field #number NULL +-- @field #number VOR +-- @field #number DME +-- @field #number VOR_DME +-- @field #number TACAN TACtical Air Navigation system. +-- @field #number VORTAC +-- @field #number RSBN +-- @field #number BROADCAST_STATION +-- @field #number HOMER +-- @field #number AIRPORT_HOMER +-- @field #number AIRPORT_HOMER_WITH_MARKER +-- @field #number ILS_FAR_HOMER +-- @field #number ILS_NEAR_HOMER +-- @field #number ILS_LOCALIZER +-- @field #number ILS_GLIDESLOPE +-- @field #number PRMG_LOCALIZER +-- @field #number PRMG_GLIDESLOPE +-- @field #number ICLS Same as ICLS glideslope. +-- @field #number ICLS_LOCALIZER +-- @field #number ICLS_GLIDESLOPE +-- @field #number NAUTICAL_HOMER +BEACON.Type={ + NULL = 0, + VOR = 1, + DME = 2, + VOR_DME = 3, + TACAN = 4, + VORTAC = 5, + RSBN = 128, + BROADCAST_STATION = 1024, + HOMER = 8, + AIRPORT_HOMER = 4104, + AIRPORT_HOMER_WITH_MARKER = 4136, + ILS_FAR_HOMER = 16408, + ILS_NEAR_HOMER = 16424, + ILS_LOCALIZER = 16640, + ILS_GLIDESLOPE = 16896, + PRMG_LOCALIZER = 33024, + PRMG_GLIDESLOPE = 33280, + ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE + ICLS_LOCALIZER = 131328, + ICLS_GLIDESLOPE = 131584, + NAUTICAL_HOMER = 65536, +} + +--- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon +-- @type BEACON.System +-- @field #number PAR_10 ? +-- @field #number RSBN_5 Russian VOR/DME system. +-- @field #number TACAN TACtical Air Navigation system on ground. +-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. +-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. +-- @field #number VOR Very High Frequency Omni-Directional Range +-- @field #number ILS_LOCALIZER ILS localizer +-- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number PRGM_LOCALIZER PRGM localizer. +-- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number BROADCAST_STATION Broadcast station. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. +-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. +-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). +-- @field #number ICLS_LOCALIZER Carrier landing system. +-- @field #number ICLS_GLIDESLOPE Carrier landing system. +BEACON.System={ + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, + TACAN_TANKER_X = 4, + TACAN_TANKER_Y = 5, + VOR = 6, + ILS_LOCALIZER = 7, + ILS_GLIDESLOPE = 8, + PRMG_LOCALIZER = 9, + PRMG_GLIDESLOPE = 10, + BROADCAST_STATION = 11, + VORTAC = 12, + TACAN_AA_MODE_X = 13, + TACAN_AA_MODE_Y = 14, + VORDME = 15, + ICLS_LOCALIZER = 16, + ICLS_GLIDESLOPE = 17, +} + +--- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. +-- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. +-- @param #BEACON self +-- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. +-- @return #BEACON Beacon object or #nil if the positionable is invalid. +function BEACON:New(Positionable) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) --#BEACON + + -- Debug. + self:F(Positionable) + + -- Set positionable. + if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid + self.Positionable = Positionable + self.name=Positionable:GetName() + self:I(string.format("New BEACON %s", tostring(self.name))) + return self + end + + self:E({"The passed positionable is invalid, no BEACON created", Positionable}) + return nil +end + + +--- Activates a TACAN BEACON. +-- @param #BEACON self +-- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". +-- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y". +-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon. +-- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available. +-- @param #number Duration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a TACAN Beacon for a tanker +-- local myUnit = UNIT:FindByName("MyUnit") +-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon +-- +-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon +function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) + self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) + + -- Get frequency. + local Frequency=UTILS.TACANToFrequency(Channel, Mode) + + -- Check. + if not Frequency then + self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) + return self + end + + -- Beacon type. + local Type=BEACON.Type.TACAN + + -- Beacon system. + local System=BEACON.System.TACAN + + -- Check if unit is an aircraft and set system accordingly. + local AA=self.Positionable:IsAir() + if AA then + System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER + -- Check if "Y" mode is selected for aircraft. + if Mode~="Y" then + self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable}) + end + end + + -- Attached unit. + local UnitID=self.Positionable:GetID() + + -- Debug. + self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))}) + + -- Start beacon. + self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) + + -- Stop sheduler. + if Duration then + self.Positionable:DeactivateBeacon(Duration) + end + + return self +end + +--- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system. +-- @param #BEACON self +-- @param #number Channel ICLS channel. +-- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. +-- @param #number Duration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +function BEACON:ActivateICLS(Channel, Callsign, Duration) + self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) + + -- Attached unit. + local UnitID=self.Positionable:GetID() + + -- Debug + self:T2({"ICLS BEACON started!"}) + + -- Start beacon. + self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) + + -- Stop sheduler + if Duration then -- Schedule the stop of the BEACON if asked by the MD + self.Positionable:DeactivateBeacon(Duration) + end + + return self +end + +--- Activates a TACAN BEACON on an Aircraft. +-- @param #BEACON self +-- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels +-- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon +-- @param #boolean Bearing Can the BEACON be homed on ? +-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a TACAN Beacon for a tanker +-- local myUnit = UNIT:FindByName("MyUnit") +-- local myBeacon = myUnit:GetBeacon() -- Creates the beacon +-- +-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon +function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) + self:F({TACANChannel, Message, Bearing, BeaconDuration}) + + local IsValid = true + + if not self.Positionable:IsAir() then + self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable}) + IsValid = false + end + + local Frequency = self:_TACANToFrequency(TACANChannel, "Y") + if not Frequency then + self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) + IsValid = false + end + + -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing + -- or 14 (TACAN_AA_MODE_Y) if it does not + local System + if Bearing then + System = 5 + else + System = 14 + end + + if IsValid then -- Starts the BEACON + self:T2({"AA TACAN BEACON started !"}) + self.Positionable:SetCommand({ + id = "ActivateBeacon", + params = { + type = 4, + system = System, + callsign = Message, + frequency = Frequency, + } + }) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New(nil, + function() + self:StopAATACAN() + end, {}, BeaconDuration) + end + end + + return self +end + +--- Stops the AA TACAN BEACON +-- @param #BEACON self +-- @return #BEACON self +function BEACON:StopAATACAN() + self:F() + if not self.Positionable then + self:E({"Start the beacon first before stoping it !"}) + else + self.Positionable:SetCommand({ + id = 'DeactivateBeacon', + params = { + } + }) + end +end + + +--- Activates a general pupose Radio Beacon +-- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. +-- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. +-- They can home in on these specific frequencies : +-- * **Mi8** +-- * R-828 -> 20-60MHz +-- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM +-- * ARK9 -> 150-1300KHz +-- * **Huey** +-- * AN/ARC-131 -> 30-76 Mhz FM +-- @param #BEACON self +-- @param #string FileName The name of the audio file +-- @param #number Frequency in MHz +-- @param #number Modulation either radio.modulation.AM or radio.modulation.FM +-- @param #number Power in W +-- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. +-- @return #BEACON self +-- @usage +-- -- Let's create a beacon for a unit in distress. +-- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131) +-- -- The beacon they use is battery-powered, and only lasts for 5 min +-- local UnitInDistress = UNIT:FindByName("Unit1") +-- local UnitBeacon = UnitInDistress:GetBeacon() +-- +-- -- Set the beacon and start it +-- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) +function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) + self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) + local IsValid = false + + -- Check the filename + if type(FileName) == "string" then + if FileName:find(".ogg") or FileName:find(".wav") then + if not FileName:find("l10n/DEFAULT/") then + FileName = "l10n/DEFAULT/" .. FileName + end + IsValid = true + end + end + if not IsValid then + self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName}) + end + + -- Check the Frequency + if type(Frequency) ~= "number" and IsValid then + self:E({"Frequency invalid. ", Frequency}) + IsValid = false + end + Frequency = Frequency * 1000000 -- Conversion to Hz + + -- Check the modulation + if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? + self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) + IsValid = false + end + + -- Check the Power + if type(Power) ~= "number" and IsValid then + self:E({"Power is invalid. ", Power}) + IsValid = false + end + Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that + + if IsValid then + self:T2({"Activating Beacon on ", Frequency, Modulation}) + -- Note that this is looped. I have to give this transmission a unique name, I use the class ID + trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) + + if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD + SCHEDULER:New( nil, + function() + self:StopRadioBeacon() + end, {}, BeaconDuration) + end + end +end + +--- Stops the AA TACAN BEACON +-- @param #BEACON self +-- @return #BEACON self +function BEACON:StopRadioBeacon() + self:F() + -- The unique name of the transmission is the class ID + trigger.action.stopRadioTransmission(tostring(self.ID)) + return self +end + +--- Converts a TACAN Channel/Mode couple into a frequency in Hz +-- @param #BEACON self +-- @param #number TACANChannel +-- @param #string TACANMode +-- @return #number Frequecy +-- @return #nil if parameters are invalid +function BEACON:_TACANToFrequency(TACANChannel, TACANMode) + self:F3({TACANChannel, TACANMode}) + + if type(TACANChannel) ~= "number" then + if TACANMode ~= "X" and TACANMode ~= "Y" then + return nil -- error in arguments + end + end + +-- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. +-- I have no idea what it does but it seems to work + local A = 1151 -- 'X', channel >= 64 + local B = 64 -- channel >= 64 + + if TACANChannel < 64 then + B = 1 + end + + if TACANMode == 'Y' then + A = 1025 + if TACANChannel < 64 then + A = 1088 + end + else -- 'X' + if TACANChannel < 64 then + A = 962 + end + end + + return (A + TACANChannel - B) * 1000000 +end \ No newline at end of file diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 3a557de65..74b5fa54b 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -236,6 +236,7 @@ do -- SETTINGS --- SETTINGS constructor. -- @param #SETTINGS self + -- @param #string PlayerName (Optional) Set settings for this player. -- @return #SETTINGS function SETTINGS:Set( PlayerName ) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index bdde44b6d..fdc6db2c7 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -1,5 +1,4 @@ --- The order of the declarations is important here. Don't touch it. - +--- GLOBALS: The order of the declarations is important here. Don't touch it. --- Declare the event dispatcher based on the EVENT class _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT @@ -10,9 +9,38 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU --- Declare the main database object, which is used internally by the MOOSE classes. _DATABASE = DATABASE:New() -- Core.Database#DATABASE +--- Settings _SETTINGS = SETTINGS:Set() _SETTINGS:SetPlayerMenuOn() +--- Register cargos. _DATABASE:_RegisterCargos() + +--- Register zones. _DATABASE:_RegisterZones() +--- Check if os etc is available. +BASE:I("Checking de-sanitization of os, io and lfs (Check /Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)") + +local __na=false +if os then + BASE:I("- os available") +else + BASE:I("- os NOT available! Some functions may not work.") + __na=true +end +if io then + BASE:I("- io available") +else + BASE:I("- io NOT available! Some functions may not work.") + __na=true +end +if lfs then + BASE:I("- lfs available") +else + BASE:I("- lfs NOT available! Some functions may not work.") + __na=true +end +if __na then + BASE:I("Check /Scripts/MissionScripting.lua and commend out the lines with sanitizeModule(''). Use at your own risk!)") +end \ No newline at end of file diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 06f53d723..2ee24ee38 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -3,8 +3,10 @@ __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) __Moose.Include( 'Scripts/Moose/Core/Report.lua' ) __Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' ) @@ -110,7 +112,7 @@ __Moose.Include( 'Scripts/Moose/Actions/Act_Account.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assist.lua' ) __Moose.Include( 'Scripts/Moose/Sound/UserSound.lua' ) -__Moose.Include( 'Scripts/Moose/Sound/SoundFile.lua' ) +__Moose.Include( 'Scripts/Moose/Sound/SoundOutput.lua' ) __Moose.Include( 'Scripts/Moose/Sound/Radio.lua' ) __Moose.Include( 'Scripts/Moose/Sound/RadioQueue.lua' ) __Moose.Include( 'Scripts/Moose/Sound/RadioSpeech.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 96353d11d..3870ba197 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -88,6 +88,8 @@ -- @field #boolean usemarker Use mark on the F10 map. -- @field #number markerid Numerical ID of the F10 map mark point. -- @field #number relHumidity Relative humidity (used to approximately calculate the dew point). +-- @field #boolean useSRS If true, use SRS for transmission. +-- @field Sound.SRS#MSRS msrs Moose SRS object. -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -1100,6 +1102,22 @@ function ATIS:MarkRunways(markall) end end +--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary. +-- @param #ATIS self +-- @param #string PathToSRS Path to SRS directory. +-- @param #string Gender Gender: "male" or "female" (default). +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @return #ATIS self +function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) + self.useSRS=true + self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) + self.msrs:SetGender(Gender) + self.msrs:SetCulture(Culture) + self.msrs:SetVoice(Voice) + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1188,15 +1206,25 @@ end -- @param #string To To state. function ATIS:onafterCheckQueue(From, Event, To) - if #self.radioqueue.queue==0 then - self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + if self.useSRS then + self:Broadcast() + + self:__CheckQueue(-120) + else - self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + + if #self.radioqueue.queue==0 then + self:T(self.lid..string.format("Radio queue empty. Repeating message.")) + self:Broadcast() + else + self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) + end + + -- Check back in 5 seconds. + self:__CheckQueue(-5) + end - - -- Check back in 5 seconds. - self:__CheckQueue(-5) end --- Broadcast ATIS radio message. @@ -1591,36 +1619,46 @@ function ATIS:onafterBroadcast(From, Event, To) if self.airbasename:find("AFB")==nil and self.airbasename:find("Airport")==nil and self.airbasename:find("Airstrip")==nil and self.airbasename:find("airfield")==nil and self.airbasename:find("AB")==nil then subtitle=subtitle.." Airport" end - self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + if not self.useSRS then + self.radioqueue:NewTransmission(string.format("%s/%s.ogg", self.theatre, self.airbasename), 3.0, self.soundpath, nil, nil, subtitle, self.subduration) + end local alltext=subtitle -- Information tag subtitle=string.format("Information %s", NATO) local _INFORMATION=subtitle - self:Transmission(ATIS.Sound.Information, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + if not self.useSRS then + self:Transmission(ATIS.Sound.Information, 0.5, subtitle) + self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + end alltext=alltext..";\n"..subtitle -- Zulu Time subtitle=string.format("%s Zulu", ZULU) - self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + if not self.useSRS then + self.radioqueue:Number2Transmission(ZULU, nil, 0.5) + self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) + end alltext=alltext..";\n"..subtitle if not self.zulutimeonly then -- Sunrise Time subtitle=string.format("Sunrise at %s local time", SUNRISE) - self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.SunriseAt, 0.5, subtitle) + self.radioqueue:Number2Transmission(SUNRISE, nil, 0.2) + self:Transmission(ATIS.Sound.TimeLocal, 0.2) + end alltext=alltext..";\n"..subtitle -- Sunset Time subtitle=string.format("Sunset at %s local time", SUNSET) - self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) - self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) - self:Transmission(ATIS.Sound.TimeLocal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.SunsetAt, 0.5, subtitle) + self.radioqueue:Number2Transmission(SUNSET, nil, 0.5) + self:Transmission(ATIS.Sound.TimeLocal, 0.2) + end alltext=alltext..";\n"..subtitle end @@ -1634,17 +1672,19 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle..", gusting" end local _WIND=subtitle - self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) - self.radioqueue:Number2Transmission(WINDFROM) - self:Transmission(ATIS.Sound.At, 0.2) - self.radioqueue:Number2Transmission(WINDSPEED) - if self.metric then - self:Transmission(ATIS.Sound.MetersPerSecond, 0.2) - else - self:Transmission(ATIS.Sound.Knots, 0.2) - end - if turbulence>0 then - self:Transmission(ATIS.Sound.Gusting, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.WindFrom, 1.0, subtitle) + self.radioqueue:Number2Transmission(WINDFROM) + self:Transmission(ATIS.Sound.At, 0.2) + self.radioqueue:Number2Transmission(WINDSPEED) + if self.metric then + self:Transmission(ATIS.Sound.MetersPerSecond, 0.2) + else + self:Transmission(ATIS.Sound.Knots, 0.2) + end + if turbulence>0 then + self:Transmission(ATIS.Sound.Gusting, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1654,12 +1694,14 @@ function ATIS:onafterBroadcast(From, Event, To) else subtitle=string.format("Visibility %s SM", VISIBILITY) end - self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) - self.radioqueue:Number2Transmission(VISIBILITY) - if self.metric then - self:Transmission(ATIS.Sound.Kilometers, 0.2) - else - self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Visibilty, 1.0, subtitle) + self.radioqueue:Number2Transmission(VISIBILITY) + if self.metric then + self:Transmission(ATIS.Sound.Kilometers, 0.2) + else + self:Transmission(ATIS.Sound.StatuteMiles, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1699,57 +1741,63 @@ function ATIS:onafterBroadcast(From, Event, To) -- Actual output if wp then subtitle=string.format("Weather phenomena:%s", wpsub) - self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) - if precepitation==1 then - self:Transmission(ATIS.Sound.Rain, 0.5) - elseif precepitation==2 then - self:Transmission(ATIS.Sound.ThunderStorm, 0.5) - elseif precepitation==3 then - self:Transmission(ATIS.Sound.Snow, 0.5) - elseif precepitation==4 then - self:Transmission(ATIS.Sound.SnowStorm, 0.5) - end - if fog then - self:Transmission(ATIS.Sound.Fog, 0.5) - end - if dust then - self:Transmission(ATIS.Sound.Dust, 0.5) + if not self.useSRS then + self:Transmission(ATIS.Sound.WeatherPhenomena, 1.0, subtitle) + if precepitation==1 then + self:Transmission(ATIS.Sound.Rain, 0.5) + elseif precepitation==2 then + self:Transmission(ATIS.Sound.ThunderStorm, 0.5) + elseif precepitation==3 then + self:Transmission(ATIS.Sound.Snow, 0.5) + elseif precepitation==4 then + self:Transmission(ATIS.Sound.SnowStorm, 0.5) + end + if fog then + self:Transmission(ATIS.Sound.Fog, 0.5) + end + if dust then + self:Transmission(ATIS.Sound.Dust, 0.5) + end end alltext=alltext..";\n"..subtitle end -- Cloud base - self:Transmission(CloudCover, 1.0, CLOUDSsub) + if not self.useSRS then + self:Transmission(CloudCover, 1.0, CLOUDSsub) + end if CLOUDBASE and static then -- Base if self.metric then - subtitle=string.format("Cloudbase %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) else - subtitle=string.format("Cloudbase %s, ceiling %s ft", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) end - self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) - if tonumber(CLOUDBASE1000)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) - end - if tonumber(CLOUDBASE0100)>0 then - self.radioqueue:Number2Transmission(CLOUDBASE0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - -- Ceiling - self:Transmission(ATIS.Sound.CloudCeiling, 0.5) - if tonumber(CLOUDCEIL1000)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) - end - if tonumber(CLOUDCEIL0100)>0 then - self.radioqueue:Number2Transmission(CLOUDCEIL0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) + if not self.useSRS then + self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) + if tonumber(CLOUDBASE1000)>0 then + self.radioqueue:Number2Transmission(CLOUDBASE1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(CLOUDBASE0100)>0 then + self.radioqueue:Number2Transmission(CLOUDBASE0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + -- Ceiling + self:Transmission(ATIS.Sound.CloudCeiling, 0.5) + if tonumber(CLOUDCEIL1000)>0 then + self.radioqueue:Number2Transmission(CLOUDCEIL1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(CLOUDCEIL0100)>0 then + self.radioqueue:Number2Transmission(CLOUDCEIL0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end end alltext=alltext..";\n"..subtitle @@ -1769,15 +1817,17 @@ function ATIS:onafterBroadcast(From, Event, To) end end local _TEMPERATURE=subtitle - self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) - if temperature<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) - end - self.radioqueue:Number2Transmission(TEMPERATURE) - if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) - else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Temperature, 1.0, subtitle) + if temperature<0 then + self:Transmission(ATIS.Sound.Minus, 0.2) + end + self.radioqueue:Number2Transmission(TEMPERATURE) + if self.TDegF then + self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + else + self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1796,15 +1846,17 @@ function ATIS:onafterBroadcast(From, Event, To) end end local _DEWPOINT=subtitle - self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) - if dewpoint<0 then - self:Transmission(ATIS.Sound.Minus, 0.2) - end - self.radioqueue:Number2Transmission(DEWPOINT) - if self.TDegF then - self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) - else - self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.DewPoint, 1.0, subtitle) + if dewpoint<0 then + self:Transmission(ATIS.Sound.Minus, 0.2) + end + self.radioqueue:Number2Transmission(DEWPOINT) + if self.TDegF then + self:Transmission(ATIS.Sound.DegreesFahrenheit, 0.2) + else + self:Transmission(ATIS.Sound.DegreesCelsius, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1813,51 +1865,53 @@ function ATIS:onafterBroadcast(From, Event, To) if self.qnhonly then subtitle=string.format("Altimeter %s.%s mmHg", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s mmHg", QNH[1], QNH[2], QFE[1], QFE[2]) end else if self.metric then if self.qnhonly then subtitle=string.format("Altimeter %s.%s hPa", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s hPa", QNH[1], QNH[2], QFE[1], QFE[2]) end else if self.qnhonly then subtitle=string.format("Altimeter %s.%s inHg", QNH[1], QNH[2]) else - subtitle=string.format("Altimeter QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2]) + subtitle=string.format("Altimeter: QNH %s.%s, QFE %s.%s inHg", QNH[1], QNH[2], QFE[1], QFE[2]) end end end local _ALTIMETER=subtitle - self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) - if not self.qnhonly then - self:Transmission(ATIS.Sound.QNH, 0.5) - end - self.radioqueue:Number2Transmission(QNH[1]) - - if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) - end - self.radioqueue:Number2Transmission(QNH[2]) - - if not self.qnhonly then - self:Transmission(ATIS.Sound.QFE, 0.75) - self.radioqueue:Number2Transmission(QFE[1]) - if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then - self:Transmission(ATIS.Sound.Decimal, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.Altimeter, 1.0, subtitle) + if not self.qnhonly then + self:Transmission(ATIS.Sound.QNH, 0.5) end - self.radioqueue:Number2Transmission(QFE[2]) - end + self.radioqueue:Number2Transmission(QNH[1]) + + if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then + self:Transmission(ATIS.Sound.Decimal, 0.2) + end + self.radioqueue:Number2Transmission(QNH[2]) - if self.PmmHg then - self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1) - else - if self.metric then - self:Transmission(ATIS.Sound.HectoPascal, 0.1) + if not self.qnhonly then + self:Transmission(ATIS.Sound.QFE, 0.75) + self.radioqueue:Number2Transmission(QFE[1]) + if ATIS.ICAOPhraseology[UTILS.GetDCSMap()] then + self:Transmission(ATIS.Sound.Decimal, 0.2) + end + self.radioqueue:Number2Transmission(QFE[2]) + end + + if self.PmmHg then + self:Transmission(ATIS.Sound.MillimetersOfMercury, 0.1) else - self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) + if self.metric then + self:Transmission(ATIS.Sound.HectoPascal, 0.1) + else + self:Transmission(ATIS.Sound.InchesOfMercury, 0.1) + end end end alltext=alltext..";\n"..subtitle @@ -1870,12 +1924,14 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle.." Right" end local _RUNACT=subtitle - self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) - self.radioqueue:Number2Transmission(runway) - if rwyLeft==true then - self:Transmission(ATIS.Sound.Left, 0.2) - elseif rwyLeft==false then - self:Transmission(ATIS.Sound.Right, 0.2) + if not self.useSRS then + self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) + self.radioqueue:Number2Transmission(runway) + if rwyLeft==true then + self:Transmission(ATIS.Sound.Left, 0.2) + elseif rwyLeft==false then + self:Transmission(ATIS.Sound.Right, 0.2) + end end alltext=alltext..";\n"..subtitle @@ -1900,21 +1956,22 @@ function ATIS:onafterBroadcast(From, Event, To) end -- Transmit. - self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + if not self.useSRS then + self:Transmission(ATIS.Sound.RunwayLength, 1.0, subtitle) + if tonumber(L1000)>0 then + self.radioqueue:Number2Transmission(L1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(L0100)>0 then + self.radioqueue:Number2Transmission(L0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) - end - alltext=alltext..";\n"..subtitle end @@ -1937,22 +1994,23 @@ function ATIS:onafterBroadcast(From, Event, To) subtitle=subtitle.." feet" end - -- Transmitt. - self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) - if tonumber(L1000)>0 then - self.radioqueue:Number2Transmission(L1000) - self:Transmission(ATIS.Sound.Thousand, 0.1) + -- Transmit. + if not self.useSRS then + self:Transmission(ATIS.Sound.Elevation, 1.0, subtitle) + if tonumber(L1000)>0 then + self.radioqueue:Number2Transmission(L1000) + self:Transmission(ATIS.Sound.Thousand, 0.1) + end + if tonumber(L0100)>0 then + self.radioqueue:Number2Transmission(L0100) + self:Transmission(ATIS.Sound.Hundred, 0.1) + end + if self.metric then + self:Transmission(ATIS.Sound.Meters, 0.1) + else + self:Transmission(ATIS.Sound.Feet, 0.1) + end end - if tonumber(L0100)>0 then - self.radioqueue:Number2Transmission(L0100) - self:Transmission(ATIS.Sound.Hundred, 0.1) - end - if self.metric then - self:Transmission(ATIS.Sound.Meters, 0.1) - else - self:Transmission(ATIS.Sound.Feet, 0.1) - end - alltext=alltext..";\n"..subtitle end @@ -1966,9 +2024,47 @@ function ATIS:onafterBroadcast(From, Event, To) end end subtitle=string.format("Tower frequency %s", freqs) - self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) - for _,freq in pairs(self.towerfrequency) do - local f=string.format("%.3f", freq) + if not self.useSRS then + self:Transmission(ATIS.Sound.TowerFrequency, 1.0, subtitle) + for _,freq in pairs(self.towerfrequency) do + local f=string.format("%.3f", freq) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end + end + alltext=alltext..";\n"..subtitle + end + + -- ILS + local ils=self:GetNavPoint(self.ils, runway, rwyLeft) + if ils then + subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) + if not self.useSRS then + self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) + local f=string.format("%.2f", ils.frequency) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end + alltext=alltext..";\n"..subtitle + end + + -- Outer NDB + local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) + if ndb then + subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) + if not self.useSRS then + self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) + local f=string.format("%.2f", ndb.frequency) f=UTILS.Split(f, ".") self.radioqueue:Number2Transmission(f[1], nil, 0.5) if tonumber(f[2])>0 then @@ -1977,41 +2073,6 @@ function ATIS:onafterBroadcast(From, Event, To) end self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - - alltext=alltext..";\n"..subtitle - end - - -- ILS - local ils=self:GetNavPoint(self.ils, runway, rwyLeft) - if ils then - subtitle=string.format("ILS frequency %.2f MHz", ils.frequency) - self:Transmission(ATIS.Sound.ILSFrequency, 1.0, subtitle) - local f=string.format("%.2f", ils.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - - alltext=alltext..";\n"..subtitle - end - - -- Outer NDB - local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft) - if ndb then - subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency) - self:Transmission(ATIS.Sound.OuterNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - alltext=alltext..";\n"..subtitle end @@ -2019,51 +2080,55 @@ function ATIS:onafterBroadcast(From, Event, To) local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft) if ndb then subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency) - self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) - local f=string.format("%.2f", ndb.frequency) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) - end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.InnerNDBFrequency, 1.0, subtitle) + local f=string.format("%.2f", ndb.frequency) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) + end alltext=alltext..";\n"..subtitle end -- VOR if self.vor then subtitle=string.format("VOR frequency %.2f MHz", self.vor) - self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) - local f=string.format("%.2f", self.vor) - f=UTILS.Split(f, ".") - self.radioqueue:Number2Transmission(f[1], nil, 0.5) - if tonumber(f[2])>0 then - self:Transmission(ATIS.Sound.Decimal, 0.2) - self.radioqueue:Number2Transmission(f[2]) + if not self.useSRS then + self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) + local f=string.format("%.2f", self.vor) + f=UTILS.Split(f, ".") + self.radioqueue:Number2Transmission(f[1], nil, 0.5) + if tonumber(f[2])>0 then + self:Transmission(ATIS.Sound.Decimal, 0.2) + self.radioqueue:Number2Transmission(f[2]) + end + self:Transmission(ATIS.Sound.MegaHertz, 0.2) end - self:Transmission(ATIS.Sound.MegaHertz, 0.2) - alltext=alltext..";\n"..subtitle end -- TACAN if self.tacan then subtitle=string.format("TACAN channel %dX", self.tacan) - self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) - self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.TACANChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(self.tacan), nil, 0.2) + self.radioqueue:NewTransmission("NATO Alphabet/Xray.ogg", 0.75, self.soundpath, nil, 0.2) + end alltext=alltext..";\n"..subtitle end -- RSBN if self.rsbn then subtitle=string.format("RSBN channel %d", self.rsbn) - self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) - + if not self.useSRS then + self:Transmission(ATIS.Sound.RSBNChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(self.rsbn), nil, 0.2) + end alltext=alltext..";\n"..subtitle end @@ -2071,17 +2136,19 @@ function ATIS:onafterBroadcast(From, Event, To) local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft) if ndb then subtitle=string.format("PRMG channel %d", ndb.frequency) - self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) - self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) - + if not self.useSRS then + self:Transmission(ATIS.Sound.PRMGChannel, 1.0, subtitle) + self.radioqueue:Number2Transmission(tostring(ndb.frequency), nil, 0.5) + end alltext=alltext..";\n"..subtitle end -- Advice on initial... subtitle=string.format("Advise on initial contact, you have information %s", NATO) - self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - + if not self.useSRS then + self:Transmission(ATIS.Sound.AdviceOnInitial, 0.5, subtitle) + self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) + end alltext=alltext..";\n"..subtitle -- Report ATIS text. @@ -2103,12 +2170,31 @@ end function ATIS:onafterReport(From, Event, To, Text) self:T(self.lid..string.format("Report:\n%s", Text)) - -- Remove line breaks - local text=string.gsub(Text, "[\r\n]", "") - env.info("FF: "..text) + if self.useSRS and self.msrs then - local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) - msrs:PlayText(text) + -- Remove line breaks + local text=string.gsub(Text, "[\r\n]", "") + + -- Replace other stuff. + local text=string.gsub(text, "SM", "statute miles") + local text=string.gsub(text, "°C", "degrees Celsius") + local text=string.gsub(text, "°F", "degrees Fahrenheit") + local text=string.gsub(text, "inHg", "inches of Mercury") + local text=string.gsub(text, "mmHg", "millimeters of Mercury") + local text=string.gsub(text, "hPa", "hecto Pascals") + local text=string.gsub(text, "m/s", "meters per second") + + -- Replace ";" by "." + local text=string.gsub(text, ";", ". ") + env.info("FF: "..text) + + --local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) + --msrs:PlayText(text) + + -- Play text-to-speech report. + self.msrs:PlayText(text) + + end end diff --git a/Moose Development/Moose/Sound/Radio.lua b/Moose Development/Moose/Sound/Radio.lua index 175bc2106..872ad6e26 100644 --- a/Moose Development/Moose/Sound/Radio.lua +++ b/Moose Development/Moose/Sound/Radio.lua @@ -1,13 +1,10 @@ ---- **Core** - Is responsible for everything that is related to radio transmission and you can hear in DCS, be it TACAN beacons, Radio transmissions. +--- **Sound** - Radio transmissions. -- -- === -- -- ## Features: -- -- * Provide radio functionality to broadcast radio transmissions. --- * Provide beacon functionality to assist pilots. --- --- The Radio contains 2 classes : RADIO and BEACON -- -- What are radio communications in DCS? -- @@ -35,13 +32,13 @@ -- -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- --- @module Core.Radio +-- @module Sound.Radio -- @image Core_Radio.JPG ---- Models the radio capability. +--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe -- --- ## RADIO usage +-- # RADIO usage -- -- There are 3 steps to a successful radio transmission. -- @@ -87,15 +84,15 @@ -- @field #string alias Name of the radio transmitter. -- @extends Core.Base#BASE RADIO = { - ClassName = "RADIO", - FileName = "", - Frequency = 0, - Modulation = radio.modulation.AM, - Subtitle = "", + ClassName = "RADIO", + FileName = "", + Frequency = 0, + Modulation = radio.modulation.AM, + Subtitle = "", SubtitleDuration = 0, - Power = 100, - Loop = false, - alias=nil, + Power = 100, + Loop = false, + alias = nil, } --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast. @@ -395,438 +392,3 @@ function RADIO:StopBroadcast() end return self end - - ---- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want. --- There are two types of BEACONs available : the AA TACAN Beacon and the general purpose Radio Beacon. --- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is --- attach to a cargo crate, for exemple. --- --- ## AA TACAN Beacon usage --- --- This beacon only works with airborne @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}. Use @{#BEACON:AATACAN}() to set the beacon parameters and start the beacon. --- Use @#BEACON:StopAATACAN}() to stop it. --- --- ## General Purpose Radio Beacon usage --- --- This beacon will work with any @{Wrapper.Positionable#POSITIONABLE}, but **it won't follow the @{Wrapper.Positionable#POSITIONABLE}** ! This means that you should only use it with --- @{Wrapper.Positionable#POSITIONABLE} that don't move, or move very slowly. Use @{#BEACON:RadioBeacon}() to set the beacon parameters and start the beacon. --- Use @{#BEACON:StopRadioBeacon}() to stop it. --- --- @type BEACON --- @field #string ClassName Name of the class "BEACON". --- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will receive radio capabilities. --- @extends Core.Base#BASE -BEACON = { - ClassName = "BEACON", - Positionable = nil, - name=nil, -} - ---- Beacon types supported by DCS. --- @type BEACON.Type --- @field #number NULL --- @field #number VOR --- @field #number DME --- @field #number VOR_DME --- @field #number TACAN TACtical Air Navigation system. --- @field #number VORTAC --- @field #number RSBN --- @field #number BROADCAST_STATION --- @field #number HOMER --- @field #number AIRPORT_HOMER --- @field #number AIRPORT_HOMER_WITH_MARKER --- @field #number ILS_FAR_HOMER --- @field #number ILS_NEAR_HOMER --- @field #number ILS_LOCALIZER --- @field #number ILS_GLIDESLOPE --- @field #number PRMG_LOCALIZER --- @field #number PRMG_GLIDESLOPE --- @field #number ICLS Same as ICLS glideslope. --- @field #number ICLS_LOCALIZER --- @field #number ICLS_GLIDESLOPE --- @field #number NAUTICAL_HOMER -BEACON.Type={ - NULL = 0, - VOR = 1, - DME = 2, - VOR_DME = 3, - TACAN = 4, - VORTAC = 5, - RSBN = 128, - BROADCAST_STATION = 1024, - HOMER = 8, - AIRPORT_HOMER = 4104, - AIRPORT_HOMER_WITH_MARKER = 4136, - ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16424, - ILS_LOCALIZER = 16640, - ILS_GLIDESLOPE = 16896, - PRMG_LOCALIZER = 33024, - PRMG_GLIDESLOPE = 33280, - ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE - ICLS_LOCALIZER = 131328, - ICLS_GLIDESLOPE = 131584, - NAUTICAL_HOMER = 65536, - -} - ---- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon --- @type BEACON.System --- @field #number PAR_10 ? --- @field #number RSBN_5 Russian VOR/DME system. --- @field #number TACAN TACtical Air Navigation system on ground. --- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. --- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. --- @field #number VOR Very High Frequency Omni-Directional Range --- @field #number ILS_LOCALIZER ILS localizer --- @field #number ILS_GLIDESLOPE ILS glideslope. --- @field #number PRGM_LOCALIZER PRGM localizer. --- @field #number PRGM_GLIDESLOPE PRGM glideslope. --- @field #number BROADCAST_STATION Broadcast station. --- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. --- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. --- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. --- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). --- @field #number ICLS_LOCALIZER Carrier landing system. --- @field #number ICLS_GLIDESLOPE Carrier landing system. -BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, - TACAN_TANKER_X = 4, - TACAN_TANKER_Y = 5, - VOR = 6, - ILS_LOCALIZER = 7, - ILS_GLIDESLOPE = 8, - PRMG_LOCALIZER = 9, - PRMG_GLIDESLOPE = 10, - BROADCAST_STATION = 11, - VORTAC = 12, - TACAN_AA_MODE_X = 13, - TACAN_AA_MODE_Y = 14, - VORDME = 15, - ICLS_LOCALIZER = 16, - ICLS_GLIDESLOPE = 17, -} - ---- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. --- If you want to create a BEACON, you probably should use @{Wrapper.Positionable#POSITIONABLE.GetBeacon}() instead. --- @param #BEACON self --- @param Wrapper.Positionable#POSITIONABLE Positionable The @{Positionable} that will receive radio capabilities. --- @return #BEACON Beacon object or #nil if the positionable is invalid. -function BEACON:New(Positionable) - - -- Inherit BASE. - local self=BASE:Inherit(self, BASE:New()) --#BEACON - - -- Debug. - self:F(Positionable) - - -- Set positionable. - if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid - self.Positionable = Positionable - self.name=Positionable:GetName() - self:I(string.format("New BEACON %s", tostring(self.name))) - return self - end - - self:E({"The passed positionable is invalid, no BEACON created", Positionable}) - return nil -end - - ---- Activates a TACAN BEACON. --- @param #BEACON self --- @param #number Channel TACAN channel, i.e. the "10" part in "10Y". --- @param #string Mode TACAN mode, i.e. the "Y" part in "10Y". --- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon. --- @param #boolean Bearing If true, beacon provides bearing information. If false (or nil), only distance information is available. --- @param #number Duration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") --- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- --- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon -function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) - self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) - - -- Get frequency. - local Frequency=UTILS.TACANToFrequency(Channel, Mode) - - -- Check. - if not Frequency then - self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) - return self - end - - -- Beacon type. - local Type=BEACON.Type.TACAN - - -- Beacon system. - local System=BEACON.System.TACAN - - -- Check if unit is an aircraft and set system accordingly. - local AA=self.Positionable:IsAir() - if AA then - System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER - -- Check if "Y" mode is selected for aircraft. - if Mode~="Y" then - self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable}) - end - end - - -- Attached unit. - local UnitID=self.Positionable:GetID() - - -- Debug. - self:I({string.format("BEACON Activating TACAN %s: Channel=%d%s, Morse=%s, Bearing=%s, Duration=%s!", tostring(self.name), Channel, Mode, Message, tostring(Bearing), tostring(Duration))}) - - -- Start beacon. - self.Positionable:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Mode, AA, Message, Bearing) - - -- Stop sheduler. - if Duration then - self.Positionable:DeactivateBeacon(Duration) - end - - return self -end - ---- Activates an ICLS BEACON. The unit the BEACON is attached to should be an aircraft carrier supporting this system. --- @param #BEACON self --- @param #number Channel ICLS channel. --- @param #string Callsign The Message that is going to be coded in Morse and broadcasted by the beacon. --- @param #number Duration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self -function BEACON:ActivateICLS(Channel, Callsign, Duration) - self:F({Channel=Channel, Callsign=Callsign, Duration=Duration}) - - -- Attached unit. - local UnitID=self.Positionable:GetID() - - -- Debug - self:T2({"ICLS BEACON started!"}) - - -- Start beacon. - self.Positionable:CommandActivateICLS(Channel, UnitID, Callsign) - - -- Stop sheduler - if Duration then -- Schedule the stop of the BEACON if asked by the MD - self.Positionable:DeactivateBeacon(Duration) - end - - return self -end - - - - - - ---- Activates a TACAN BEACON on an Aircraft. --- @param #BEACON self --- @param #number TACANChannel (the "10" part in "10Y"). Note that AA TACAN are only available on Y Channels --- @param #string Message The Message that is going to be coded in Morse and broadcasted by the beacon --- @param #boolean Bearing Can the BEACON be homed on ? --- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a TACAN Beacon for a tanker --- local myUnit = UNIT:FindByName("MyUnit") --- local myBeacon = myUnit:GetBeacon() -- Creates the beacon --- --- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon -function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) - self:F({TACANChannel, Message, Bearing, BeaconDuration}) - - local IsValid = true - - if not self.Positionable:IsAir() then - self:E({"The POSITIONABLE you want to attach the AA Tacan Beacon is not an aircraft ! The BEACON is not emitting", self.Positionable}) - IsValid = false - end - - local Frequency = self:_TACANToFrequency(TACANChannel, "Y") - if not Frequency then - self:E({"The passed TACAN channel is invalid, the BEACON is not emitting"}) - IsValid = false - end - - -- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing - -- or 14 (TACAN_AA_MODE_Y) if it does not - local System - if Bearing then - System = 5 - else - System = 14 - end - - if IsValid then -- Starts the BEACON - self:T2({"AA TACAN BEACON started !"}) - self.Positionable:SetCommand({ - id = "ActivateBeacon", - params = { - type = 4, - system = System, - callsign = Message, - frequency = Frequency, - } - }) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New(nil, - function() - self:StopAATACAN() - end, {}, BeaconDuration) - end - end - - return self -end - ---- Stops the AA TACAN BEACON --- @param #BEACON self --- @return #BEACON self -function BEACON:StopAATACAN() - self:F() - if not self.Positionable then - self:E({"Start the beacon first before stoping it !"}) - else - self.Positionable:SetCommand({ - id = 'DeactivateBeacon', - params = { - } - }) - end -end - - ---- Activates a general pupose Radio Beacon --- This uses the very generic singleton function "trigger.action.radioTransmission()" provided by DCS to broadcast a sound file on a specific frequency. --- Although any frequency could be used, only 2 DCS Modules can home on radio beacons at the time of writing : the Huey and the Mi-8. --- They can home in on these specific frequencies : --- * **Mi8** --- * R-828 -> 20-60MHz --- * ARKUD -> 100-150MHz (canal 1 : 114166, canal 2 : 114333, canal 3 : 114583, canal 4 : 121500, canal 5 : 123100, canal 6 : 124100) AM --- * ARK9 -> 150-1300KHz --- * **Huey** --- * AN/ARC-131 -> 30-76 Mhz FM --- @param #BEACON self --- @param #string FileName The name of the audio file --- @param #number Frequency in MHz --- @param #number Modulation either radio.modulation.AM or radio.modulation.FM --- @param #number Power in W --- @param #number BeaconDuration How long will the beacon last in seconds. Omit for forever. --- @return #BEACON self --- @usage --- -- Let's create a beacon for a unit in distress. --- -- Frequency will be 40MHz FM (home-able by a Huey's AN/ARC-131) --- -- The beacon they use is battery-powered, and only lasts for 5 min --- local UnitInDistress = UNIT:FindByName("Unit1") --- local UnitBeacon = UnitInDistress:GetBeacon() --- --- -- Set the beacon and start it --- UnitBeacon:RadioBeacon("MySoundFileSOS.ogg", 40, radio.modulation.FM, 20, 5*60) -function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDuration) - self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) - local IsValid = false - - -- Check the filename - if type(FileName) == "string" then - if FileName:find(".ogg") or FileName:find(".wav") then - if not FileName:find("l10n/DEFAULT/") then - FileName = "l10n/DEFAULT/" .. FileName - end - IsValid = true - end - end - if not IsValid then - self:E({"File name invalid. Maybe something wrong with the extension ? ", FileName}) - end - - -- Check the Frequency - if type(Frequency) ~= "number" and IsValid then - self:E({"Frequency invalid. ", Frequency}) - IsValid = false - end - Frequency = Frequency * 1000000 -- Conversion to Hz - - -- Check the modulation - if Modulation ~= radio.modulation.AM and Modulation ~= radio.modulation.FM and IsValid then --TODO Maybe make this future proof if ED decides to add an other modulation ? - self:E({"Modulation is invalid. Use DCS's enum radio.modulation.", Modulation}) - IsValid = false - end - - -- Check the Power - if type(Power) ~= "number" and IsValid then - self:E({"Power is invalid. ", Power}) - IsValid = false - end - Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that - - if IsValid then - self:T2({"Activating Beacon on ", Frequency, Modulation}) - -- Note that this is looped. I have to give this transmission a unique name, I use the class ID - trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID)) - - if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD - SCHEDULER:New( nil, - function() - self:StopRadioBeacon() - end, {}, BeaconDuration) - end - end -end - ---- Stops the AA TACAN BEACON --- @param #BEACON self --- @return #BEACON self -function BEACON:StopRadioBeacon() - self:F() - -- The unique name of the transmission is the class ID - trigger.action.stopRadioTransmission(tostring(self.ID)) - return self -end - ---- Converts a TACAN Channel/Mode couple into a frequency in Hz --- @param #BEACON self --- @param #number TACANChannel --- @param #string TACANMode --- @return #number Frequecy --- @return #nil if parameters are invalid -function BEACON:_TACANToFrequency(TACANChannel, TACANMode) - self:F3({TACANChannel, TACANMode}) - - if type(TACANChannel) ~= "number" then - if TACANMode ~= "X" and TACANMode ~= "Y" then - return nil -- error in arguments - end - end - --- This code is largely based on ED's code, in DCS World\Scripts\World\Radio\BeaconTypes.lua, line 137. --- I have no idea what it does but it seems to work - local A = 1151 -- 'X', channel >= 64 - local B = 64 -- channel >= 64 - - if TACANChannel < 64 then - B = 1 - end - - if TACANMode == 'Y' then - A = 1025 - if TACANChannel < 64 then - A = 1088 - end - else -- 'X' - if TACANChannel < 64 then - A = 962 - end - end - - return (A + TACANChannel - B) * 1000000 -end - - diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 1f5e9cd07..53207850f 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -77,7 +77,7 @@ RADIOQUEUE = { --- Create a new RADIOQUEUE object for a given radio frequency/modulation. -- @param #RADIOQUEUE self -- @param #number frequency The radio frequency in MHz. --- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. +-- @param #number modulation (Optional) The radio modulation. Default `radio.modulation.AM` (=0). -- @param #string alias (Optional) Name of the radio queue. -- @return #RADIOQUEUE self The RADIOQUEUE object. function RADIOQUEUE:New(frequency, modulation, alias) @@ -245,8 +245,6 @@ end -- @return #RADIOQUEUE.Transmission Radio transmission table. function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, subtitle, subduration) - env.info("FF new transmission.") - -- Sanity checks. if not filename then self:E(self.lid.."ERROR: No filename specified.") @@ -288,7 +286,7 @@ end --- Create a new transmission and add it to the radio queue. -- @param #RADIOQUEUE self --- @param Sound.SoundFile#SOUNDFILE soundfile Sound file object to be added. +-- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added. -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index c4fc31c15..78cbaeaf2 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -71,7 +71,7 @@ MSRS = { modulations = {}, coalition = 0, gender = "female", - culture = "en-GB", + culture = nil, voice = nil, volume = 1, speed = 1, @@ -81,15 +81,19 @@ MSRS = { altitude = nil, } +--- Counter. +_MSRSuuid=0 + --- MSRS class version. -- @field #string version -MSRS.version="0.0.2" +MSRS.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add functions to add/remove freqs and modulations. +-- TODO: Add coordinate. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -144,7 +148,7 @@ function MSRS:SetPath(Path) n=n+1 end - self.path=self.path.."/" + self.path=self.path --.."/" self:I(string.format("SRS path=%s", self:GetPath())) @@ -225,16 +229,40 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @return #MSRS self function MSRS:SetGender(Gender) - + self:I("Input gender to "..tostring(Gender)) + Gender=Gender or "female" - Gender=Gender:lower() + self.gender=Gender:lower() - self.gender=Gender + self:I("Setting gender to "..tostring(self.gender)) return self end +--- Set culture. +-- @param #MSRS self +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @return #MSRS self +function MSRS:SetCulture(Culture) + + self.culture=Culture + + return self +end + +--- Set to use a specific voice. Will override gender and culture settings. +-- @param #MSRS self +-- @param #string Voice Voice. +-- @return #MSRS self +function MSRS:SetVoice(Voice) + + self.voice=Voice + + return self +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transmission Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -250,16 +278,20 @@ function MSRS:PlaySoundFile(Soundfile, Delay) self:ScheduleOnce(Delay, MSRS.PlaySoundFile, self, Soundfile, 0) else + -- Sound file name. local soundfile=Soundfile:GetName() + -- Get command. local command=self:_GetCommand() + -- Append file. command=command.." --file="..tostring(soundfile) - env.info(string.format("FF PlaySoundfile command=%s", command)) + -- Debug output. + self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) end @@ -277,14 +309,17 @@ function MSRS:PlaySoundText(SoundText, Delay) self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0) else + -- Get command. local command=self:_GetCommand(nil, nil, nil, SoundText.gender, SoundText.voice, SoundText.culture, SoundText.volume, SoundText.speed) + -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) - env.info(string.format("FF PlaySoundfile command=%s", command)) + -- Debug putput. + self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. - os.execute(command) + local x=os.execute(command) end @@ -302,17 +337,79 @@ function MSRS:PlayText(Text, Delay) self:ScheduleOnce(Delay, MSRS.PlayText, self, Text, 0) else - local text=string.format("\"%s\"", Text) - + -- Get command line. local command=self:_GetCommand() + -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) - env.info(string.format("FF Text command=%s", command)) + -- Check that length of command is max 255 chars or os.execute() will not work! + if string.len(command)>255 then + + -- Create a tmp file. + local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + + local script = io.open(filename, "w+") + script:write(command.." && exit") + script:close() + + -- Play command. + command=string.format("\"%s\"", filename) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.05) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + else + + -- Debug output. + self:I(string.format("MSRS Text command=%s", command)) + + -- Execute SRS command. + local x=os.execute(command) + + end + + + end + + return self +end + + +--- Play text file via STTS. +-- @param #MSRS self +-- @param #string TextFile Full path to the file. +-- @param #number Delay Delay in seconds, before the message is played. +-- @return #MSRS self +function MSRS:PlayTextFile(TextFile, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, MSRS.PlayTextFile, self, TextFile, 0) + else + + -- First check if text file exists! + local exists=UTILS.FileExists(TextFile) + if not exists then + self:E("ERROR: MSRS Text file does not exist! File="..tostring(TextFile)) + return self + end + + -- Get command line. + local command=self:_GetCommand() + + -- Append text file. + command=command..string.format(" --textFile=\"%s\"", tostring(TextFile)) + + -- Debug output. + self:I(string.format("MSRS TextFile command=%s", command)) + + -- Count length of command. + local l=string.len(command) -- Execute SRS command. local x=os.execute(command) - env.info(x) end @@ -338,8 +435,8 @@ end -- @return #string Command. function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port) - - local exe=self:GetPath().."DCS-SR-ExternalAudio.exe" + local path=self:GetPath() or STTS.DIRECTORY + local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" freqs=table.concat(freqs or self.frequencies, ",") modus=table.concat(modus or self.modulations, ",") coal=coal or self.coalition @@ -348,19 +445,34 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp culture=culture or self.culture volume=volume or self.volume speed=speed or self.speed - port=port or self.port + port=port or self.port - local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --gender=%s --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, gender, volume, speed) + env.info("FF gender="..tostring(gender)) + env.info("FF gender="..tostring(self.gender)) + -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. + --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) + + -- Command from orig STTS script. Works better for some unknown reason! + local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\"", path, exe, freqs, modus, coal, port, "ROBOT") + + -- Set voice or gender/culture. if voice then + -- Use a specific voice (no need for gender and/or culture. command=command..string.format(" --voice=\"%s\"", tostring(voice)) + else + -- Add gender. + if gender and gender~="female" then + command=command..string.format(" --gender=%s", tostring(gender)) + end + -- Add culture. + if culture and culture~="en-GB" then + command=command..string.format(" -l %s", tostring(culture)) + end end - if culture then - command=command.." --culture="..tostring(culture) - end - - env.info("FF command="..command) + -- Debug output. + self:I("MSRS command="..command) return command end diff --git a/Moose Development/Moose/Sound/SoundFile.lua b/Moose Development/Moose/Sound/SoundOutput.lua similarity index 72% rename from Moose Development/Moose/Sound/SoundFile.lua rename to Moose Development/Moose/Sound/SoundOutput.lua index 884eeab6a..697d63941 100644 --- a/Moose Development/Moose/Sound/SoundFile.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -4,7 +4,8 @@ -- -- ## Features: -- --- * Add a sound file to the +-- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions +-- * Create a SOUNDTEXT object for text-to-speech output -- -- === -- @@ -12,22 +13,19 @@ -- -- === -- --- @module Sound.Soundfile --- @image Sound_Soundfile.png --- +-- @module Sound.SoundOutput +-- @image Sound_SoundOutput.png do -- Sound Base --- @type SOUNDBASE - -- @field #string ClassName Name of the class + -- @field #string ClassName Name of the class. -- @extends Core.Base#BASE - --- Sound files used by other classes. + --- Basic sound output inherited by other classes. -- - -- # 1. USERFLAG constructor - -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- This class is **not** meant to be used by "ordinary" users. -- -- @field #SOUNDBASE SOUNDBASE={ @@ -40,7 +38,7 @@ do -- Sound Base function SOUNDBASE:New() -- Inherit BASE. - local self=BASE:Inherit(self, BASE:New()) -- #SOUNDFILE + local self=BASE:Inherit(self, BASE:New()) -- #SOUNDBASE @@ -94,10 +92,11 @@ do -- Sound File -- Set file name. self:SetFileName(FileName) - -- Set path + -- Set path. self:SetPath(Path) - self.duration=Duration or 3 + -- Set duration. + self:SetDuration(Duration) -- Debug info: self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) @@ -137,11 +136,11 @@ do -- Sound File --- Set sound file name. This must be a .ogg or .mp3 file! -- @param #SOUNDFILE self - -- @param #string FileName Name of the file. + -- @param #string FileName Name of the file. Default is "Hello World.mp3". -- @return #SOUNDFILE self function SOUNDFILE:SetFileName(FileName) --TODO: check that sound file is really .ogg or .mp3 - self.filename=FileName or "HelloWorld.mp3" + self.filename=FileName or "Hello World.mp3" return self end @@ -187,17 +186,27 @@ do -- Text-To-Speech -- @field #string ClassName Name of the class -- @field #string text Text to speak. -- @field #number duration Duration in seconds. - -- @field #string gender Gender. - -- @field #string voice Voice. - -- @field #string culture Culture. + -- @field #string gender Gender: "male", "female". + -- @field #string culture Culture, e.g. "en-GB". + -- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings. -- @extends Core.Base#BASE - --- Sound files used by other classes. + --- Text-to-speech objects for other classes. -- - -- # 1. USERFLAG constructor + -- # Constructor -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. + -- + -- Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) + -- Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) + -- Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) + -- Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German + -- Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) + -- Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French + -- Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) + -- Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian + -- Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) -- -- @field #SOUNDTEXT SOUNDTEXT={ @@ -216,8 +225,8 @@ do -- Text-To-Speech self:SetText(Text) self:SetDuration(Duration) - self:SetGender() - self:SetCulture() + --self:SetGender() + --self:SetCulture() -- Debug info: self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) @@ -258,9 +267,10 @@ do -- Text-To-Speech return self end - --- Set the voice name. See the list from --help or if using google see: https://cloud.google.com/text-to-speech/docs/voices + --- Set to use a specific voice name. + -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices -- @param #SOUNDTEXT self - -- @param #string Voice + -- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self function SOUNDTEXT:SetVoice(Voice) diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua new file mode 100644 index 000000000..97836a1ee --- /dev/null +++ b/Moose Development/Moose/Utilities/STTS.lua @@ -0,0 +1,256 @@ +--- **Utilities** DCS Simple Text-To-Speech (STTS). +-- +-- +-- +-- @module Utils.STTS +-- @image MOOSE.JPG + +--- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) +-- @type STTS +-- @field #string DIRECTORY Path of the SRS directory. + +--- Simple Text-To-Speech +-- +-- Version 0.4 - Compatible with SRS version 1.9.6.0+ +-- +-- # DCS Modification Required +-- +-- You will need to edit MissionScripting.lua in DCS World/Scripts/MissionScripting.lua and remove the sanitisation. +-- To do this remove all the code below the comment - the line starts "local function sanitizeModule(name)" +-- Do this without DCS running to allow mission scripts to use os functions. +-- +-- *You WILL HAVE TO REAPPLY AFTER EVERY DCS UPDATE* +-- +-- # USAGE: +-- +-- Add this script into the mission as a DO SCRIPT or DO SCRIPT FROM FILE to initialise it +-- Make sure to edit the STTS.SRS_PORT and STTS.DIRECTORY to the correct values before adding to the mission. +-- Then its as simple as calling the correct function in LUA as a DO SCRIPT or in your own scripts. +-- +-- Example calls: +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2) +-- +-- Arguments in order are: +-- +-- * Message to say, make sure not to use a newline (\n) ! +-- * Frequency in MHz +-- * Modulation - AM/FM +-- * Volume - 1.0 max, 0.5 half +-- * Name of the transmitter - ATC, RockFM etc +-- * Coalition - 0 spectator, 1 red 2 blue +-- * OPTIONAL - Vec3 Point i.e Unit.getByName("A UNIT"):getPoint() - needs Vec3 for Height! OR null if not needed +-- * OPTIONAL - Speed -10 to +10 +-- * OPTIONAL - Gender male, female or neuter +-- * OPTIONAL - Culture - en-US, en-GB etc +-- * OPTIONAL - Voice - a specfic voice by name. Run DCS-SR-ExternalAudio.exe with --help to get the ones you can use on the command line +-- * OPTIONAL - Google TTS - Switch to Google Text To Speech - Requires STTS.GOOGLE_CREDENTIALS path and Google project setup correctly +-- +-- +-- ## Example +-- +-- This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,null,-5,"male","en-GB") +-- +-- ## Example +-- +--This example will say the words "Hello DCS WORLD" on 251 MHz AM at maximum volume with a client called SRS and to the Blue coalition only centered on the position of the Unit called "A UNIT" +-- +-- STTS.TextToSpeech("Hello DCS WORLD","251","AM","1.0","SRS",2,Unit.getByName("A UNIT"):getPoint(),-5,"male","en-GB") +-- +-- Arguments in order are: +-- +-- * FULL path to the MP3 OR OGG to play +-- * Frequency in MHz - to use multiple separate with a comma - Number of frequencies MUST match number of Modulations +-- * Modulation - AM/FM - to use multiple +-- * Volume - 1.0 max, 0.5 half +-- * Name of the transmitter - ATC, RockFM etc +-- * Coalition - 0 spectator, 1 red 2 blue +-- +-- ## Example +-- +-- This will play that MP3 on 255MHz AM & 31 FM at half volume with a client called "Multiple" and to Spectators only +-- +-- STTS.PlayMP3("C:\\Users\\Ciaran\\Downloads\\PR-Music.mp3","255,31","AM,FM","0.5","Multiple",0) +-- +-- @field #STTS +STTS={ + ClassName="STTS", + DIRECTORY="", + SRS_PORT=5002, + GOOGLE_CREDENTIALS="C:\\Users\\Ciaran\\Downloads\\googletts.json", + EXECUTABLE="DCS-SR-ExternalAudio.exe", +} + +--- FULL Path to the FOLDER containing DCS-SR-ExternalAudio.exe - EDIT TO CORRECT FOLDER +STTS.DIRECTORY = "D:/DCS/_SRS" + +--- LOCAL SRS PORT - DEFAULT IS 5002 +STTS.SRS_PORT = 5002 + +--- Google credentials file +STTS.GOOGLE_CREDENTIALS = "C:\\Users\\Ciaran\\Downloads\\googletts.json" + +--- DONT CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING +STTS.EXECUTABLE = "DCS-SR-ExternalAudio.exe" + + +--- Function for UUID. +function STTS.uuid() + local random = math.random + local template ='yxxx-xxxxxxxxxxxx' + return string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and random(0, 0xf) or random(8, 0xb) + return string.format('%x', v) + end) +end + +--- Round a number. +-- @param #number x Number. +-- @param #number n Precision. +function STTS.round(x, n) + n = math.pow(10, n or 0) + x = x * n + if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end + return x / n +end + +--- Function returns estimated speech time in seconds. +-- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so +-- +-- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second +-- +-- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: +-- +-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min +-- +function STTS.getSpeechTime(length,speed,isGoogle) + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1/speedFactor + end + end + + local wpm = math.ceil(100 * speedFactor) + local cps = math.floor((wpm * 5)/60) + + if type(length) == "string" then + length = string.len(length) + end + + return math.ceil(length/cps) +end + +--- Text to speech function. +function STTS.TextToSpeech(message, freqs, modulations, volume, name, coalition, point, speed, gender, culture, voice, googleTTS) + if os == nil or io == nil then + env.info("[DCS-STTS] LUA modules os or io are sanitized. skipping. ") + return + end + + speed = speed or 1 + gender = gender or "female" + culture = culture or "" + voice = voice or "" + coalition=coalition or "0" + name=name or "ROBOT" + volume=1 + speed=1 + + + message = message:gsub("\"","\\\"") + + local cmd = string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", STTS.DIRECTORY, STTS.EXECUTABLE, freqs or "305", modulations or "AM", coalition, STTS.SRS_PORT, name) + + if voice ~= "" then + cmd = cmd .. string.format(" -V \"%s\"",voice) + else + + if culture ~= "" then + cmd = cmd .. string.format(" -l %s",culture) + end + + if gender ~= "" then + cmd = cmd .. string.format(" -g %s",gender) + end + end + + if googleTTS == true then + cmd = cmd .. string.format(" -G \"%s\"",STTS.GOOGLE_CREDENTIALS) + end + + if speed ~= 1 then + cmd = cmd .. string.format(" -s %s",speed) + end + + if volume ~= 1.0 then + cmd = cmd .. string.format(" -v %s",volume) + end + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + cmd = cmd ..string.format(" -t \"%s\"",message) + + if string.len(cmd) > 255 then + local filename = os.getenv('TMP') .. "\\DCS_STTS-" .. STTS.uuid() .. ".bat" + local script = io.open(filename,"w+") + script:write(cmd .. " && exit" ) + script:close() + cmd = string.format("\"%s\"",filename) + timer.scheduleFunction(os.remove, filename, timer.getTime() + 1) + end + + if string.len(cmd) > 255 then + env.info("[DCS-STTS] - cmd string too long") + env.info("[DCS-STTS] TextToSpeech Command :\n" .. cmd.."\n") + end + os.execute(cmd) + + return STTS.getSpeechTime(message,speed,googleTTS) +end + +--- Play mp3 function. +-- @param #string pathToMP3 Path to the sound file. +-- @param #string freqs Frequencies, e.g. "305, 256". +-- @param #string modulations Modulations, e.g. "AM, FM". +-- @param #string volume Volume, e.g. "0.5". +function STTS.PlayMP3(pathToMP3, freqs, modulations, volume, name, coalition, point) + + local cmd = string.format("start \"\" /d \"%s\" /b /min \"%s\" -i \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -v %s -h", + STTS.DIRECTORY, STTS.EXECUTABLE, pathToMP3, freqs or "305", modulations or "AM", coalition or "0", STTS.SRS_PORT, name or "ROBOT", volume or "1") + + if point and type(point) == "table" and point.x then + local lat, lon, alt = coord.LOtoLL(point) + + lat = STTS.round(lat,4) + lon = STTS.round(lon,4) + alt = math.floor(alt) + + cmd = cmd .. string.format(" -L %s -O %s -A %s",lat,lon,alt) + end + + env.info("[DCS-STTS] MP3/OGG Command :\n" .. cmd.."\n") + os.execute(cmd) + +end \ No newline at end of file diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 939bf7770..0bdf83161 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -4,8 +4,10 @@ Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua +Utilities/STTS.lua Core/Base.lua +Core/Beacon.lua Core/UserFlag.lua Core/Report.lua Core/Scheduler.lua @@ -111,7 +113,7 @@ Actions/Act_Account.lua Actions/Act_Assist.lua Sound/UserSound.lua -Sound/SoundFile.lua +Sound/SoundOutput.lua Sound/Radio.lua Sound/RadioQueue.lua Sound/RadioSpeech.lua From 2544ec5587c4a5d8cf06b4a571c0eb6d0d94c756 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 01:44:36 +0200 Subject: [PATCH 270/382] Update SRS.lua --- Moose Development/Moose/Sound/SRS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 78cbaeaf2..08eafd52f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -463,7 +463,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp else -- Add gender. if gender and gender~="female" then - command=command..string.format(" --gender=%s", tostring(gender)) + command=command..string.format(" -g %s", tostring(gender)) end -- Add culture. if culture and culture~="en-GB" then From cfcd7d7588bf4d4cbb6aecc13e2c1e6e13a7bd76 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 May 2021 22:36:52 +0200 Subject: [PATCH 271/382] Sound update --- Moose Development/Moose/Globals.lua | 5 ++-- Moose Development/Moose/Ops/ATIS.lua | 4 +-- Moose Development/Moose/Sound/RadioQueue.lua | 30 ++++++++++++++----- Moose Development/Moose/Sound/SRS.lua | 5 +--- Moose Development/Moose/Sound/SoundOutput.lua | 19 ++++++++++-- Moose Development/Moose/Sound/UserSound.lua | 4 +-- 6 files changed, 45 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index fdc6db2c7..734f7c1df 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -20,8 +20,7 @@ _DATABASE:_RegisterCargos() _DATABASE:_RegisterZones() --- Check if os etc is available. -BASE:I("Checking de-sanitization of os, io and lfs (Check /Scripts/MissionScripting.lua and commend out sanitizeModule(''). Use at your own risk!)") - +BASE:I("Checking de-sanitization of os, io and lfs:") local __na=false if os then BASE:I("- os available") @@ -42,5 +41,5 @@ else __na=true end if __na then - BASE:I("Check /Scripts/MissionScripting.lua and commend out the lines with sanitizeModule(''). Use at your own risk!)") + BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") end \ No newline at end of file diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 3870ba197..c278539fc 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1115,6 +1115,7 @@ function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetGender(Gender) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) + self.msrs:SetPort(Port) return self end @@ -2187,9 +2188,6 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace ";" by "." local text=string.gsub(text, ";", ". ") env.info("FF: "..text) - - --local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, Modulation) - --msrs:PlayText(text) -- Play text-to-speech report. self.msrs:PlayText(text) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 53207850f..11cea9de8 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -70,8 +70,8 @@ RADIOQUEUE = { -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. -- @field #number interval Interval in seconds before next transmission. --- @field Sound.SoundFile#SOUNDFILE soundfile Sound file object to play via SRS. --- @field Sound.SoundFile#SOUNDTEXT soundtext Sound TTS object to play via SRS. +-- @field Sound.SoundOutput#SOUNDFILE soundfile Sound file object to play via SRS. +-- @field Sound.SoundOutput#SOUNDTEXT soundtext Sound TTS object to play via SRS. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -176,9 +176,11 @@ end --- Set SRS. -- @param #RADIOQUEUE self -- @param #string PathToSRS Path to SRS. +-- @param #number Port SRS port. Default 5002. -- @return #RADIOQUEUE self The RADIOQUEUE object. -function RADIOQUEUE:SetSRS(PathToSRS) +function RADIOQUEUE:SetSRS(PathToSRS, Port) self.msrs=MSRS:New(PathToSRS, self.frequency/1000000, self.modulation) + self.msrs:SetPort(Port) return self end @@ -284,19 +286,33 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, return transmission end ---- Create a new transmission and add it to the radio queue. +--- Add a SOUNDFILE to the radio queue. -- @param #RADIOQUEUE self -- @param Sound.SoundOutput#SOUNDFILE soundfile Sound file object to be added. -- @param #number tstart Start time (abs) seconds. Default now. -- @param #number interval Interval in seconds after the last transmission finished. -- @return #RADIOQUEUE self function RADIOQUEUE:AddSoundFile(soundfile, tstart, interval) - env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) + --env.info(string.format("FF add soundfile: name=%s%s", soundfile:GetPath(), soundfile:GetFileName())) local transmission=self:NewTransmission(soundfile:GetFileName(), soundfile.duration, soundfile:GetPath(), tstart, interval, soundfile.subtitle, soundfile.subduration) transmission.soundfile=soundfile return self end +--- Add a SOUNDTEXT to the radio queue. +-- @param #RADIOQUEUE self +-- @param Sound.SoundOutput#SOUNDTEXT soundtext Text-to-speech text. +-- @param #number tstart Start time (abs) seconds. Default now. +-- @param #number interval Interval in seconds after the last transmission finished. +-- @return #RADIOQUEUE self +function RADIOQUEUE:AddSoundText(soundtext, tstart, interval) + + local transmission=self:NewTransmission("SoundText.ogg", soundtext.duration, nil, tstart, interval, soundtext.subtitle, soundtext.subduration) + transmission.soundtext=soundtext + return self +end + + --- Convert a number (as string) into a radio transmission. -- E.g. for board number or headings. -- @param #RADIOQUEUE self @@ -340,7 +356,7 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) - if (transmission.soundfile or transmission.soundtext) and self.msrs then + if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then self:_BroadcastSRS(transmission) return end @@ -441,7 +457,7 @@ end -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:_BroadcastSRS(transmission) - if transmission.soundfile then + if transmission.soundfile and transmission.soundfile.useSRS then self.msrs:PlaySoundFile(transmission.soundfile) elseif transmission.soundtext then self.msrs:PlaySoundText(transmission.soundtext) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 78cbaeaf2..119f9b167 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -447,14 +447,11 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp speed=speed or self.speed port=port or self.port - env.info("FF gender="..tostring(gender)) - env.info("FF gender="..tostring(self.gender)) - -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) -- Command from orig STTS script. Works better for some unknown reason! - local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\"", path, exe, freqs, modus, coal, port, "ROBOT") + local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 697d63941..bb974af08 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -57,7 +57,7 @@ do -- Sound File -- @field #string duration Duration of the sound file in seconds. -- @field #string subtitle Subtitle of the transmission. -- @field #number subduration Duration in seconds how long the subtitle is displayed. - -- @field #boolean insideMiz If true (default), the sound file is located inside the mission .miz file. + -- @field #boolean useSRS If true, sound file is played via SRS. Sound file needs to be on local disk not inside the miz file! -- @extends Core.Base#BASE @@ -75,7 +75,7 @@ do -- Sound File duration = 3, subtitle = nil, subduration = 0, - insideMiz = true, + useSRS = false, } --- Constructor to create a new SOUNDFILE object. @@ -178,6 +178,19 @@ do -- Sound File return name end + --- Get the complete sound file name inlcuding its path. + -- @param #SOUNDFILE self + -- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission. + -- @return #SOUNDFILE self + function SOUNDFILE:UseSRS(Switch) + if Switch==true or Switch==nil then + self.useSRS=true + else + self.useSRS=false + end + return self + end + end do -- Text-To-Speech @@ -224,7 +237,7 @@ do -- Text-To-Speech local self=BASE:Inherit(self, BASE:New()) -- #SOUNDTEXT self:SetText(Text) - self:SetDuration(Duration) + self:SetDuration(Duration or STTS.getSpeechTime(Text)) --self:SetGender() --self:SetCulture() diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index b0f6fb393..8b94ad114 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -1,4 +1,4 @@ ---- **Core** - Manage user sound. +--- **Sound** - Manage user sound. -- -- === -- @@ -16,7 +16,7 @@ -- -- === -- --- @module Core.UserSound +-- @module Sound.UserSound -- @image Core_Usersound.JPG do -- UserSound From 05f95796f6abec34c23f61c652d977fa0ef94c67 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 31 May 2021 23:47:43 +0200 Subject: [PATCH 272/382] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 1 + Moose Development/Moose/Sound/SRS.lua | 36 +++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index c278539fc..8f6d3045b 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -18,6 +18,7 @@ -- * Option to present information in imperial or metric units -- * Runway length and airfield elevation (optional) -- * Frequencies/channels of nav aids (ILS, VOR, NDB, TACAN, PRMG, RSBN) (optional) +-- * SRS Simple-Text-To-Speech (STTS) integration (no sound files necessary) -- -- === -- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 119f9b167..04eaa762d 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -57,9 +57,40 @@ -- -- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS). -- --- # Prerequisites +-- ## Prerequisites -- --- This script needs SRS version >= 0.9.6. +-- This script needs SRS version >= 1.9.6. +-- +-- # Play Sound Files +-- +-- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "D:\\Sounds For DCS") +-- local msrs=MSRS:New("C:\\Path To SRS", 251, radio.modulation.AM) +-- msrs:PlaySoundFile(soundfile) +-- +-- # Play Text-To-Speech +-- +-- Basic example: +-- +-- -- Create a SOUNDTEXT object. +-- local text=SOUNDTEXT:New("All Enemies destroyed") +-- +-- -- MOOSE SRS +-- local msrs=MSRS:New("D:\\DCS\\_SRS\\", 305, radio.modulation.AM) +-- +-- -- Text-to speech with default voice after 2 seconds. +-- msrs:PlaySoundText(text, 2) +-- +-- ## Set Gender +-- +-- Use a specific gender by :SetGender("male") or :SetGender("female"). +-- +-- ## Set Culture +-- +-- Use a specific "culture" by :SetCulture("en-US") or :SetGender("de-DE"). +-- +-- ## Set Voice +-- +-- Use a specifc voice by :SetVoice("Microsoft Hedda Desktop"). Note that this must be installed on your windows system. -- -- @field #MSRS MSRS = { @@ -94,6 +125,7 @@ MSRS.version="0.0.3" -- TODO: Add functions to add/remove freqs and modulations. -- TODO: Add coordinate. +-- TODO: Add google. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor From bb43b08190a3d63bbe1f8720e0b6399f3929b2d0 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 1 Jun 2021 23:20:38 +0200 Subject: [PATCH 273/382] Sound update --- Moose Development/Moose/Sound/SRS.lua | 6 +++- Moose Development/Moose/Sound/SoundOutput.lua | 31 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 04eaa762d..40732802f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -86,7 +86,7 @@ -- -- ## Set Culture -- --- Use a specific "culture" by :SetCulture("en-US") or :SetGender("de-DE"). +-- Use a specific "culture" by :SetCulture("en-US") or :SetCulture("de-DE"). -- -- ## Set Voice -- @@ -479,6 +479,10 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp speed=speed or self.speed port=port or self.port + -- Replace modulation + modus=modus:gsub("0", "AM") + modus=modus:gsub("1", "FM") + -- This did not work well. Stopped if the transmission was a bit longer with no apparent error. --local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index bb974af08..bbe93cfa6 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -211,15 +211,22 @@ do -- Text-To-Speech -- -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. -- - -- Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) - -- Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) - -- Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) - -- Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German - -- Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) - -- Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French - -- Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) - -- Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian - -- Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) + -- + -- # Specific Voice + -- + -- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples + -- + -- * Name: Microsoft Hazel Desktop, Culture: en-GB, Gender: Female, Age: Adult, Desc: Microsoft Hazel Desktop - English (Great Britain) + -- * Name: Microsoft David Desktop, Culture: en-US, Gender: Male, Age: Adult, Desc: Microsoft David Desktop - English (United States) + -- * Name: Microsoft Zira Desktop, Culture: en-US, Gender: Female, Age: Adult, Desc: Microsoft Zira Desktop - English (United States) + -- * Name: Microsoft Hedda Desktop, Culture: de-DE, Gender: Female, Age: Adult, Desc: Microsoft Hedda Desktop - German + -- * Name: Microsoft Helena Desktop, Culture: es-ES, Gender: Female, Age: Adult, Desc: Microsoft Helena Desktop - Spanish (Spain) + -- * Name: Microsoft Hortense Desktop, Culture: fr-FR, Gender: Female, Age: Adult, Desc: Microsoft Hortense Desktop - French + -- * Name: Microsoft Elsa Desktop, Culture: it-IT, Gender: Female, Age: Adult, Desc: Microsoft Elsa Desktop - Italian (Italy) + -- * Name: Microsoft Irina Desktop, Culture: ru-RU, Gender: Female, Age: Adult, Desc: Microsoft Irina Desktop - Russian + -- * Name: Microsoft Huihui Desktop, Culture: zh-CN, Gender: Female, Age: Adult, Desc: Microsoft Huihui Desktop - Chinese (Simplified) + -- + -- Note that this must be installed on your windos machine. Also note that this overrides any culture and gender settings. -- -- @field #SOUNDTEXT SOUNDTEXT={ @@ -283,11 +290,11 @@ do -- Text-To-Speech --- Set to use a specific voice name. -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices -- @param #SOUNDTEXT self - -- @param #string Voice Voice name. Note that this will overrule `Gender` and `Culture`. + -- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self - function SOUNDTEXT:SetVoice(Voice) + function SOUNDTEXT:SetVoice(VoiceName) - self.voice=Voice + self.voice=VoiceName return self end From 5a00f461e96ce7db3fa60b56d2d7d3ba84b37551 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 4 Jun 2021 23:04:49 +0200 Subject: [PATCH 274/382] Sound update --- Moose Development/Moose/Ops/ATIS.lua | 10 +++++++++- Moose Development/Moose/Sound/SRS.lua | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 8f6d3045b..3799d974a 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -255,6 +255,13 @@ -- # Marks on the F10 Map -- -- You can place marks on the F10 map via the @{#ATIS.SetMapMarks}() function. These will contain info about the ATIS frequency, the currently active runway and some basic info about the weather (wind, pressure and temperature). +-- +-- # Text-To-Speech +-- +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSTTS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- Advantages are that no sound files are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. +-- +-- The @{#ATIS.SetSTTS}() requires you to specify the path to the SRS install directory. -- -- # Examples -- @@ -1109,8 +1116,9 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @param #string Culture Culture, e.g. "en-GB" (default). -- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @param #number Port SRS port. Default 5002. -- @return #ATIS self -function ATIS:SetSTTS(PathToSRS, Gender, Culture, Voice, Port) +function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.useSRS=true self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) self.msrs:SetGender(Gender) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 40732802f..fe3e13211 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -21,8 +21,7 @@ -- -- === -- --- Automatic terminal information service, or ATIS, is a continuous broadcast of recorded aeronautical information in busier terminal areas, *i.e.* airports and their immediate surroundings. --- ATIS broadcasts contain essential information, such as current weather information, active runways, and any other information required by the pilots. +-- The goal of the [SRS](https://github.com/ciribob/DCS-SimpleRadioStandalone) project is to bring VoIP communication into DCS and to make communication as frictionless as possible. -- -- === -- From cf83abfe90813d541094c9a8b4c6cd75526348c9 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Jun 2021 23:40:50 +0200 Subject: [PATCH 275/382] Sound update docs --- Moose Development/Moose/Sound/RadioQueue.lua | 6 +- Moose Development/Moose/Sound/SoundOutput.lua | 92 +++++++++++++++---- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 11cea9de8..fbe183fd6 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -4,7 +4,7 @@ -- -- ## Features: -- --- * Managed Radio Transmissions. +-- * Manage Radio Transmissions -- -- === -- @@ -15,6 +15,10 @@ --- Manages radio transmissions. -- +-- The main goal of the RADIOQUEUE class is to string together multiple sound files to play a complete sentence. +-- The underlying problem is that radio transmissions in DCS are not queued but played "on top" of each other. +-- Therefore, to achive the goal, it is vital to know the precise duration how long it takes to play the sound file. +-- -- @type RADIOQUEUE -- @field #string ClassName Name of the class "RADIOQUEUE". -- @field #boolean Debugmode Debug mode. More info. diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index bbe93cfa6..2efb9e015 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -5,7 +5,7 @@ -- ## Features: -- -- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions --- * Create a SOUNDTEXT object for text-to-speech output +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech -- -- === -- @@ -13,6 +13,12 @@ -- -- === -- +-- There are two classes, SOUNDFILE and SOUNDTEXT, defined in this section that deal with playing +-- sound files or arbitrary text (via SRS Simple-Text-To-Speech), respectively. +-- +-- The SOUNDFILE and SOUNDTEXT objects can be defined and used in other MOOSE classes. +-- +-- -- @module Sound.SoundOutput -- @image Sound_SoundOutput.png @@ -63,9 +69,39 @@ do -- Sound File --- Sound files used by other classes. -- - -- # 1. USERFLAG constructor + -- # The SOUNDFILE Concept -- - -- * @{#USERFLAG.New}(): Creates a new USERFLAG object. + -- A SOUNDFILE object hold the important properties that are necessary to play the sound file, e.g. its file name, path, duration. + -- + -- It can be created with the @{#SOUNDFILE.New}(*FileName*, *Path*, *Duration*) function: + -- + -- local soundfile=SOUNDFILE:New("My Soundfile.ogg", "Sound File/", 3.5) + -- + -- ## SRS + -- + -- If sound files are supposed to be played via SRS, you need to use the @{#SOUNDFILE.SetPlayWithSRS}() function. + -- + -- # Location/Path + -- + -- ## DCS + -- + -- DCS can only play sound files that are located inside the mission (.miz) file. In particular, DCS cannot make use of files that are stored on + -- your hard drive. + -- + -- The default location where sound files are stored in DCS is the directory "l10n/DEFAULT/". This is where sound files are placed, if they are + -- added via the mission editor (TRIGGERS-->ACTIONS-->SOUND TO ALL). Note however, that sound files which are not added with a trigger command, + -- will be deleted each time the mission is saved! Therefore, this directory is not ideal to be used especially if many sound files are to + -- be included since for each file a trigger action needs to be created. Which is cumbersome, to say the least. + -- + -- The recommended way is to create a new folder inside the mission (.miz) file (a miz file is essentially zip file and can be opened, e.g., with 7-Zip) + -- and to place the sound files in there. Sound files in these folders are not wiped out by DCS on the next save. + -- + -- ## SRS + -- + -- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path. + -- + -- + -- ## SRS -- -- @field #SOUNDFILE SOUNDFILE={ @@ -178,11 +214,11 @@ do -- Sound File return name end - --- Get the complete sound file name inlcuding its path. + --- Set whether sound files should be played via SRS. -- @param #SOUNDFILE self -- @param #boolean Switch If true or nil, use SRS. If false, use DCS transmission. -- @return #SOUNDFILE self - function SOUNDFILE:UseSRS(Switch) + function SOUNDFILE:SetPlayWithSRS(Switch) if Switch==true or Switch==nil then self.useSRS=true else @@ -207,12 +243,30 @@ do -- Text-To-Speech --- Text-to-speech objects for other classes. -- - -- # Constructor + -- # The SOUNDTEXT Concept + -- + -- A SOUNDTEXT object holds all necessary information to play a general text via SRS Simple-Text-To-Speech. + -- + -- It can be created with the @{#SOUNDTEXT.New}(*Text*, *Duration*) function. -- -- * @{#SOUNDTEXT.New}(*Text, Duration*): Creates a new SOUNDTEXT object. -- + -- # Options + -- + -- ## Gender + -- + -- You can choose a gender ("male" or "femal") with the @{#SOUNDTEXT.SetGender}(*Gender*) function. + -- Note that the gender voice needs to be installed on your windows machine for the used culture (see below). + -- + -- ## Culture + -- + -- You can choose a "culture" (accent) with the @{#SOUNDTEXT.SetCulture}(*Culture*) function, where the default (SRS) culture is "en-GB". + -- + -- Other examples for culture are: "en-US" (US accent), "de-DE" (German), "it-IT" (Italian), "ru-RU" (Russian), "zh-CN" (Chinese). + -- + -- Note that the chosen culture needs to be installed on your windows machine. -- - -- # Specific Voice + -- ## Specific Voice -- -- You can use a specific voice for the transmission with the @{SOUNDTEXT.SetVoice}(*VoiceName*) function. Here are some examples -- @@ -287,8 +341,19 @@ do -- Text-To-Speech return self end + --- Set TTS culture - local for the voice. + -- @param #SOUNDTEXT self + -- @param #string Culture TTS culture. Default "en-GB". + -- @return #SOUNDTEXT self + function SOUNDTEXT:SetCulture(Culture) + + self.culture=Culture or "en-GB" + + return self + end + --- Set to use a specific voice name. - -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see https://cloud.google.com/text-to-speech/docs/voices + -- See the list from `DCS-SR-ExternalAudio.exe --help` or if using google see [google voices](https://cloud.google.com/text-to-speech/docs/voices). -- @param #SOUNDTEXT self -- @param #string VoiceName Voice name. Note that this will overrule `Gender` and `Culture`. -- @return #SOUNDTEXT self @@ -299,15 +364,4 @@ do -- Text-To-Speech return self end - --- Set TTS culture - local for the voice. - -- @param #SOUNDTEXT self - -- @param #string Culture TTS culture. Default "en-GB". - -- @return #SOUNDTEXT self - function SOUNDTEXT:SetCulture(Culture) - - self.culture=Culture or "en-GB" - - return self - end - end \ No newline at end of file From e03e87f501f7aebcbee7ace099c0884fe1503383 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Jun 2021 23:44:25 +0200 Subject: [PATCH 276/382] Sound update docs --- Moose Development/Moose/Sound/SRS.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index fe3e13211..6260c62ca 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -81,15 +81,16 @@ -- -- ## Set Gender -- --- Use a specific gender by :SetGender("male") or :SetGender("female"). +-- Use a specific gender with the @{#MSRS.SetGender} function, e.g. `SetGender("male")` or `:SetGender("female")`. -- -- ## Set Culture -- --- Use a specific "culture" by :SetCulture("en-US") or :SetCulture("de-DE"). +-- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`. -- -- ## Set Voice -- --- Use a specifc voice by :SetVoice("Microsoft Hedda Desktop"). Note that this must be installed on your windows system. +-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. +-- Note that this must be installed on your windows system. -- -- @field #MSRS MSRS = { From 06c3f7998b465ac46f3b69beac8a9b79aae2273a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 15:09:30 +0200 Subject: [PATCH 277/382] Added function for message duration (#1542) ... and correct flash status setting --- .../Moose/Tasking/CommandCenter.lua | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 0673facb1..2bcfe972c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAcceptTasks( true ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetFlashStatus( false ) + self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -682,7 +683,7 @@ end -- @param #string Message The message text. function COMMANDCENTER:MessageToAll( Message ) - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) + self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) end @@ -692,7 +693,7 @@ end -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) + self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) end @@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) + self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) end @@ -795,9 +796,18 @@ end --- Let the command center flash a report of the status of the subscribed task to a group. -- @param #COMMANDCENTER self +-- @param Flash #boolean function COMMANDCENTER:SetFlashStatus( Flash ) self:F() - self.FlashStatus = Flash or true - + self.FlashStatus = Flash and true +end + +--- Duration a command center message is shown. +-- @param #COMMANDCENTER self +-- @param seconds #number +function COMMANDCENTER:SetMessageDuration(seconds) + self:F() + + self.MessageDuration = 10 or seconds end From 82d78c98bba379ce491da3e28744c38cfb9f84e9 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 15:16:04 +0200 Subject: [PATCH 278/382] Update Spawn.lua (#1544) --- Moose Development/Moose/Core/Spawn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89d6d681c..c865f5dbc 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3406,10 +3406,10 @@ function SPAWN:_SpawnCleanUpScheduler() self:T( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then - if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then local NewVec2 = SpawnUnit:GetVec2() if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... + -- If the plane is not moving or dead, and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) From 858b00336bdb744de5f20420b94d1d75e0279bf1 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 18:07:14 +0200 Subject: [PATCH 279/382] Update Spawn.lua --- Moose Development/Moose/Core/Spawn.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index c865f5dbc..d25186b66 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3406,10 +3406,10 @@ function SPAWN:_SpawnCleanUpScheduler() self:T( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then - if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving or dead, and is on the ground, assign it with a timestamp... + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) @@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler() else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then + if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end else From af9324dd5fda66f70b1c297511b734575a9b264c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 18:26:43 +0200 Subject: [PATCH 280/382] master updates to develop for InitCleanup() and CC Messages (#1545) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua --- Moose Development/Moose/Core/Spawn.lua | 6 +++--- .../Moose/Tasking/CommandCenter.lua | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89d6d681c..d25186b66 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3408,8 +3408,8 @@ function SPAWN:_SpawnCleanUpScheduler() if Stamp.Vec2 then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) @@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler() else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then + if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end else diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 0673facb1..2bcfe972c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAcceptTasks( true ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetFlashStatus( false ) + self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -682,7 +683,7 @@ end -- @param #string Message The message text. function COMMANDCENTER:MessageToAll( Message ) - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) + self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) end @@ -692,7 +693,7 @@ end -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) + self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) end @@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) + self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) end @@ -795,9 +796,18 @@ end --- Let the command center flash a report of the status of the subscribed task to a group. -- @param #COMMANDCENTER self +-- @param Flash #boolean function COMMANDCENTER:SetFlashStatus( Flash ) self:F() - self.FlashStatus = Flash or true - + self.FlashStatus = Flash and true +end + +--- Duration a command center message is shown. +-- @param #COMMANDCENTER self +-- @param seconds #number +function COMMANDCENTER:SetMessageDuration(seconds) + self:F() + + self.MessageDuration = 10 or seconds end From 65ed8825b0ef5426f14e2a1f40b430e32a9835ed Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 8 Jun 2021 15:59:36 +0200 Subject: [PATCH 281/382] Update Intelligence.lua Added functionality to calculate the position of a cluster after x seconds, based on trajectory (average speed and heading) of a cluster * INTEL:CalcClusterFuturePosition(cluster,seconds) Will also draw arrows on the map if `self.clustermarkers` is true and `self.verbose > 1` Change cluster coordinate updates to better suite cluster movement --- Moose Development/Moose/Ops/Intelligence.lua | 96 +++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 9c4a3a876..7ee0a79fd 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -35,6 +35,7 @@ -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. -- @field #number clusterradius Radius im kilometers in which groups/units are considered to belong to a cluster +-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @extends Core.Fsm#FSM --- Top Secret! @@ -95,6 +96,9 @@ INTEL = { Clusters = {}, clustercounter = 1, clusterradius = 15, + clusteranalysis = true, + clustermarkers = false, + prediction = 300, } --- Detected item info. @@ -131,7 +135,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.1" +INTEL.version="0.2.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -898,7 +902,7 @@ end -- Cluster Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Paint picture of the battle field. +--- [Internal] Paint picture of the battle field. Does Cluster analysis and updates clusters. Sets markers if markers are enabled. -- @param #INTEL self function INTEL:PaintPicture() @@ -918,9 +922,13 @@ function INTEL:PaintPicture() else local mission = _cluster.mission or nil local marker = _cluster.marker + local markerID = _cluster.markerID if marker then marker:Remove() end + if markerID then + COORDINATE:RemoveMark(markerID) + end self:LostCluster(_cluster, mission) end end @@ -989,12 +997,10 @@ function INTEL:PaintPicture() if self.clustermarkers then for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - - local coordinate=self:GetClusterCoordinate(cluster) - - - -- Update F10 marker. - self:UpdateClusterMarker(cluster) + --local coordinate=self:GetClusterCoordinate(cluster) + -- Update F10 marker. + self:UpdateClusterMarker(cluster) + self:CalcClusterFuturePosition(cluster,self.prediction) end end end @@ -1108,6 +1114,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster) local threatlevel=0 for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact if contact.threatlevel>threatlevel then @@ -1119,6 +1126,65 @@ function INTEL:CalcClusterThreatlevelMax(cluster) return threatlevel end +--- Calculate cluster heading. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Heading average of all groups in the cluster. +function INTEL:CalcClusterDirection(cluster) + + local direction = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + direction = direction + group:GetHeading() + n=n+1 + end + end + return math.floor(direction / n) + +end + +--- Calculate cluster speed. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Speed average of all groups in the cluster in MPS. +function INTEL:CalcClusterSpeed(cluster) + + local velocity = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + velocity = velocity + group:GetVelocityMPS() + n=n+1 + end + end + return math.floor(velocity / n) + +end + +--- Calculate cluster future position after given seconds. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @param #number seconds Timeframe in seconds. +-- @return Core.Point#COORDINATE Calculated future position of the cluster. +function INTEL:CalcClusterFuturePosition(cluster,seconds) + local speed = self:CalcClusterSpeed(cluster) -- #number MPS + local direction = self:CalcClusterDirection(cluster) -- #number heading + -- local currposition = cluster.coordinate -- Core.Point#COORDINATE + local currposition = self:GetClusterCoordinate(cluster) -- Core.Point#COORDINATE + local distance = speed * seconds -- #number in meters the cluster will travel + local futureposition = currposition:Translate(distance,direction,true,false) + if self.clustermarkers and (self.verbose > 1) then + if cluster.markerID then + COORDINATE:RemoveMark(cluster.markerID) + end + cluster.markerID = currposition:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Postion Calc") + end + return futureposition +end + --- Check if contact is in any known cluster. -- @param #INTEL self @@ -1216,10 +1282,16 @@ function INTEL:GetClusterCoordinate(cluster) for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - - x=x+contact.position.x - y=y+contact.position.y - z=z+contact.position.z + local group = contact.group --Wrapper.Group#GROUP + local coord = {} + if group:IsAlive() then + coord = group:GetCoordinate() + else + coord = contact.position + end + x=x+coord.x + y=y+coord.y + z=z+coord.z n=n+1 end From 4fa525a4aedf419885cb9d0d2646dad39178fa27 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Jun 2021 22:06:33 +0200 Subject: [PATCH 282/382] Update Airbase.lua --- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 220e3a254..55f5cc632 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1391,7 +1391,8 @@ function AIRBASE:GetRunwayData(magvar, mark) name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or - name==AIRBASE.PersianGulf.Kish_International_Airport then + name==AIRBASE.PersianGulf.Kish_International_Airport or + name==AIRBASE.MarianaIslands.Andersen then -- 1-->4, 2-->3, 3-->2, 4-->1 exception=1 From 0c9390914ae491680c9b9d602b7419f9f333aef8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Jun 2021 23:51:03 +0200 Subject: [PATCH 283/382] Update SRS.lua --- Moose Development/Moose/Sound/SRS.lua | 51 +++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 6260c62ca..d4e4ad2f7 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -319,11 +319,19 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command.." --file="..tostring(soundfile) + self:_ExectCommand(command) + + --[[ + + command=command.." > bla.txt" + -- Debug output. self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. local x=os.execute(command) + + ]] end @@ -347,11 +355,17 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) + self:_ExectCommand(command) + + --[[ + command=command.." > bla.txt" + -- Debug putput. self:I(string.format("MSRS PlaySoundfile command=%s", command)) -- Execute SRS command. local x=os.execute(command) + ]] end @@ -375,6 +389,10 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) + self:_ExectCommand(command) + + --[[ + -- Check that length of command is max 255 chars or os.execute() will not work! if string.len(command)>255 then @@ -403,7 +421,7 @@ function MSRS:PlayText(Text, Delay) end - + ]] end return self @@ -441,7 +459,8 @@ function MSRS:PlayTextFile(TextFile, Delay) local l=string.len(command) -- Execute SRS command. - local x=os.execute(command) + self:_ExectCommand(command) +-- local x=os.execute(command) end @@ -453,6 +472,32 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. +-- @param #MSRS self +-- @param #string command Command to executer +-- @return #string Command. +function MSRS:_ExectCommand(command) + + -- Create a tmp file. + local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + + local script = io.open(filename, "w+") + script:write(command.." && exit") + script:close() + + -- Play command. + command=string.format('start /b "" "\"%s\"', filename) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + + return res +end + + --- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self -- @param #table freqs Frequencies in MHz. @@ -488,6 +533,8 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp -- Command from orig STTS script. Works better for some unknown reason! local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") + + --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then From 8a44fae3d4367cf7b9af3b50dcbd34d0e84839a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 9 Jun 2021 13:01:48 +0200 Subject: [PATCH 284/382] Update SRS.lua - VBS script --- Moose Development/Moose/Sound/SRS.lua | 67 ++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index d4e4ad2f7..069ee3d34 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -319,7 +319,7 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command.." --file="..tostring(soundfile) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ @@ -355,7 +355,7 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ command=command.." > bla.txt" @@ -389,7 +389,7 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) - self:_ExectCommand(command) + self:_ExecCommand(command) --[[ @@ -459,7 +459,7 @@ function MSRS:PlayTextFile(TextFile, Delay) local l=string.len(command) -- Execute SRS command. - self:_ExectCommand(command) + self:_ExecCommand(command) -- local x=os.execute(command) end @@ -476,7 +476,7 @@ end -- @param #MSRS self -- @param #string command Command to executer -- @return #string Command. -function MSRS:_ExectCommand(command) +function MSRS:_ExecCommand(command) -- Create a tmp file. local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" @@ -484,15 +484,56 @@ function MSRS:_ExectCommand(command) local script = io.open(filename, "w+") script:write(command.." && exit") script:close() - + -- Play command. - command=string.format('start /b "" "\"%s\"', filename) - - -- Play file in 0.05 seconds - timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + command=string.format('start /b "" "%s"', filename) + + if true then + + -- Create a tmp file. + local filenvbs = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".vbs" + + -- VBS script + local script = io.open(filenvbs, "w+") + script:write(string.format('Dim WinScriptHost\n')) + script:write(string.format('Set WinScriptHost = CreateObject("WScript.Shell")\n')) + script:write(string.format('WinScriptHost.Run Chr(34) & "%s" & Chr(34), 0\n', filename)) + script:write(string.format('Set WinScriptHost = Nothing')) + script:close() + + -- Run visual basic script. This still pops up a window but very briefly and does not put the DCS window out of focus. + local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) + + -- Debug output. + self:I("MSRS execute command="..command) + self:I("MSRS execute VBS command="..runvbs) + + -- Now create powershell process and feed your script to its stdin + --local pipe = io.popen("cscript.exe //Nologo //B", "w") + --pipe:write(script) + --pipe:close() + + -- Play file in 0.01 seconds + os.execute(runvbs) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1) + + + else + + -- Debug output. + self:I("MSRS execute command="..command) + + -- Play file in 0.05 seconds + timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) + + end - -- Remove file in 1 second. - timer.scheduleFunction(os.remove, filename, timer.getTime()+1) return res end @@ -535,6 +576,8 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT") --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") + + local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. if voice then From e7936950f43bba97c3f0cdc7108948e410a58846 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 00:18:49 +0200 Subject: [PATCH 285/382] ATIS --- Moose Development/Moose/Ops/ATIS.lua | 67 +++++++++++++++---- Moose Development/Moose/Sound/RadioQueue.lua | 2 +- Moose Development/Moose/Sound/RadioSpeech.lua | 34 +++++----- Moose Development/Moose/Sound/SRS.lua | 7 +- Moose Development/Moose/Sound/SoundOutput.lua | 3 - Moose Development/Moose/Utilities/Utils.lua | 5 +- 6 files changed, 77 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 3799d974a..310d42a9d 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -35,7 +35,7 @@ -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord #ops-atis channel. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -91,6 +91,7 @@ -- @field #number relHumidity Relative humidity (used to approximately calculate the dew point). -- @field #boolean useSRS If true, use SRS for transmission. -- @field Sound.SRS#MSRS msrs Moose SRS object. +-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used. -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -258,10 +259,13 @@ -- -- # Text-To-Speech -- --- You can enable text-to-speech ATIS information with the @{#ATIS.SetSTTS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. --- Advantages are that no sound files are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. +-- You can enable text-to-speech ATIS information with the @{#ATIS.SetSRS}() function. This uses [SRS](http://dcssimpleradio.com/) (Version >= 1.9.6.0) for broadcasing. +-- Advantages are that **no sound files** or radio relay units are necessary. Also the issue that FC3 aircraft hear all transmissions will be circumvented. -- --- The @{#ATIS.SetSTTS}() requires you to specify the path to the SRS install directory. +-- The @{#ATIS.SetSRS}() requires you to specify the path to the SRS install directory or more specifically the path to the DCS-SR-ExternalAudio.exe file. +-- +-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before +-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds. -- -- # Examples -- @@ -293,7 +297,14 @@ -- atisAbuDhabi:SetTowerFrequencies({250.5, 119.2}) -- atisAbuDhabi:SetVOR(114.25) -- atisAbuDhabi:Start() +-- +-- ## SRS +-- +-- atis=ATIS:New("Batumi", 305, radio.modulation.AM) +-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") +-- atis:Start() -- +-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux). -- -- @field #ATIS ATIS = { @@ -378,6 +389,7 @@ ATIS.Alphabet = { -- @field #number PersianGulf +2° (East). -- @field #number TheChannel -10° (West). -- @field #number Syria +5° (East). +-- @field #number MarianaIslands +2° (East). ATIS.RunwayM2T={ Caucasus=0, Nevada=12, @@ -385,6 +397,7 @@ ATIS.RunwayM2T={ PersianGulf=2, TheChannel=-10, Syria=5, + MarianaIslands=2, } --- Whether ICAO phraseology is used for ATIS broadcasts. @@ -395,6 +408,7 @@ ATIS.RunwayM2T={ -- @field #boolean PersianGulf true. -- @field #boolean TheChannel true. -- @field #boolean Syria true. +-- @field #boolean MarianaIslands true. ATIS.ICAOPhraseology={ Caucasus=true, Nevada=false, @@ -402,6 +416,7 @@ ATIS.ICAOPhraseology={ PersianGulf=true, TheChannel=true, Syria=true, + MarianaIslands=true, } --- Nav point data. @@ -574,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.1" +ATIS.version="0.9.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -638,6 +653,7 @@ function ATIS:New(airbasename, frequency, modulation) self:SetAltimeterQNH(true) self:SetMapMarks(false) self:SetRelativeHumidity() + self:SetQueueUpdateTime() -- Start State. self:SetStartState("Stopped") @@ -965,7 +981,9 @@ end -- * 170° on the Normany map -- * 182° on the Persian Gulf map -- --- Likewise, to convert *magnetic* into *true* heading, one has to substract easterly and add westerly variation. +-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. +-- +-- Or you make your life simple and just include the sign so you don't have to bother about East/West. -- -- @param #ATIS self -- @param #number magvar Magnetic variation in degrees. Positive for easterly and negative for westerly variation. Default is magnatic declinaton of the used map, c.f. @{Utilities.UTils#UTILS.GetMagneticDeclination}. @@ -1125,9 +1143,20 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) self.msrs:SetPort(Port) + if self.dTQueueCheck<=10 then + self:SetQueueUpdateTime(90) + end return self end +--- Set the time interval between radio queue updates. +-- @param #ATIS self +-- @param #number TimeInterval Interval in seconds. Default 5 sec. +-- @return #ATIS self +function ATIS:SetQueueUpdateTime(TimeInterval) + self.dTQueueCheck=TimeInterval or 5 +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1199,7 +1228,13 @@ function ATIS:onafterStatus(From, Event, To) end -- Info text. - local text=string.format("State %s: Freq=%.3f MHz %s, Relay unit=%s (alive=%s)", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation), tostring(self.relayunitname), relayunitstatus) + local text=string.format("State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName(self.modulation)) + if self.useSRS then + text=text..string.format(", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", + tostring(self.msrs.path), tostring(self.msrs.port), tostring(self.msrs.gender), tostring(self.msrs.culture), tostring(self.msrs.voice)) + else + text=text..string.format(", Relay unit=%s (alive=%s)", tostring(self.relayunitname), relayunitstatus) + end self:I(self.lid..text) self:__Status(-60) @@ -1220,8 +1255,6 @@ function ATIS:onafterCheckQueue(From, Event, To) self:Broadcast() - self:__CheckQueue(-120) - else if #self.radioqueue.queue==0 then @@ -1231,10 +1264,12 @@ function ATIS:onafterCheckQueue(From, Event, To) self:T2(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) end - -- Check back in 5 seconds. - self:__CheckQueue(-5) + end + + -- Check back in 5 seconds. + self:__CheckQueue(-math.abs(self.dTQueueCheck)) end --- Broadcast ATIS radio message. @@ -1778,10 +1813,14 @@ function ATIS:onafterBroadcast(From, Event, To) end if CLOUDBASE and static then -- Base + local cbase=tostring(tonumber(CLOUDBASE1000)*1000+tonumber(CLOUDBASE0100)*100) + local cceil=tostring(tonumber(CLOUDCEIL1000)*1000+tonumber(CLOUDCEIL0100)*100) if self.metric then - subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + --subtitle=string.format("Cloud base %s, ceiling %s meters", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s meters", cbase, cceil) else - subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + --subtitle=string.format("Cloud base %s, ceiling %s feet", CLOUDBASE, CLOUDCEIL) + subtitle=string.format("Cloud base %s, ceiling %s feet", cbase, cceil) end if not self.useSRS then self:Transmission(ATIS.Sound.CloudBase, 1.0, subtitle) @@ -2195,7 +2234,7 @@ function ATIS:onafterReport(From, Event, To, Text) local text=string.gsub(text, "m/s", "meters per second") -- Replace ";" by "." - local text=string.gsub(text, ";", ". ") + local text=string.gsub(text, ";", " . ") env.info("FF: "..text) -- Play text-to-speech report. diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index fbe183fd6..80ac49752 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -11,7 +11,7 @@ -- ### Authors: funkyfranky -- -- @module Sound.RadioQueue --- @image Sound_Radio.JPG +-- @image Core_Radio.JPG --- Manages radio transmissions. -- diff --git a/Moose Development/Moose/Sound/RadioSpeech.lua b/Moose Development/Moose/Sound/RadioSpeech.lua index d4cf22af1..f77c446a2 100644 --- a/Moose Development/Moose/Sound/RadioSpeech.lua +++ b/Moose Development/Moose/Sound/RadioSpeech.lua @@ -11,7 +11,7 @@ -- -- ### Authors: FlightControl -- --- @module Core.RadioSpeech +-- @module Sound.RadioSpeech -- @image Core_Radio.JPG --- Makes the radio speak. @@ -162,31 +162,31 @@ RADIOSPEECH.Vocabulary.RU = { ["8000"] = { "8000", 0.92 }, ["9000"] = { "9000", 0.87 }, - ["степени"] = { "degrees", 0.5 }, - ["километров"] = { "kilometers", 0.65 }, + ["Ñ�тепени"] = { "degrees", 0.5 }, + ["километров"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 }, - ["миль"] = { "miles", 0.45 }, + ["миль"] = { "miles", 0.45 }, ["mi"] = { "miles", 0.45 }, - ["метры"] = { "meters", 0.41 }, + ["метры"] = { "meters", 0.41 }, ["m"] = { "meters", 0.41 }, - ["ноги"] = { "feet", 0.37 }, + ["ноги"] = { "feet", 0.37 }, ["br"] = { "br", 1.1 }, ["bra"] = { "bra", 0.3 }, - ["возвращаясь на базу"] = { "returning_to_base", 1.40 }, - ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, - ["перехват самолетов"] = { "intercepting_bogeys", 1.22 }, - ["поражение наземной цели"] = { "engaging_ground_target", 1.53 }, - ["захватывающие самолеты"] = { "engaging_bogeys", 1.68 }, - ["колеса вверх"] = { "wheels_up", 0.92 }, - ["посадка на базу"] = { "landing at base", 1.04 }, - ["патрулирующий"] = { "patrolling", 0.96 }, + ["возвращаÑ�Ñ�ÑŒ на базу"] = { "returning_to_base", 1.40 }, + ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, + ["перехват Ñ�амолетов"] = { "intercepting_bogeys", 1.22 }, + ["поражение наземной цели"] = { "engaging_ground_target", 1.53 }, + ["захватывающие Ñ�амолеты"] = { "engaging_bogeys", 1.68 }, + ["колеÑ�а вверх"] = { "wheels_up", 0.92 }, + ["поÑ�адка на базу"] = { "landing at base", 1.04 }, + ["патрулирующий"] = { "patrolling", 0.96 }, - ["за"] = { "for", 0.27 }, - ["и"] = { "and", 0.17 }, - ["в"] = { "at", 0.19 }, + ["за"] = { "for", 0.27 }, + ["и"] = { "and", 0.17 }, + ["в"] = { "at", 0.19 }, ["dot"] = { "dot", 0.51 }, ["defender"] = { "defender", 0.45 }, } diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 069ee3d34..44c5a4c3f 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -26,8 +26,8 @@ -- === -- -- ### Author: **funkyfranky** --- @module Addons.SRS --- @image Addons_SRS.png +-- @module Sound.MSRS +-- @image Sound_MSRS.png --- MSRS class. -- @type MSRS @@ -112,9 +112,6 @@ MSRS = { altitude = nil, } ---- Counter. -_MSRSuuid=0 - --- MSRS class version. -- @field #string version MSRS.version="0.0.3" diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 2efb9e015..af0e0675b 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -100,9 +100,6 @@ do -- Sound File -- -- SRS sound files need to be located on your local drive (not inside the miz). Therefore, you need to specify the full path. -- - -- - -- ## SRS - -- -- @field #SOUNDFILE SOUNDFILE={ ClassName = "SOUNDFILE", diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index af7642406..52eb6ada7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1182,6 +1182,9 @@ end -- * NTTR +12 (East), year ~ 2011 -- * Normandy -10 (West), year ~ 1944 -- * Persian Gulf +2 (East), year ~ 2011 +-- * The Cannel Map -10 (West) +-- * Syria +5 (East) +-- * Mariana Islands +2 (East) -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @return #number Declination in degrees. function UTILS.GetMagneticDeclination(map) @@ -1203,7 +1206,7 @@ function UTILS.GetMagneticDeclination(map) elseif map==DCSMAP.Syria then declination=5 elseif map==DCSMAP.MarianaIslands then - declination=-2 + declination=2 else declination=0 end From 832d6b1c083bff4f287dfd62f536b81f3cbf8eef Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:20:01 +0200 Subject: [PATCH 286/382] Update Group.lua (#1546) Added invisible and immortal commands on GROUP level. --- Moose Development/Moose/Wrapper/Group.lua | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 263b3a0ec..a9c678ab5 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch) return self end +--- Switch on/off invisible flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandInvisible(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetInvisible', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + +--- Switch on/off immortal flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandImmortal(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetImmortal', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From c8d2a7e833368e3434c5e47908920ae86e048820 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:59:50 +0200 Subject: [PATCH 287/382] Added commands for immortal and invisible to Wrapper.Group#GROUP (#1547) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua * Update Group.lua (#1546) Added invisible and immortal commands on GROUP level. --- Moose Development/Moose/Wrapper/Group.lua | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 263b3a0ec..a9c678ab5 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch) return self end +--- Switch on/off invisible flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandInvisible(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetInvisible', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + +--- Switch on/off immortal flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandImmortal(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetImmortal', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From 3e8db6a1fad274c1ad59bb8a6b23e5729d6c0d04 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 23:23:16 +0200 Subject: [PATCH 288/382] ATIS Improved time TTS format --- Moose Development/Moose/Ops/ATIS.lua | 12 ++++- Moose Development/Moose/Sound/SRS.lua | 46 ++++++++++++------- Moose Development/Moose/Sound/SoundOutput.lua | 4 +- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 310d42a9d..92683b48e 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1395,11 +1395,14 @@ function ATIS:onafterBroadcast(From, Event, To) if time < 0 then time = 24*60*60 + time --avoid negative time around midnight - end + end local clock=UTILS.SecondsToClock(time) local zulu=UTILS.Split(clock, ":") local ZULU=string.format("%s%s", zulu[1], zulu[2]) + if self.useSRS then + ZULU=string.format("%s hours", zulu[1]) + end -- NATO time stamp. 0=Alfa, 1=Bravo, 2=Charlie, etc. @@ -1419,10 +1422,17 @@ function ATIS:onafterBroadcast(From, Event, To) local sunrise=coord:GetSunrise() sunrise=UTILS.Split(sunrise, ":") local SUNRISE=string.format("%s%s", sunrise[1], sunrise[2]) + if self.useSRS then + SUNRISE=string.format("%s %s hours", sunrise[1], sunrise[2]) + end local sunset=coord:GetSunset() sunset=UTILS.Split(sunset, ":") local SUNSET=string.format("%s%s", sunset[1], sunset[2]) + if self.useSRS then + SUNSET=string.format("%s %s hours", sunset[1], sunset[2]) + end + --------------------------------- --- Temperature and Dew Point --- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 44c5a4c3f..04b785b6c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -158,7 +158,7 @@ end --- Set path to SRS install directory. More precisely, path to where the DCS- -- @param #MSRS self --- @param #string Path Path to the directory, where the sound file is located. +-- @param #string Path Path to the directory, where the sound file is located. This does **not** contain a final backslash or slash. -- @return #MSRS self function MSRS:SetPath(Path) @@ -177,9 +177,8 @@ function MSRS:SetPath(Path) n=n+1 end - self.path=self.path --.."/" - - self:I(string.format("SRS path=%s", self:GetPath())) + -- Debug output. + self:T(string.format("SRS path=%s", self:GetPath())) return self end @@ -258,13 +257,13 @@ end -- @param #string Gender Gender: "male" or "female" (default). -- @return #MSRS self function MSRS:SetGender(Gender) - self:I("Input gender to "..tostring(Gender)) Gender=Gender or "female" self.gender=Gender:lower() - self:I("Setting gender to "..tostring(self.gender)) + -- Debug output. + self:T("Setting gender to "..tostring(self.gender)) return self end @@ -291,6 +290,26 @@ function MSRS:SetVoice(Voice) return self end +--- Opens a new command window and prints the SRS STTS help. +-- @param #MSRS self +-- @return #MSRS self +function MSRS:Help() + local path=self:GetPath() or STTS.DIRECTORY + local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" + + local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt" + + local command=string.format("%s/%s --help > %s", path, exe, filename) + os.execute(command) + + local f=assert(io.open(filename, "rb")) + local data=f:read("*all") + f:close() + + env.info(data) + + return self +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Transmission Functions @@ -450,7 +469,7 @@ function MSRS:PlayTextFile(TextFile, Delay) command=command..string.format(" --textFile=\"%s\"", tostring(TextFile)) -- Debug output. - self:I(string.format("MSRS TextFile command=%s", command)) + self:T(string.format("MSRS TextFile command=%s", command)) -- Count length of command. local l=string.len(command) @@ -502,13 +521,8 @@ function MSRS:_ExecCommand(command) local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) -- Debug output. - self:I("MSRS execute command="..command) - self:I("MSRS execute VBS command="..runvbs) - - -- Now create powershell process and feed your script to its stdin - --local pipe = io.popen("cscript.exe //Nologo //B", "w") - --pipe:write(script) - --pipe:close() + self:T("MSRS execute command="..command) + self:T("MSRS execute VBS command="..runvbs) -- Play file in 0.01 seconds os.execute(runvbs) @@ -521,7 +535,7 @@ function MSRS:_ExecCommand(command) else -- Debug output. - self:I("MSRS execute command="..command) + self:T("MSRS execute command="..command) -- Play file in 0.05 seconds timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) @@ -592,7 +606,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end -- Debug output. - self:I("MSRS command="..command) + self:T("MSRS command="..command) return command end diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index af0e0675b..f581e9fe3 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -132,7 +132,7 @@ do -- Sound File self:SetDuration(Duration) -- Debug info: - self:I(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) + self:T(string.format("New SOUNDFILE: file name=%s, path=%s", self.filename, self.path)) return self end @@ -300,7 +300,7 @@ do -- Text-To-Speech --self:SetCulture() -- Debug info: - self:I(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) + self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration)) return self end From 6d61c5ee9443b7cedbfef73c3316131b3ce637a7 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Jun 2021 23:33:14 +0200 Subject: [PATCH 289/382] Update Utils.lua --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 52eb6ada7..d01707797 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1336,7 +1336,7 @@ function UTILS.GMTToLocalTimeDifference() elseif theatre==DCSMAP.Syria then return 3 -- Damascus is UTC+3 hours elseif theatre==DCSMAP.MarianaIslands then - return 2 -- Guam is UTC+10 hours but is +2 for now. + return 10 -- Guam is UTC+10 hours. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 From 973b8323c99683b60aefe9051d6a8219a63d1558 Mon Sep 17 00:00:00 2001 From: cammel tech <47948096+cammeltech@users.noreply.github.com> Date: Fri, 11 Jun 2021 13:38:25 +0200 Subject: [PATCH 290/382] Ratmanager documentation add :SetTspawn(dt) infite spawns - see discord func.rat discussion (#1548) Co-authored-by: wob3155@posteo.de --- Moose Development/Moose/Functional/RAT.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 327e2e518..691f53338 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -5786,6 +5786,8 @@ end -- If desired, the @{#RATMANAGER} can be stopped by the @{#RATMANAGER.Stop}(stoptime) function. The parameter "stoptime" specifies the time delay in seconds after which the manager stops. -- When this happens, no new aircraft will be spawned and the population will eventually decrease to zero. -- +-- When you are using a time intervall like @{#RATMANAGER.dTspawn}(delay), @{#RATMANAGER} will ignore the amount set with @{#RATMANAGER.New}(). @{#RATMANAGER.dTspawn}(delay) will spawn infinite groups. +-- -- ## Example -- In this example, three different @{#RAT} objects are created (but not spawned manually). The @{#RATMANAGER} takes care that at least five aircraft of each type are alive and that the total number of aircraft -- spawned is 25. The @{#RATMANAGER} is started after 30 seconds and stopped after two hours. From 078573629d168310d17805e76b48ed3251bbde97 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 11 Jun 2021 23:41:37 +0200 Subject: [PATCH 291/382] SRS - added coordinate - added google --- Moose Development/Moose/Functional/Range.lua | 6 +- Moose Development/Moose/Ops/ATIS.lua | 3 + Moose Development/Moose/Sound/SRS.lua | 89 +++++++++++++++---- Moose Development/Moose/Sound/SoundOutput.lua | 48 +++++++++- Moose Development/Moose/Wrapper/Airbase.lua | 83 +++++++++++------ 5 files changed, 180 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index fc850743c..7ba9dcfc7 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -36,7 +36,7 @@ -- -- === -- --- ## Sound files: Check out the pinned messages in the Moose discord *#func-range* channel. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -91,9 +91,9 @@ -- @field #boolean defaultsmokebomb If true, initialize player settings to smoke bomb. -- @field #boolean autosave If true, automatically save results every X seconds. -- @field #number instructorfreq Frequency on which the range control transmitts. --- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue. +-- @field Sound.RadioQueue#RADIOQUEUE instructor Instructor radio queue. -- @field #number rangecontrolfreq Frequency on which the range control transmitts. --- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. +-- @field Sound.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. -- @field #string rangecontrolrelayname Name of relay unit. -- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 92683b48e..65b0475e5 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2156,6 +2156,9 @@ function ATIS:onafterBroadcast(From, Event, To) -- VOR if self.vor then subtitle=string.format("VOR frequency %.2f MHz", self.vor) + if self.useSRS then + subtitle=string.format("V O R frequency %.2f MHz", self.vor) + end if not self.useSRS then self:Transmission(ATIS.Sound.VORFrequency, 1.0, subtitle) local f=string.format("%.2f", self.vor) diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 04b785b6c..d4253971c 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -17,7 +17,7 @@ -- -- === -- --- ## Sound files: None yet. +-- ## Sound files: [MOOSE Sound Files](https://github.com/FlightControl-Master/MOOSE_SOUND/releases) -- -- === -- @@ -44,6 +44,7 @@ -- @field #string voice Specifc voce. -- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send. -- @field #string path Path to the SRS exe. This includes the final slash "/". +-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -91,6 +92,10 @@ -- -- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`. -- Note that this must be installed on your windows system. +-- +-- ## Set Coordinate +-- +-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted. -- -- @field #MSRS MSRS = { @@ -107,9 +112,6 @@ MSRS = { volume = 1, speed = 1, coordinate = nil, - latitude = nil, - longitude = nil, - altitude = nil, } --- MSRS class version. @@ -121,8 +123,8 @@ MSRS.version="0.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add functions to add/remove freqs and modulations. --- TODO: Add coordinate. --- TODO: Add google. +-- DONE: Add coordinate. +-- DONE: Add google. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -290,15 +292,41 @@ function MSRS:SetVoice(Voice) return self end ---- Opens a new command window and prints the SRS STTS help. +--- Set the coordinate from which the transmissions will be broadcasted. +-- @param #MSRS self +-- @param Core.Point#COORDINATE Coordinate Origin of the transmission. +-- @return #MSRS self +function MSRS:SetCoordinate(Coordinate) + + self.coordinate=Coordinate + + return self +end + +--- Use google text-to-speech. +-- @param #MSRS self +-- @param PathToCredentials Full path to the google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json". +-- @return #MSRS self +function MSRS:SetGoogle(PathToCredentials) + + self.google=PathToCredentials + + return self +end + +--- Print SRS STTS help to DCS log file. -- @param #MSRS self -- @return #MSRS self function MSRS:Help() + + -- Path and exe. local path=self:GetPath() or STTS.DIRECTORY local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe" + -- Text file for output. local filename = os.getenv('TMP') .. "\\MSRS-help-"..STTS.uuid()..".txt" + -- Print help. local command=string.format("%s/%s --help > %s", path, exe, filename) os.execute(command) @@ -306,7 +334,11 @@ function MSRS:Help() local data=f:read("*all") f:close() + -- Print to log file. + env.info("SRS STTS help output:") + env.info("======================================================================") env.info(data) + env.info("======================================================================") return self end @@ -371,6 +403,7 @@ function MSRS:PlaySoundText(SoundText, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(SoundText.text)) + -- Execute command. self:_ExecCommand(command) --[[ @@ -405,6 +438,7 @@ function MSRS:PlayText(Text, Delay) -- Append text. command=command..string.format(" --text=\"%s\"", tostring(Text)) + -- Execute command. self:_ExecCommand(command) --[[ @@ -474,9 +508,8 @@ function MSRS:PlayTextFile(TextFile, Delay) -- Count length of command. local l=string.len(command) - -- Execute SRS command. + -- Execute command. self:_ExecCommand(command) --- local x=os.execute(command) end @@ -491,19 +524,20 @@ end --- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self -- @param #string command Command to executer --- @return #string Command. +-- @return #number Return value of os.execute() command. function MSRS:_ExecCommand(command) -- Create a tmp file. - local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat" + local filename=os.getenv('TMP').."\\MSRS-"..STTS.uuid()..".bat" - local script = io.open(filename, "w+") + local script=io.open(filename, "w+") script:write(command.." && exit") script:close() -- Play command. command=string.format('start /b "" "%s"', filename) + local res=nil if true then -- Create a tmp file. @@ -525,7 +559,7 @@ function MSRS:_ExecCommand(command) self:T("MSRS execute VBS command="..runvbs) -- Play file in 0.01 seconds - os.execute(runvbs) + res=os.execute(runvbs) -- Remove file in 1 second. timer.scheduleFunction(os.remove, filename, timer.getTime()+1) @@ -537,8 +571,8 @@ function MSRS:_ExecCommand(command) -- Debug output. self:T("MSRS execute command="..command) - -- Play file in 0.05 seconds - timer.scheduleFunction(os.execute, command, timer.getTime()+0.01) + -- Execute command + res=os.execute(command) -- Remove file in 1 second. timer.scheduleFunction(os.remove, filename, timer.getTime()+1) @@ -549,6 +583,19 @@ function MSRS:_ExecCommand(command) return res end +--- Get lat, long and alt from coordinate. +-- @param #MSRS self +-- @param Core.Point#Coordinate Coordinate Coordinate. Can also be a DCS#Vec3. +-- @return #number Latitude. +-- @return #number Longitude. +-- @return #number Altitude. +function MSRS:_GetLatLongAlt(Coordinate) + + local lat, lon, alt=coord.LOtoLL(Coordinate) + + return lat, lon, math.floor(alt) +end + --- Get SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. -- @param #MSRS self @@ -588,6 +635,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp --local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT") + -- Command. local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT") -- Set voice or gender/culture. @@ -605,6 +653,17 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp end end + -- Set coordinate. + if self.coordinate then + local lat,lon,alt=self:_GetLatLongAlt(self.coordinate) + command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) + end + + -- Set google. + if self.google then + command=command..string.format(' -G "%s"', self.google) + end + -- Debug output. self:T("MSRS command="..command) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index f581e9fe3..01fd00483 100644 --- a/Moose Development/Moose/Sound/SoundOutput.lua +++ b/Moose Development/Moose/Sound/SoundOutput.lua @@ -5,7 +5,7 @@ -- ## Features: -- -- * Create a SOUNDFILE object (mp3 or ogg) to be played via DCS or SRS transmissions --- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (STTS) -- -- === -- @@ -29,7 +29,7 @@ do -- Sound Base -- @extends Core.Base#BASE - --- Basic sound output inherited by other classes. + --- Basic sound output inherited by other classes suche as SOUNDFILE and SOUNDTEXT. -- -- This class is **not** meant to be used by "ordinary" users. -- @@ -51,6 +51,50 @@ do -- Sound Base return self end + --- Function returns estimated speech time in seconds. + -- Assumptions for time calc: 100 Words per min, avarage of 5 letters for english word so + -- + -- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second + -- + -- So lengh of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function: + -- + -- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min + -- + -- @param #string Text The text string to analyze. + -- @param #number Speed Speed factor. Default 1. + -- @param #boolean isGoogle If true, google text-to-speech is used. + function SOUNDBASE:GetSpeechTime(length,speed,isGoogle) + + local maxRateRatio = 3 + + speed = speed or 1.0 + isGoogle = isGoogle or false + + local speedFactor = 1.0 + if isGoogle then + speedFactor = speed + else + if speed ~= 0 then + speedFactor = math.abs(speed) * (maxRateRatio - 1) / 10 + 1 + end + if speed < 0 then + speedFactor = 1/speedFactor + end + end + + -- Words per minute. + local wpm = math.ceil(100 * speedFactor) + + -- Characters per second. + local cps = math.floor((wpm * 5)/60) + + if type(length) == "string" then + length = string.len(length) + end + + return math.ceil(length/cps) + end + end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 55f5cc632..615cf6870 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -336,41 +336,53 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Incirlik -- * AIRBASE.Syria.Damascus -- * AIRBASE.Syria.Bassel_Al_Assad +-- * AIRBASE.Syria.Rosh_Pina -- * AIRBASE.Syria.Aleppo --- * AIRBASE.Syria.Qabr_as_Sitt +-- * AIRBASE.Syria.Al_Qusayr -- * AIRBASE.Syria.Wujah_Al_Hajar -- * AIRBASE.Syria.Al_Dumayr +-- * AIRBASE.Syria.Gazipasa +-- * AIRBASE.Syria.Ru_Convoy_4 -- * AIRBASE.Syria.Hatay +-- * AIRBASE.Syria.Nicosia +-- * AIRBASE.Syria.Pinarbashi +-- * AIRBASE.Syria.Paphos +-- * AIRBASE.Syria.Kingsfield +-- * AIRBASE.Syria.Tha'lah -- * AIRBASE.Syria.Haifa -- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Megiddo +-- * AIRBASE.Syria.Lakatamia -- * AIRBASE.Syria.Rayak +-- * AIRBASE.Syria.Larnaca -- * AIRBASE.Syria.Mezzeh --- * AIRBASE.Syria.King_Hussein_Air_College --- * AIRBASE.Syria.Jirah +-- * AIRBASE.Syria.Gecitkale +-- * AIRBASE.Syria.Akrotiri +-- * AIRBASE.Syria.Naqoura +-- * AIRBASE.Syria.Gaziantep +-- * AIRBASE.Syria.CVN_71 +-- * AIRBASE.Syria.Sayqal +-- * AIRBASE.Syria.Tiyas +-- * AIRBASE.Syria.Shayrat -- * AIRBASE.Syria.Taftanaz +-- * AIRBASE.Syria.H4 +-- * AIRBASE.Syria.King_Hussein_Air_College -- * AIRBASE.Syria.Rene_Mouawad +-- * AIRBASE.Syria.Jirah -- * AIRBASE.Syria.Ramat_David +-- * AIRBASE.Syria.Qabr_as_Sitt -- * AIRBASE.Syria.Minakh -- * AIRBASE.Syria.Adana_Sakirpasa --- * AIRBASE.Syria.Marj_as_Sultan_South --- * AIRBASE.Syria.Hama --- * AIRBASE.Syria.Al_Qusayr -- * AIRBASE.Syria.Palmyra +-- * AIRBASE.Syria.Hama +-- * AIRBASE.Syria.Ercan +-- * AIRBASE.Syria.Marj_as_Sultan_South -- * AIRBASE.Syria.Tabqa -- * AIRBASE.Syria.Beirut_Rafic_Hariri -- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur --- * AIRBASE.Syria.H4 --- * AIRBASE.Syria.Gaziantep --- * AIRBASE.Syria.Rosh_Pina --- * AIRBASE.Syria.Sayqal --- * AIRBASE.Syria.Shayrat --- * AIRBASE.Syria.Tiyas --- * AIRBASE.Syria.Tha_lah --- * AIRBASE.Syria.Naqoura -- --- @field Syria +--@field Syria AIRBASE.Syria={ ["Kuweires"]="Kuweires", ["Marj_Ruhayyil"]="Marj Ruhayyil", @@ -380,42 +392,55 @@ AIRBASE.Syria={ ["Incirlik"]="Incirlik", ["Damascus"]="Damascus", ["Bassel_Al_Assad"]="Bassel Al-Assad", + ["Rosh_Pina"]="Rosh Pina", ["Aleppo"]="Aleppo", - ["Qabr_as_Sitt"]="Qabr as Sitt", + ["Al_Qusayr"]="Al Qusayr", ["Wujah_Al_Hajar"]="Wujah Al Hajar", ["Al_Dumayr"]="Al-Dumayr", + ["Gazipasa"]="Gazipasa", + ["Ru_Convoy_4"]="Ru Convoy-4", ["Hatay"]="Hatay", + ["Nicosia"]="Nicosia", + ["Pinarbashi"]="Pinarbashi", + ["Paphos"]="Paphos", + ["Kingsfield"]="Kingsfield", + ["Tha'lah"]="Tha'lah", ["Haifa"]="Haifa", ["Khalkhalah"]="Khalkhalah", ["Megiddo"]="Megiddo", + ["Lakatamia"]="Lakatamia", ["Rayak"]="Rayak", + ["Larnaca"]="Larnaca", ["Mezzeh"]="Mezzeh", - ["King_Hussein_Air_College"]="King Hussein Air College", - ["Jirah"]="Jirah", + ["Gecitkale"]="Gecitkale", + ["Akrotiri"]="Akrotiri", + ["Naqoura"]="Naqoura", + ["Gaziantep"]="Gaziantep", + ["CVN_71"]="CVN-71", + ["Sayqal"]="Sayqal", + ["Tiyas"]="Tiyas", + ["Shayrat"]="Shayrat", ["Taftanaz"]="Taftanaz", + ["H4"]="H4", + ["King_Hussein_Air_College"]="King Hussein Air College", ["Rene_Mouawad"]="Rene Mouawad", + ["Jirah"]="Jirah", ["Ramat_David"]="Ramat David", + ["Qabr_as_Sitt"]="Qabr as Sitt", ["Minakh"]="Minakh", ["Adana_Sakirpasa"]="Adana Sakirpasa", - ["Marj_as_Sultan_South"]="Marj as Sultan South", - ["Hama"]="Hama", - ["Al_Qusayr"]="Al Qusayr", ["Palmyra"]="Palmyra", + ["Hama"]="Hama", + ["Ercan"]="Ercan", + ["Marj_as_Sultan_South"]="Marj as Sultan South", ["Tabqa"]="Tabqa", ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", ["An_Nasiriyah"]="An Nasiriyah", ["Abu_al_Duhur"]="Abu al-Duhur", - ["H4"]="H4", - ["Gaziantep"]="Gaziantep", - ["Rosh_Pina"]="Rosh Pina", - ["Sayqal"]="Sayqal", - ["Shayrat"]="Shayrat", - ["Tiyas"]="Tiyas", - ["Tha_lah"]="Tha'lah", - ["Naqoura"]="Naqoura", } + --- Airbases of the Mariana Islands map. -- -- * AIRBASE.MarianaIslands.Rota_International_Airport From 246935622c23e5f909fb615a020ca292a02213cc Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 12 Jun 2021 21:54:42 +0200 Subject: [PATCH 292/382] Update ATIS.lua --- Moose Development/Moose/Ops/ATIS.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 65b0475e5..368d3c488 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -589,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.3" +ATIS.version="0.9.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2248,7 +2248,9 @@ function ATIS:onafterReport(From, Event, To, Text) -- Replace ";" by "." local text=string.gsub(text, ";", " . ") - env.info("FF: "..text) + + --Debug output. + self:T("SRS TTS: "..text) -- Play text-to-speech report. self.msrs:PlayText(text) From 117cf8888ac74b0c6e8e30323f17566def370696 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 13 Jun 2021 10:39:07 +0200 Subject: [PATCH 293/382] Update Globals.lua --- Moose Development/Moose/Globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 21d0c7eaa..f63f57e84 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -43,4 +43,4 @@ else end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end \ No newline at end of file +end From 8d8070bbd725d031ff842dc84204c259fa19f91f Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 13 Jun 2021 10:59:46 +0200 Subject: [PATCH 294/382] Update Globals.lua Added CR at the end. --- Moose Development/Moose/Globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 734f7c1df..df7fafe36 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -42,4 +42,4 @@ else end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end \ No newline at end of file +end From 0396741f3da4557d3da05c980c52828878802661 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:18:27 +0200 Subject: [PATCH 295/382] Create CSAR.lua Initial Release --- Moose Development/Moose/Ops/CSAR.lua | 1919 ++++++++++++++++++++++++++ 1 file changed, 1919 insertions(+) create mode 100644 Moose Development/Moose/Ops/CSAR.lua diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua new file mode 100644 index 000000000..9bc98340c --- /dev/null +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -0,0 +1,1919 @@ +--- **Ops** -- Combat Search and Rescue. +-- +-- === +-- +-- **CSAR** - MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CSAR +-- @image OPS_CSAR.jpg + +-- Date: June 2021 + +------------------------------------------------------------------------- +--- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +-- @type CSAR +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) +-- +-- === +-- +-- ![Banner Image](OPS_CSAR.jpg) +-- +-- # CSAR Concept +-- +-- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- +-- ## 1. Basic Setup +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" +-- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") +-- -- options +-- my_csar.immortalcrew = true -- downed pilot spawn is immortal +-- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- -- start the FSM +-- my_csar:__Start(5) +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults): +-- +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. +-- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +-- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.enableForAI = true -- set to false to disable AI units from being rescued. +-- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.immortalcrew = true -- Set to true to make wounded crew immortal. +-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. +-- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. +-- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ### 1. PilotDown. +-- +-- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: +-- +-- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) +-- ... your code here ... +-- end +-- +-- ### 2. Approach. +-- +-- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- +-- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 3. Boarded. +-- +-- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: +-- +-- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4. Returning. +-- +-- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: +-- +-- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 5. Rescued. +-- +-- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: +-- +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- ... your code here ... +-- end +-- +-- ## 4. Spawn downed pilots at location to be picked up. +-- +-- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- +-- +-- @field #CSAR +CSAR = { + ClassName = "CSAR", + verbose = 1, + lid = "", + coalition = 1, + coalitiontxt = "blue", + FreeVHFFrequencies = {}, + UsedVHFFrequencies = {}, + takenOff = {}, + csarUnits = {}, -- table of unit names + downedPilots = {}, + woundedGroups = {}, + landedStatus = {}, + addedTo = {}, + woundedGroups = {}, -- contains the new group of units + inTransitGroups = {}, -- contain a table for each SAR with all units he has with the original names + smokeMarkers = {}, -- tracks smoke markers for groups + heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible + heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance + max_units = 6, --number of pilots that can be carried + hoverStatus = {}, -- tracks status of a helis hover above a downed pilot + pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for + pilotLives = {}, -- tracks how many lives a pilot has + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + csarPrefix = {}, + template = nil, + bluemash = {}, + smokecolor = 4, + rescues = 0, +} + +--- Downed pilots info. +-- @type CSAR.DownedPilot +-- @field #number index Pilot index. +-- @field #string name Name of the spawned group. +-- @field #number side Coalition. +-- @field #string originalUnit Name of the original unit. +-- @field #string desc Description. +-- @field #string typename Typename of Unit. +-- @field #number frequency Frequency of the NDB. +-- @field #string player Player name if applicable. +-- @field Wrapper.Group#GROUP group Spawned group object. +-- @field #number timestamp Timestamp for approach process + +--- Known beacons from the available maps +-- @field #CSAR.SkipFrequencies +CSAR.SkipFrequencies = { + 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, + 440,795,525,520,690,625,291.5,300.50, + 435,309.50,920,1065,274,312.50, + 580,602,297.50,750,485,950,214, + 1025,730,995,455,307,670,329,395,770, + 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, + 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, + 337,312.5,830,740,309.5,641,312,722,682,1050, + 1116,935,1000,430,577,540,550,560,570, + } + +--- All slot / Limit settings +-- @type CSAR.AircraftType +-- @field #string typename Unit type name. +CSAR.AircraftType = {} -- Type and limit +CSAR.AircraftType["SA342Mistral"] = 2 +CSAR.AircraftType["SA342Minigun"] = 2 +CSAR.AircraftType["SA342L"] = 4 +CSAR.AircraftType["SA342M"] = 4 +CSAR.AircraftType["UH-1H"] = 4 +CSAR.AircraftType["Mi-8MT"] = 8 +CSAR.AircraftType["Mi-24"] = 8 + +--- CSAR class version. +-- @field #string version +CSAR.version="0.1.0r1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Documentation +-- WONTDO: Slot blocker etc + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new CSAR object and start the FSM. +-- @param #CSAR self +-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral". +-- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. +-- @param #string Alias An *optional* alias how this object is called in the logs etc. +-- @return #CSAR self +function CSAR:New(Coalition, Template, Alias) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CSAR + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CSAR!") + end + else + self.coalition = Coalition + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="Red Cross" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Спасение" + elseif self.coalition==coalition.side.BLUE then + self.alias="CSAR" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CSAR status update. + self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added + self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. + self:AddTransition("*", "Boarded", "*") -- Pilot boarded. + self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables, mainly for tracking actions + self.addedTo = {} + self.allheligroupset = {} -- GROUP_SET of all helis + self.csarUnits = {} -- table of CSAR unit names + self.FreeVHFFrequencies = {} + self.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible + self.heliCloseMessage = {} -- tracks heli close message ie heli < 500m distance + self.hoverStatus = {} -- tracks status of a helis hover above a downed pilot + self.inTransitGroups = {} -- contain a table for each SAR with all units he has with the original names + self.landedStatus = {} + self.lastCrash = {} + self.takenOff = {} + self.smokeMarkers = {} -- tracks smoke markers for groups + self.UsedVHFFrequencies = {} + self.woundedGroups = {} -- contains the new group of units + self.downedPilots = {} -- Replacement woundedGroups + self.downedpilotcounter = 1 + + -- settings, counters etc + self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. + self.enableForAI = true -- set to false to disable AI units from being rescued. + self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.immortalcrew = true -- Set to true to make wounded crew immortal + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 30 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter + self.loadtimemax = 135 -- seconds + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.max_units = 6 --number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.template = Template or "generic" -- template for downed pilot + self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.autosmoke = false -- automatically smoke location when heli is near + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] Start + -- @param #CSAR self + + --- Triggers the FSM event "Start" after a delay. Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] __Start + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CSAR and all its event handlers. + -- @param #CSAR self + + --- Triggers the FSM event "Stop" after a delay. Stops the CSAR and all its event handlers. + -- @function [parent=#CSAR] __Stop + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CSAR] Status + -- @param #CSAR self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CSAR] __Status + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- On After "PilotDown" event. Downed Pilot detected. + -- @function [parent=#CSAR] OnAfterPilotDown + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP Group Group object of the downed pilot. + -- @param #number Frequency Beacon frequency in kHz. + -- @param #string Leadername Name of the #UNIT of the downed pilot. + -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. + + --- On After "Aproach" event. Heli close to downed Pilot. + -- @function [parent=#CSAR] OnAfterApproach + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. + + --- On After "Boarded" event. Downed pilot boarded heli. + -- @function [parent=#CSAR] OnAfterBoarded + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Returning" event. Heli can return home with downed pilot(s). + -- @function [parent=#CSAR] OnAfterReturning + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @function [parent=#CSAR] OnAfterRescued + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter + -- @param #string HeliName Name of the helicopter group + + return self +end + +------------------------ +--- Helper Functions --- +------------------------ + +--- Function to insert downed pilot tracker object. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP Group The #GROUP object +-- @param #string Groupname Name of the spawned group. +-- @param #number Side Coalition. +-- @param #string OriginalUnit Name of original Unit. +-- @param #string Description Descriptive text. +-- @param #string Typename Typename of unit. +-- @param #number Frequency Frequency of the NDB in Hz +-- @param #string Playername Name of Player (if applicable) +-- @return #CSAR self. +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) + self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) + + -- create new entry + local DownedPilot = {} -- #CSAR.DownedPilot + DownedPilot.desc = Description or "" + DownedPilot.frequency = Frequency or 0 + DownedPilot.index = self.downedpilotcounter + DownedPilot.name = Groupname or "" + DownedPilot.originalUnit = OriginalUnit or "" + DownedPilot.player = Playername or "" + DownedPilot.side = Side or 0 + DownedPilot.typename = Typename or "" + DownedPilot.group = Group + DownedPilot.timestamp = 0 + + -- Add Pilot + local PilotTable = self.downedPilots + local counter = self.downedpilotcounter + PilotTable[counter] = {} + PilotTable[counter] = DownedPilot + --self.downedPilots[self.downedpilotcounter]=DownedPilot + self:T({Table=PilotTable}) + self.downedPilots = PilotTable + -- Increase counter + self.downedpilotcounter = self.downedpilotcounter+1 +end + +--- Count pilots on board. +-- @param #CSAR self +-- @param #string _heliName +-- @return #number count +function CSAR:_PilotsOnboard(_heliName) + self:T(self.lid .. " _PilotsOnboard") + local count = 0 + if self.inTransitGroups[_heliName] then + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end + end + return count +end + +--- Function to check for dupe eject events. +-- @param #CSAR self +-- @param #string _unitname Name of unit. +-- @return #boolean Outcome +function CSAR:_DoubleEjection(_unitname) + + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true + end + end + + self.lastCrash[_unitname] = timer.getTime() + return false +end + +--- Spawn a downed pilot +-- @param #CSAR self +-- @param #number country Country for template. +-- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @return Wrapper.Group#GROUP group The #GROUP object. +-- @return #string alias The alias name. +function CSAR:_SpawnPilotInField(country,point) + self:T({country,point}) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local coalition = self.coalition + local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local _spawnedGroup = SPAWN + :NewWithAlias(template,alias) + :InitCoalition(coalition) + :InitCountry(country) + :InitAIOnOff(pilotcacontrol) + :InitDelayOff() + :SpawnFromCoordinate(point) + + --return object + return _spawnedGroup, alias -- Wrapper.Group#GROUP object +end + +--- Add options to a downed pilot +-- @param #CSAR self +-- @param Wrapper.Group#GROUP group Group to use. +function CSAR:_AddSpecialOptions(group) + self:T(self.lid.." _AddSpecialOptions") + self:T({group}) + + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + if immortalcrew then + local _setImmortal = { + id = 'SetImmortal', + params = { + value = true + } + } + group:SetCommand(_setImmortal) + end + + if invisiblecrew then + -- invisible + local _setInvisible = { + id = 'SetInvisible', + params = { + value = true + } + } + group:SetCommand(_setInvisible) + end + + group:OptionAlarmStateGreen() + group:OptionROEHoldFire() + +end + +--- Function to spawn a CSAR object into the scene. +-- @param #CSAR self +-- @param #number _coalition Coalition +-- @param DCS#country.id _country Country ID +-- @param Core.Point#COORDINATE _point Coordinate +-- @param #string _typeName Typename +-- @param #string _unitName Unitname +-- @param #string _playerName Playername +-- @param #number _freq Frequency +-- @param #boolean noMessage +-- @param #string _description Description +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) + self:T(self.lid .. " _AddCsar") + self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) + -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) + local _typeName = _typeName or "PoW" + if not noMessage then + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + end + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = "333.25" end --noob catch + end + + if _freq then + self:_AddBeaconToGroup(_spawnedGroup, _freq) + end + + self:_AddSpecialOptions(_spawnedGroup) + -- Generate DESCRIPTION text + local _text = " " + if _playerName ~= nil then + _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName + elseif _typeName ~= nil then + _text = "AI Pilot of " .. _unitName .. " - " .. _typeName + else + _text = _description + end + + + self:T({_spawnedGroup, _alias}) + + local _GroupName = _spawnedGroup:GetName() or _alias + --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + + --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string _zone Name of the zone. +-- @param #number _coalition Coalition. +-- @param #string _description (optional) Description. +-- @param #boolean _randomPoint (optional) Random yes or no. +-- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) + self:T(self.lid .. " _SpawnCsarAtZone") + local freq = self:_GenerateADFFrequency() + local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + if _triggerZone == nil then + self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + return + end + + local _description = _description or "none" + + local pos = {} + if _randomPoint then + local _pos = _triggerZone:GetRandomPointVec3() + pos = COORDINATE:NewFromVec3(_pos) + else + pos = _triggerZone:GetCoordinate() + end + + local _country = 0 + if _coalition == coalition.side.BLUE then + _country = country.id.USA + elseif _coalition == coalition.side.RED then + _country = country.id.RUSSIA + else + _country = country.id.UN_PEACEKEEPERS + end + + self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) +end + +-- TODO: Split in functions per Event type +--- Event handler. +-- @param #CSAR self +function CSAR:_EventHandler(EventData) + self:T(self.lid .. " _EventHandler") + self:T({Event = EventData.id}) + + local _event = EventData -- Core.Event#EVENTDATA + + -- no event + if _event == nil or _event.initiator == nil then + return false + + -- take off + elseif _event.id == EVENTS.Takeoff then -- taken off + self:T(self.lid .. " Event unit - Takeoff") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniGroupName then + self.takenOff[_event.IniUnitName] = true + end + + return true + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit + self:T(self.lid .. " Event unit - Player Enter") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniPlayerName then + self.takenOff[_event.IniPlayerName] = nil + end + + -- if its a sar heli, re-add check status script + for _, _heliName in pairs(self.csarUnits) do + if _heliName == _event.IniPlayerName then + -- add back the status script + local DownedPilotTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot + if _groupInfo.side == _event.IniCoalition then + local _woundedName = _groupInfo.name + self:_CheckWoundedGroupStatus(_heliName,_woundedName) + end + end + end + end + + return true + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection(_unitname) then + return + end + + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + -- self:_HandleEjectOrCrash(_unit, true) + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return --ignore! + end + + if self.enableForAI == false and _event.IniPlayerName == nil then + return + end + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + return + end + + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) + + return true + + elseif _event.id == EVENTS.Land then + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + --local _unit = _event.initiator + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return -- error! + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_RescuePilots(_unit) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return true + end + +end + +--- Initialize the action for a pilot. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _downedGroup The group to rescue. +-- @param #string _GroupName Name of the Group +-- @param #number _freq Beacon frequency. +-- @param #boolean _nomessage Send message true or false. +function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) + self:T(self.lid .. " _InitSARForPilot") + local _leader = _downedGroup:GetUnit(1) + --local _groupName = _downedGroup:GetName() + local _groupName = _GroupName + local _freqk = _freq / 1000 + local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) + local _leadername = _leader:GetName() + + if not _nomessage then + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) + self:_DisplayToAllSAR(_text) + end + + for _,_heliName in pairs(self.csarUnits) do + self:_CheckWoundedGroupStatus(_heliName, _groupName) + end + + -- trigger FSM event + self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) +end + +--- Check if a name is in downed pilot table +-- @param #CSAR self +-- @param #string name Name to search for. +-- @return #boolean Outcome. +-- @return #CSAR.DownedPilot Table if found else nil. +function CSAR:_CheckNameInDownedPilots(name) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + local table = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + found = true + table = _pilot + break + end + end + return found, table +end + +--- Check if a name is in downed pilot table and remove it. +-- @param #CSAR self +-- @param #string name Name to search for. +-- @param #boolean force Force removal. +-- @return #boolean Outcome. +function CSAR:_RemoveNameFromDownedPilots(name,force) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + local group = _pilot.group -- Wrapper.Group#GROUP + if group then + if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + found = true + _pilot.desc = nil + _pilot.frequency = nil + _pilot.index = nil + _pilot.name = nil + _pilot.originalUnit = nil + _pilot.player = nil + _pilot.side = nil + _pilot.typename = nil + _pilot.group = nil + _pilot.timestamp = nil + end + end + end + end + return found +end + +--- Check state of wounded group. +-- @param #CSAR self +-- @param #string heliname heliname +-- @param #string woundedgroupname woundedgroupname +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) + self:T(self.lid .. " _CheckWoundedGroupStatus") + local _heliName = heliname + local _woundedGroupName = woundedgroupname + self:T({Heli = _heliName, Downed = _woundedGroupName}) + -- if wounded group is not here then message alread been sent to SARs + -- stop processing any further + local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) + if not _found then + self:T("...not found in list!") + return + end + + --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) + --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP + local _woundedGroup = _downedpilot.group + if _woundedGroup ~= nil then + local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT + + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking + + if _heliUnit == nil then + self.heliVisibleMessage[_lookupKeyHeli] = nil + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil + self:T("...helinunit nil!") + return + end + + --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then + local _heliCoord = _heliUnit:GetCoordinate() + local _leaderCoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + if _distance < 3000 and _distance > 0 then + if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then + -- we're close, reschedule + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-5,heliname,woundedgroupname) + end + else + self.heliVisibleMessage[_lookupKeyHeli] = nil + --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-10,heliname,woundedgroupname) + end + else + self:T("...Downed Pilot KIA?!") + self:_RemoveNameFromDownedPilots(_downedpilot.name) + end +end + +--- Function to pop a smoke at a wounded pilot's positions. +-- @param #CSAR self +-- @param #string _woundedGroupName Name of the group. +-- @param Wrapper.Group#GROUP _woundedLeader Object of the group. +function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + self:T(self.lid .. " _PopSmokeForGroup") + -- have we popped smoke already in the last 5 mins + local _lastSmoke = self.smokeMarkers[_woundedGroupName] + if _lastSmoke == nil or timer.getTime() > _lastSmoke then + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate() + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + end +end + +--- Function to pickup the wounded pilot from the ground. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit Object of the group. +-- @param #string _pilotName Name of the pilot. +-- @param Wrapper.Group#GROUP _woundedGroup Object of the group. +-- @param #string _woundedGroupName Name of the group. +function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _PickupUnit") + --local _woundedLeader = _woundedGroup:GetUnit(1) + + -- GET IN! + local _heliName = _heliUnit:GetName() + local _groups = self.inTransitGroups[_heliName] + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + + -- init table if there is none for this helicopter + if not _groups then + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] + end + + -- if the heli can't pick them up, show a message and return + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + if _unitsInHelicopter + 1 > _maxUnits then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10) + return true + end + + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) + local grouptable = downedgrouptable --#CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = + { + -- DONE: Fix with #CSAR.DownedPilot + originalUnit = grouptable.originalUnit, + woundedGroup = _woundedGroupName, + side = self.coalition, + desc = grouptable.desc, + player = grouptable.player, + } + + _woundedGroup:Destroy() + self:_RemoveNameFromDownedPilots(_woundedGroupName,true) + + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + + self:__Boarded(5,_heliName,_woundedGroupName) + + return true +end + +--- Move group to destination. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _leader +-- @param Core.Point#COORDINATE _destination +function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) + self:T(self.lid .. " _OrderGroupToMoveToPoint") + local group = _leader + local coordinate = _destination:GetVec2() + --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() + group:RouteToVec2(coordinate,5) +end + +--- Function to check if heli is close to group. +-- @param #CSAR self +-- @param #number _distance +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @return #boolean Outcome +function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _CheckCloseWoundedGroup") + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot + local _pilotName = _pilotable.desc + --local _pilotName = self.woundedGroups[_woundedGroupName].desc + --local _pilotName = _woundedGroup:GetName() + + local _reset = true + + if (self.autosmoke == true) and (_distance < 500) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + end + + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end + + if (_distance < 500) then + + if self.heliCloseMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + end + --mark as shown for THIS heli and THIS group + self.heliCloseMessage[_lookupKeyHeli] = true + end + + -- have we landed close enough? + if not _heliUnit:InAir() then + + -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + if (_distance < self.loadDistance) then + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + + if _distance < 8.0 then + + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + if _height <= 20.0 then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + _reset = false + else + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + end + end + + end + end + end + + if _reset then + self.hoverStatus[_lookupKeyHeli] = nil + end + + if _distance < 500 then + return true + else + return false + end +end + +--- Check if group not KIA. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @return #boolean Outcome +function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) + self:T(self.lid .. " _CheckGroupNotKIA") + -- check if unit has died or been picked up + local inTransit = false + if _woundedGroup and _heliUnit then + for _currentHeli, _groups in pairs(self.inTransitGroups) do + if _groups[_woundedGroupName] then + --local _group = _groups[_woundedGroupName] + inTransit = true + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + break + end -- end name check + end -- end loop + if not inTransit then + -- KIA + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + end + --stops the message being displayed again + self:_RemoveNameFromDownedPilots(_woundedGroupName) + --self.woundedGroups[_woundedGroupName] = nil + end + --continue + return inTransit +end + +--- Monitor in-flight returning groups. +-- @param #CSAR self +-- @param #string heliname Heli name +-- @param #string groupname Group name +function CSAR:_ScheduledSARFlight(heliname,groupname) + self:T(self.lid .. " _ScheduledSARFlight") + self:T({heliname,groupname}) + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname + + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end + + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end + + local _dist = self:_GetClosestMASH(_heliUnit) + + if _dist == -1 then + return + end + + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end + + --queue up + self:__Returning(-5,heliname,_woundedGroupName) +end + +--- Mark pilot as rescued and remove from tables. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit +function CSAR:_RescuePilots(_heliUnit) + self:T(self.lid .. " _RescuePilots") + local _heliName = _heliUnit:GetName() + local _rescuedGroups = self.inTransitGroups[_heliName] + + if _rescuedGroups == nil then + -- Groups already rescued + return + end + + self.inTransitGroups[_heliName] = nil + + local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + + self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + -- trigger event + self:__Rescued(-1,_heliUnit,_heliName) +end + +--- Check and return Wrappe.Unit#UNIT based on the name if alive. +-- @param #CSAR self +-- @param #string _unitname Name of Unit +-- @return #UNIT or nil +function CSAR:_GetSARHeli(_unitName) + self:T(self.lid .. " _GetSARHeli") + local unit = UNIT:FindByName(_unitName) + if unit and unit:IsAlive() then + return unit + else + return nil + end +end + +--- Display message to single Unit. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _unit +-- @param #string _text +-- @param #number _time +-- @param #boolean _clear +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) + self:T(self.lid .. " _DisplayMessageToSAR") + local group = _unit:GetGroup() + local _clear = _clear or nil + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) +end + +--- Function to get string of a group's position. +-- @param #CSAR self +-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @return #string Coordinates as Text +function CSAR:_GetPositionOfWounded(_woundedGroup) + self:T(self.lid .. " _GetPositionOfWounded") + local _coordinate = _woundedGroup:GetCoordinate() + local _coordinatesText = "None" + if _coordinate then + if self.coordtype == 0 then -- Lat/Long DMTM + _coordinatesText = _coordinate:ToStringLLDDM() + elseif self.coordtype == 1 then -- Lat/Long DMS + _coordinatesText = _coordinate:ToStringLLDMS() + elseif self.coordtype == 2 then -- MGRS + _coordinatesText = _coordinate:ToStringMGRS() + elseif self.coordtype == 3 then -- Bullseye Imperial + local Settings = _SETTINGS:SetImperial() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4) + local Settings = _SETTINGS:SetMetric() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + end + end + return _coordinatesText +end + +--- Display active SAR tasks to player. +-- @param #CSAR self +-- @param #string _unitName Unit to display to +function CSAR:_DisplayActiveSAR(_unitName) + self:T(self.lid .. " _DisplayActiveSAR") + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT + if _heli == nil then + return + end + + local _heliSide = self.coalition + local _csarList = {} + + local _DownedPilotTable = self.downedPilots + self:T({Table=_DownedPilotTable}) + for _, _value in pairs(_DownedPilotTable) do + local _groupName = _value.name + self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) + self:T({Table=_value}) + --local _woundedGroup = GROUP:FindByName(_groupName) + local _woundedGroup = _value.group + if _woundedGroup then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + end + end + + local function sortDistance(a, b) + return a.dist < b.dist + end + + table.sort(_csarList, sortDistance) + + for _, _line in pairs(_csarList) do + _msg = _msg .. "\n" .. _line.msg + end + + self:_DisplayMessageToSAR(_heli, _msg, 20) +end + +--- Find the closest downed pilot to a heli. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @return #table Table of results +function CSAR:_GetClosestDownedPilot(_heli) + self:T(self.lid .. " _GetClosestDownedPilot") + local _side = self.coalition + local _closestGroup = nil + local _shortestDistance = -1 + local _distance = 0 + local _closestGroupInfo = nil + local _heliCoord = _heli:GetCoordinate() + + local DownedPilotsTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + --local _tempWounded = GROUP:FindByName(_woundedName) + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo + end + end + end + + return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } +end + +--- Fire a flare at the point of a downed pilot. +-- @param #CSAR self +-- @param #string _unitName Name of the unit. +function CSAR:_SignalFlare(_unitName) + self:T(self.lid .. " _SignalFlare") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + + local _closest = self:_GetClosestDownedPilot(_heli) + + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Display info to all SAR groups. +-- @param #CSAR self +-- @param #string _message +-- @param #number _side +-- @param #string _ignore +function CSAR:_DisplayToAllSAR(_message, _side, _ignore) + self:T(self.lid .. " _DisplayToAllSAR") + for _, _unitName in pairs(self.csarUnits) do + local _unit = self:_GetSARHeli(_unitName) + if _unit then + if not _ignore then + self:_DisplayMessageToSAR(_unit, _message, 10) + end + end + end +end + +---Request smoke at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_Reqsmoke( _unitName ) + self:T(self.lid .. " _Reqsmoke") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local _closest = self:_GetClosestDownedPilot(_heli) + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Determine distance to closest MASH. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @retunr +function CSAR:_GetClosestMASH(_heli) + self:T(self.lid .. " _GetClosestMASH") + local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashes = _mashset:GetSetObjects() -- #table + local _shortestDistance = -1 + local _distance = 0 + local _helicoord = _heli:GetCoordinate() + + local function GetCloseAirbase(coordinate,Coalition,Category) + + local a=coordinate:GetVec3() + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + if dist 0 then + self:_AddBeaconToGroup(group,frequency) + end + end +end + + ------------------------------ + --- FSM internal Functions --- + ------------------------------ + +--- Function called after Start() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started.") + -- event handler + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:_GenerateVHFrequencies() + if self.useprefix then + local prefixes = self.csarPrefix or {} + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() + else + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + self:__Status(-10) + return self +end + +--- Function called before Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- housekeeping + self:_AddMedevacMenuItem() + self:_RefreshRadioBeacons() + for _,_sar in pairs (self.csarUnits) do + local PilotTable = self.downedPilots + for _,_entry in pairs (PilotTable) do + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end + end + end + return self +end + +--- Function called after Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- collect some stats + local NumberOfSARPilots = 0 + for _, _unitName in pairs(self.csarUnits) do + NumberOfSARPilots = NumberOfSARPilots + 1 + end + + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + + local PilotsBoarded = 0 + for _, _unitName in pairs(self.inTransitGroups) do + for _,_units in pairs(_unitName) do + PilotsBoarded = PilotsBoarded + 1 + end + end + + if self.verbose > 0 then + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + self:T(text) + if self.verbose > 1 then + local m = MESSAGE:New(text,"10","Status"):ToAll() + end + end + self:__Status(-20) + return self +end + +--- Function called after Stop() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStop(From, Event, To) + self:T({From, Event, To}) + -- event handler + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.Dead) + self:T(self.lid .. "Stopped.") + return self +end + +--- Function called before Approach() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) + return self +end + +--- Function called before Boarded() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Returning() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Rescued() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. +-- @param #string HeliName Name of the helicopter group. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) + self:T({From, Event, To, HeliName, HeliUnit}) + self.rescues = self.rescues + 1 + return self +end + +--- Function called before PilotDown() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group Group object of the downed pilot. +-- @param #number Frequency Beacon frequency in kHz. +-- @param #string Leadername Name of the #UNIT of the downed pilot. +-- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. +function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) + self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) + return self +end +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 7cde279be1e067248ce7171830ed303e1bfbb283 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:20:00 +0200 Subject: [PATCH 296/382] Update Modules.lua added CSAR module --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ff11712bf..fef4fc982 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -84,6 +84,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From 1b37af321febd383b54268e1d3a705c3b422639b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 16:21:47 +0200 Subject: [PATCH 297/382] Update CSAR.lua Small updates --- Moose Development/Moose/Ops/CSAR.lua | 44 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 9bc98340c..41dc944ab 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -8,7 +8,7 @@ -- -- ## Missions: -- --- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) -- -- === -- @@ -127,7 +127,7 @@ -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- --- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... -- end -- @@ -212,7 +212,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r1" +CSAR.version="0.1.0r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -309,7 +309,7 @@ function CSAR:New(Coalition, Template, Alias) self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue - self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible self.messageTime = 30 -- Time to show longer messages for in seconds @@ -383,8 +383,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -392,8 +392,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -401,8 +401,9 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter - -- @param #string HeliName Name of the helicopter group + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. + -- @param #string HeliName Name of the helicopter group. + -- @param #number PilotsSaved Number of the save pilots on board when landing. return self end @@ -492,10 +493,13 @@ end -- @return #string alias The alias name. function CSAR:_SpawnPilotInField(country,point) self:T({country,point}) + for i=1,10 do + math.random(i,10000) + end local template = self.template local alias = string.format("Downed Pilot-%d",math.random(1,10000)) local coalition = self.coalition - local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN :NewWithAlias(template,alias) :InitCoalition(coalition) @@ -559,9 +563,9 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) local template = self.template - local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - local immortalcrew = self.immortalcrew - local invisiblecrew = self.invisiblecrew + --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + --local immortalcrew = self.immortalcrew + --local invisiblecrew = self.invisiblecrew local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -1240,13 +1244,20 @@ function CSAR:_RescuePilots(_heliUnit) return end + -- TODO: count saved units? + local PilotsSaved = 0 + for _,_units in pairs(_rescuedGroups) do + PilotsSaved = PilotsSaved + 1 + end + + self.inTransitGroups[_heliName] = nil local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) self:_DisplayMessageToSAR(_heliUnit, _txt, 10) -- trigger event - self:__Rescued(-1,_heliUnit,_heliName) + self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end --- Check and return Wrappe.Unit#UNIT based on the name if alive. @@ -1917,3 +1928,6 @@ function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, C return self end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End Ops.CSAR +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + From 10a0793af730dc32b66987eeb4192a9e17543285 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:15:58 +0200 Subject: [PATCH 298/382] Update CSAR.lua Small updates to reflect correct measurements. Options to SRS TTS. --- Moose Development/Moose/Ops/CSAR.lua | 191 ++++++++++++++------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 41dc944ab..f95c319da 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -48,7 +48,7 @@ -- ## 0. Prerequisites -- -- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. --- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup -- @@ -79,7 +79,7 @@ -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. -- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. -- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. @@ -212,13 +212,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r2" +CSAR.version="0.1.2r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Documentation +-- TODO: SRS Integration (to be tested) -- WONTDO: Slot blocker etc ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -281,7 +281,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. self:AddTransition("*", "Boarded", "*") -- Pilot boarded. - self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -312,7 +312,7 @@ function CSAR:New(Coalition, Template, Alias) self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 30 -- Time to show longer messages for in seconds + self.messageTime = 15 -- Time to show longer messages for in seconds self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter @@ -327,6 +327,14 @@ function CSAR:New(Coalition, Template, Alias) self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- WARNING - here'll be dragons + -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua + -- needs SRS => 1.9.6 to work (works on the *server* side) + self.useSRS = false -- Use FF's SRS integration + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSchannel = 300 -- radio channel + self.SRSModulation = radio.modulation.AM -- modulation + ------------------------ --- Pseudo Functions --- ------------------------ @@ -403,7 +411,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. - -- @param #number PilotsSaved Number of the save pilots on board when landing. + -- @param #number PilotsSaved Number of the saved pilots on board when landing. return self end @@ -444,7 +452,6 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript local counter = self.downedpilotcounter PilotTable[counter] = {} PilotTable[counter] = DownedPilot - --self.downedPilots[self.downedpilotcounter]=DownedPilot self:T({Table=PilotTable}) self.downedPilots = PilotTable -- Increase counter @@ -471,16 +478,13 @@ end -- @param #string _unitname Name of unit. -- @return #boolean Outcome function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then self:E(self.lid.."Caught double ejection!") return true end end - self.lastCrash[_unitname] = timer.getTime() return false end @@ -508,7 +512,6 @@ function CSAR:_SpawnPilotInField(country,point) :InitDelayOff() :SpawnFromCoordinate(point) - --return object return _spawnedGroup, alias -- Wrapper.Group#GROUP object end @@ -532,7 +535,6 @@ function CSAR:_AddSpecialOptions(group) end if invisiblecrew then - -- invisible local _setInvisible = { id = 'SetInvisible', params = { @@ -561,11 +563,9 @@ end function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) - -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template - --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - --local immortalcrew = self.immortalcrew - --local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -582,7 +582,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end self:_AddSpecialOptions(_spawnedGroup) - -- Generate DESCRIPTION text + local _text = " " if _playerName ~= nil then _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName @@ -591,16 +591,13 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla else _text = _description end - - + self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias - --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } - --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) - - --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) end @@ -617,7 +614,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position if _triggerZone == nil then - self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end @@ -722,10 +719,8 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - + end local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) - -- self:_HandleEjectOrCrash(_unit, true) else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -779,7 +774,6 @@ function CSAR:_EventHandler(EventData) if self.allowFARPRescue then local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - --local _unit = _event.initiator if _unit == nil then self:T(self.lid .. " Unit nil on landing") @@ -829,7 +823,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) if not _nomessage then local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) - self:_DisplayToAllSAR(_text) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) end for _,_heliName in pairs(self.csarUnits) do @@ -899,7 +893,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _heliName = heliname local _woundedGroupName = woundedgroupname self:T({Heli = _heliName, Downed = _woundedGroupName}) - -- if wounded group is not here then message alread been sent to SARs + -- if wounded group is not here then message already been sent to SARs -- stop processing any further local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) if not _found then @@ -907,14 +901,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) - --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP local _woundedGroup = _downedpilot.group if _woundedGroup ~= nil then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT - local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking if _heliUnit == nil then @@ -972,9 +962,7 @@ end -- @param #string _woundedGroupName Name of the group. function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _PickupUnit") - --local _woundedLeader = _woundedGroup:GetUnit(1) - - -- GET IN! + -- board local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] local _unitsInHelicopter = self:_PilotsOnboard(_heliName) @@ -991,7 +979,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,7 +998,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) @@ -1025,7 +1013,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) self:T(self.lid .. " _OrderGroupToMoveToPoint") local group = _leader local coordinate = _destination:GetVec2() - --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() group:RouteToVec2(coordinate,5) end @@ -1040,14 +1028,13 @@ end -- @return #boolean Outcome function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _CheckCloseWoundedGroup") - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - --local _pilotName = self.woundedGroups[_woundedGroupName].desc - --local _pilotName = _woundedGroup:GetName() + local _reset = true @@ -1057,9 +1044,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliVisibleMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1069,9 +1056,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1085,11 +1072,10 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1136,7 +1122,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1144,7 +1130,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end _reset = false else - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) end end @@ -1177,19 +1163,17 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he if _woundedGroup and _heliUnit then for _currentHeli, _groups in pairs(self.inTransitGroups) do if _groups[_woundedGroupName] then - --local _group = _groups[_woundedGroupName] inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) break end -- end name check end -- end loop if not inTransit then -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) end --stops the message being displayed again self:_RemoveNameFromDownedPilots(_woundedGroupName) - --self.woundedGroups[_woundedGroupName] = nil end --continue return inTransit @@ -1244,18 +1228,14 @@ function CSAR:_RescuePilots(_heliUnit) return end - -- TODO: count saved units? - local PilotsSaved = 0 - for _,_units in pairs(_rescuedGroups) do - PilotsSaved = PilotsSaved + 1 - end - + -- DONE: count saved units? + local PilotsSaved = self:_PilotsOnboard(_heliName) self.inTransitGroups[_heliName] = nil - local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) - self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end @@ -1276,15 +1256,26 @@ end --- Display message to single Unit. -- @param #CSAR self --- @param Wrapper.Unit#UNIT _unit --- @param #string _text --- @param #number _time --- @param #boolean _clear -function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) +-- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. +-- @param #string _text Text of message. +-- @param #number _time Message show duration. +-- @param #boolean _clear (optional) Clear screen. +-- @param #boolean _speak (optional) Speak message via SRS. +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) self:T(self.lid .. " _DisplayMessageToSAR") local group = _unit:GetGroup() local _clear = _clear or nil + local _time = _time or self.messageTime local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + -- integrate SRS + if _speak and self.useSRS then + local srstext = SOUNDTEXT:New(_text) + local path = self.SRSPath + local modulation = self.SRSModulation + local channel = self.SRSchannel + local msrs = MSRS:New(path,channel,modulation) + msrs:PlaySoundText(srstext, 2) + end end --- Function to get string of a group's position. @@ -1341,7 +1332,14 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + -- change distance to miles if self.coordtype < 4 + local distancetext = "" + if self.coordtype < 4 then + distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.3fkm", _distance/1000.0) + end + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end end @@ -1355,7 +1353,7 @@ function CSAR:_DisplayActiveSAR(_unitName) _msg = _msg .. "\n" .. _line.msg end - self:_DisplayMessageToSAR(_heli, _msg, 20) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) end --- Find the closest downed pilot to a heli. @@ -1374,7 +1372,6 @@ function CSAR:_GetClosestDownedPilot(_heli) local DownedPilotsTable = self.downedPilots for _, _groupInfo in pairs(DownedPilotsTable) do local _woundedName = _groupInfo.name - --local _tempWounded = GROUP:FindByName(_woundedName) local _tempWounded = _groupInfo.group -- check group exists and not moving to someone else @@ -1408,29 +1405,34 @@ function CSAR:_SignalFlare(_unitName) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end --- Display info to all SAR groups. -- @param #CSAR self --- @param #string _message --- @param #number _side --- @param #string _ignore -function CSAR:_DisplayToAllSAR(_message, _side, _ignore) +-- @param #string _message Message to display. +-- @param #number _side Coalition of message. +-- @param #number _messagetime How long to show. +function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) self:T(self.lid .. " _DisplayToAllSAR") for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) if _unit then - if not _ignore then - self:_DisplayMessageToSAR(_unit, _message, 10) + if not _messagetime then + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end end @@ -1448,13 +1450,19 @@ function CSAR:_Reqsmoke( _unitName ) local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end @@ -1493,7 +1501,6 @@ function CSAR:_GetClosestMASH(_heli) if self.allowFARPRescue then local position = _heli:GetCoordinate() local afb,distance = position:GetClosestAirbase2(nil,self.coalition) - --local distance = GetCloseAirbase(position,self.coalition,nil) _shortestDistance = distance end @@ -1532,7 +1539,7 @@ function CSAR:_CheckOnboard(_unitName) for _, _onboard in pairs(self.inTransitGroups[_unitName]) do _text = _text .. "\n" .. _onboard.desc end - self:_DisplayMessageToSAR(_unit, _text, self.messageTime) + self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end end @@ -1552,10 +1559,7 @@ function CSAR:_AddMedevacMenuItem() if _unit then if _unit:IsAlive() then local unitName = _unit:GetName() - --if not self.csarUnits[unitName] then - --self.csarUnits[unitName] = unitName _UnitList[unitName] = unitName - --end end -- end isAlive end -- end if _unit end -- end for @@ -1700,9 +1704,7 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - --clock = math.abs(math.floor(Aspect / 30)) clock = math.floor(Aspect / 30) - --clock = UTILS.Round(clock,-2) end return clock end @@ -1730,7 +1732,6 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Frequency = _freq -- Freq in Hertz local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight - --timer.scheduleFunction(self._RefreshRadioBeacons, { _group, _freq }, timer.getTime() + 30) end end @@ -1908,7 +1909,8 @@ end -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) +-- @param #number PilotsSaved Number of the saved pilots on board when landing. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 return self @@ -1930,4 +1932,3 @@ end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End Ops.CSAR -------------------------------------------------------------------------------------------------------------------------------------------------------------------- - From 270c69344f5fff637fcec68a755e4908aaf30311 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 07:42:45 +0200 Subject: [PATCH 299/382] Update CSAR.lua Updated documentation / clarification. Added feature docu on using SRS --- Moose Development/Moose/Ops/CSAR.lua | 40 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index f95c319da..55ac59b82 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -64,27 +64,37 @@ -- -- ## 2. Options -- --- The following options are available (with their defaults): +-- The following options are available (with their defaults). Only set the ones you want changed: -- -- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. --- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. --- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! --- self.enableForAI = true -- set to false to disable AI units from being rescued. --- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. --- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. --- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. --- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. --- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- +-- ## 2.1 Experimental Features +-- +-- WARNING - Here'll be dragons! +-- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) +-- self.useSRS = false -- Set true to use FF's SRS integration +-- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSchannel = 300 -- radio channel +-- self.SRSModulation = radio.modulation.AM -- modulation -- -- ## 3. Events -- @@ -142,7 +152,7 @@ -- @field #CSAR CSAR = { ClassName = "CSAR", - verbose = 1, + verbose = 0, lid = "", coalition = 1, coalitiontxt = "blue", @@ -212,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r1" +CSAR.version="0.1.2r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list From 64262d6eccd6cda8bdc1eed8a9b2b11545046f57 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 10:32:52 +0200 Subject: [PATCH 300/382] Update CSAR.lua (#1550) --- Moose Development/Moose/Ops/CSAR.lua | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 55ac59b82..a915d984c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -80,7 +80,7 @@ -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! @@ -88,10 +88,10 @@ -- -- ## 2.1 Experimental Features -- --- WARNING - Here'll be dragons! +-- "WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua --- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) --- self.useSRS = false -- Set true to use FF's SRS integration +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS)" +-- self.useSRS = false -- Set true to use FF\'s SRS integration -- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation @@ -222,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r2" +CSAR.version="0.1.3r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -262,6 +262,7 @@ function CSAR:New(Coalition, Template, Alias) end else self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end -- Set alias. @@ -1082,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1427,7 +1428,11 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1472,7 +1477,11 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1715,6 +1724,7 @@ function CSAR:_GetClockDirection(_heli, _group) local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end clock = math.floor(Aspect / 30) + if clock == 0 then clock = 12 end end return clock end From cd1935be1d939a780e9f5d16dd6e1ed359b4ef66 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 15:49:58 +0200 Subject: [PATCH 301/382] Update CSAR.lua (#1551) --- Moose Development/Moose/Ops/CSAR.lua | 63 +++++++++++++++------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a915d984c..c7600e156 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -96,12 +96,19 @@ -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- --- ## 3. Events +-- ## 3. Results +-- +-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: +-- +-- self.rescues -- number of successful landings *with* saved pilots +-- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) +-- +-- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: -- --- ### 1. PilotDown. +-- ### 4.1. PilotDown. -- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: -- @@ -109,7 +116,7 @@ -- ... your code here ... -- end -- --- ### 2. Approach. +-- ### 4.2. Approach. -- -- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: -- @@ -117,7 +124,7 @@ -- ... your code here ... -- end -- --- ### 3. Boarded. +-- ### 4.3. Boarded. -- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: -- @@ -125,7 +132,7 @@ -- ... your code here ... -- end -- --- ### 4. Returning. +-- ### 4.4. Returning. -- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: -- @@ -133,7 +140,7 @@ -- ... your code here ... -- end -- --- ### 5. Rescued. +-- ### 4.5. Rescued. -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- @@ -141,7 +148,7 @@ -- ... your code here ... -- end -- --- ## 4. Spawn downed pilots at location to be picked up. +-- ## 5. Spawn downed pilots at location to be picked up. -- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- @@ -179,6 +186,7 @@ CSAR = { bluemash = {}, smokecolor = 4, rescues = 0, + rescuedpilots = 0, } --- Downed pilots info. @@ -222,14 +230,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r1" +CSAR.version="0.1.3r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: SRS Integration (to be tested) --- WONTDO: Slot blocker etc +-- DONE: SRS Integration (to be tested) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -316,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.rescuedpilots = 0 -- counter for saved pilots self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. @@ -692,19 +700,11 @@ function CSAR:_EventHandler(EventData) self.takenOff[_event.IniPlayerName] = nil end - -- if its a sar heli, re-add check status script - for _, _heliName in pairs(self.csarUnits) do - if _heliName == _event.IniPlayerName then - -- add back the status script - local DownedPilotTable = self.downedPilots - for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot - if _groupInfo.side == _event.IniCoalition then - local _woundedName = _groupInfo.name - self:_CheckWoundedGroupStatus(_heliName,_woundedName) - end - end - end - end + local _unit = _event.IniUnit + local _group = _event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_AddMedevacMenuItem() + end return true @@ -1083,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1789,7 +1789,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Ejection, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:_GenerateVHFrequencies() if self.useprefix then local prefixes = self.csarPrefix or {} @@ -1855,10 +1855,14 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) - if self.verbose > 1 then - local m = MESSAGE:New(text,"10","Status"):ToAll() + if self.verbose < 2 then + self:I(text) + elseif self.verbose > 1 then + self:I(text) + local m = MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) end end self:__Status(-20) @@ -1878,7 +1882,7 @@ function CSAR:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.PlayerEnterUnit) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) - self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.PilotDead) self:T(self.lid .. "Stopped.") return self end @@ -1933,6 +1937,7 @@ end function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 + self.rescuedpilots = self.rescuedpilots + PilotsSaved return self end From 77a3c7369d7ec062f078c43aee058451fce0d3f5 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 18 Jun 2021 12:00:15 +0200 Subject: [PATCH 302/382] Update Shorad.lua --- Moose Development/Moose/Functional/Shorad.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 5dc7e769f..011a4989b 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -13,7 +13,7 @@ -- -- === -- --- ### Author : **applevangelist ** +-- ### Author : **applevangelist** -- -- @module Functional.Shorad -- @image Functional.Shorad.jpg @@ -94,7 +94,7 @@ SHORAD = { lid = "", DefendHarms = true, DefendMavs = true, - DefenseLowProb = 70, + DefenseLowProb = 75, DefenseHighProb = 90, UseEmOnOff = false, } From bf33e4ed4fea8ee73da71a02f8be7aa49e28d0de Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 18 Jun 2021 12:12:54 +0200 Subject: [PATCH 303/382] Update CSAR.lua (#1552) --- Moose Development/Moose/Ops/CSAR.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c7600e156..8d6e7292c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -224,13 +224,14 @@ CSAR.AircraftType["SA342Mistral"] = 2 CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 -CSAR.AircraftType["UH-1H"] = 4 -CSAR.AircraftType["Mi-8MT"] = 8 -CSAR.AircraftType["Mi-24"] = 8 +CSAR.AircraftType["UH-1H"] = 8 +CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-24P"] = 8 +CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r2" +CSAR.version="0.1.3r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1748,7 +1749,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) end if _group:IsAlive() then - local _radioUnit = _group:GetUnit(1) + local _radioUnit = _group:GetUnit(1) local Frequency = _freq -- Freq in Hertz local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight From 0e8732fd449a3eecac3dacb0b462432a24be15d4 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Jun 2021 22:27:12 +0200 Subject: [PATCH 304/382] ATIS - ATIS MSRS uses coalition of airbase - update coalition if base was captured --- Moose Development/Moose/Ops/ATIS.lua | 45 ++++++++++++++++++++++++++- Moose Development/Moose/Sound/SRS.lua | 17 ++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 368d3c488..e8b475195 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -589,7 +589,7 @@ _ATIS={} --- ATIS class version. -- @field #string version -ATIS.version="0.9.5" +ATIS.version="0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1143,6 +1143,7 @@ function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port) self.msrs:SetCulture(Culture) self.msrs:SetVoice(Voice) self.msrs:SetPort(Port) + self.msrs:SetCoalition(self:GetCoalition()) if self.dTQueueCheck<=10 then self:SetQueueUpdateTime(90) end @@ -1157,6 +1158,14 @@ function ATIS:SetQueueUpdateTime(TimeInterval) self.dTQueueCheck=TimeInterval or 5 end +--- Get the coalition of the associated airbase. +-- @param #ATIS self +-- @return #number Coalition of the associcated airbase. +function ATIS:GetCoalition() + local coal=self.airbase and self.airbase:GetCoalition() or nil + return coal +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1203,6 +1212,10 @@ function ATIS:onafterStart(From, Event, To) -- Start radio queue. self.radioqueue:Start(1, 0.1) + + -- Handle airbase capture + -- Handle events. + self:HandleEvent(EVENTS.BaseCaptured) -- Init status updates. self:__Status(-2) @@ -2259,6 +2272,36 @@ function ATIS:onafterReport(From, Event, To, Text) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Base captured +-- @param #ATIS self +-- @param Core.Event#EVENTDATA EventData Event data. +function ATIS:OnEventBaseCaptured(EventData) + + 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 + + -- New coalition of airbase after it was captured. + local NewCoalitionAirbase=airbase:GetCoalition() + + if self.useSRS and self.msrs and self.msrs.coalition~=NewCoalitionAirbase then + self.msrs:SetCoalition(NewCoalitionAirbase) + end + + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index d4253971c..9be975a78 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -150,6 +150,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation) self:SetFrequencies(Frequency) self:SetModulations(Modulation) self:SetGender() + self:SetCoalition() return self end @@ -207,6 +208,22 @@ function MSRS:GetPort() return self.port end +--- Set coalition. +-- @param #MSRS self +-- @param #number Coalition Coalition. Default 0. +-- @return #MSRS self +function MSRS:SetCoalition(Coalition) + self.coalition=Coalition or 0 +end + +--- Get coalition. +-- @param #MSRS self +-- @return #number Coalition. +function MSRS:GetCoalition() + return self.coalition +end + + --- Set frequencies. -- @param #MSRS self -- @param #table Frequencies Frequencies in MHz. Can also be given as a #number if only one frequency should be used. From 231c5bfea77852d436fb5b221bc5f1463d3e148b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 22 Jun 2021 12:18:25 +0200 Subject: [PATCH 305/382] Ops --- Moose Development/Moose/Core/Set.lua | 31 ++++++++++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 8 ++++- Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 19 +++++++++++- Moose Development/Moose/Ops/OpsTransport.lua | 20 +++++++++++++ 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index fbcf05e45..7dfdfca49 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1400,6 +1400,22 @@ do -- SET_GROUP return self end + --- Activate late activated groups. + -- @param #SET_GROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_GROUP self + function SET_GROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + local group=GroupData --Wrapper.Group#GROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end + + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. @@ -6411,6 +6427,21 @@ do -- SET_OPSGROUP return self end + + --- Activate late activated groups in the set. + -- @param #SET_OPSGROUP self + -- @param #number Delay Delay in seconds. + -- @return #SET_OPSGROUP self + function SET_OPSGROUP:Activate(Delay) + local Set = self:GetSet() + for GroupID, GroupData in pairs(Set) do + local group=GroupData --Ops.OpsGroup#OPSGROUP + if group and group:IsAlive()==false then + group:Activate(Delay) + end + end + return self + end --- Handles the OnDead or OnCrash event for alive groups set. -- Note: The GROUP object in the SET_OPSGROUP collection will only be removed if the last unit is destroyed of the GROUP. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c4b916448..bcf8227e7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -417,6 +417,9 @@ end -- @param Wrapper.Airbase#AIRBASE HomeAirbase The home airbase. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetHomebase(HomeAirbase) + if type(HomeAirbase)=="string" then + HomeAirbase=AIRBASE:FindByName(HomeAirbase) + end self.homebase=HomeAirbase return self end @@ -426,6 +429,9 @@ end -- @param Wrapper.Airbase#AIRBASE DestinationAirbase The destination airbase. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) + if type(DestinationAirbase)=="string" then + DestinationAirbase=AIRBASE:FindByName(DestinationAirbase) + end self.destbase=DestinationAirbase return self end @@ -730,7 +736,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) end self:I(self.lid.."Starting uncontrolled group") self.group:StartUncontrolled(_delay) - self.isUncontrolled=true + self.isUncontrolled=false else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 4f233faac..fb836d489 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1005,7 +1005,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) Depth=Depth or 50 - self:T(self.lid..string.format("Diving to %d meters", Depth)) + self:I(self.lid..string.format("Diving to %d meters", Depth)) self.depth=Depth diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6f8412eb5..2792b93f5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5447,6 +5447,8 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then + + env.info("FF pickup is flightgroup") if airbasePickup then @@ -5461,10 +5463,13 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Activate uncontrolled group. if self:IsParking() then + env.info("FF pickup start uncontrolled while parking at current airbase") self:StartUncontrolled() end else + + env.info("FF pickup land at airbase") -- Order group to land at an airbase. self:LandAtAirbase(airbasePickup) @@ -5477,6 +5482,14 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- + env.info("FF pickup helo addwaypoint") + + -- Activate uncontrolled group. + if self:IsParking() then + env.info("FF pickup start uncontrolled while parking airbase") + self:StartUncontrolled() + end + -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) @@ -5485,6 +5498,7 @@ function OPSGROUP:onafterPickup(From, Event, To) else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") end + elseif self.isNavygroup then -- Navy Group @@ -5656,8 +5670,11 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) CargoGroup:Despawn(0, true) end - -- Trigger embarked event. + -- Trigger embarked event for cargo group. CargoGroup:Embarked(self, Carrier) + + -- Trigger "Loaded" event for current cargo transport. + self.cargoTransport:Loaded(CargoGroup, Carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 92a9ff9ff..4eca27af6 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -415,6 +415,15 @@ function OPSTRANSPORT:SetPriority(Prio, Importance, Urgent) return self end +--- Set verbosity. +-- @param #OPSTRANSPORT self +-- @param #number Verbosity Be more verbose. Default 0 +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetVerbosity(Verbosity) + self.verbose=Verbosity or 0 + return self +end + --- Add start condition. -- @param #OPSTRANSPORT self -- @param #function ConditionFunction Function that needs to be true before the transport can be started. Must return a #boolean. @@ -674,6 +683,17 @@ function OPSTRANSPORT:onafterDelivered(From, Event, To) end +--- On after "Loaded" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP.Element Carrier Carrier element. +function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroup, Carrier) + self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroup:GetName(), tostring(Carrier.name))) +end + --- On after "Unloaded" event. -- @param #OPSTRANSPORT self -- @param #string From From state. From 0cd1cd97a67eee08e266944a64029e1d9ff0100f Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 13:11:12 +0200 Subject: [PATCH 306/382] Update CSAR.lua (#1553) --- Moose Development/Moose/Ops/CSAR.lua | 98 +++++++++++++++------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8d6e7292c..ad6ae1a97 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -14,7 +14,7 @@ -- -- **Main Features:** -- --- * MOOSE based Helicopter CSAR Operations. +-- * MOOSE-based Helicopter CSAR Operations for Players. -- -- === -- @@ -41,13 +41,14 @@ -- -- # CSAR Concept -- --- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * MOOSE-based Helicopter CSAR Operations for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script. -- * No need for extra MIST loading. -- * Additional events to tailor your mission. -- -- ## 0. Prerequisites -- --- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. -- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup @@ -66,19 +67,19 @@ -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! --- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. +-- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. --- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = false -- set to false to disable AI pilots from being rescued. -- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. --- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. @@ -202,18 +203,21 @@ CSAR = { -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process ---- Known beacons from the available maps +--- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. -- @field #CSAR.SkipFrequencies CSAR.SkipFrequencies = { - 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, - 440,795,525,520,690,625,291.5,300.50, - 435,309.50,920,1065,274,312.50, - 580,602,297.50,750,485,950,214, - 1025,730,995,455,307,670,329,395,770, - 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, - 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, - 337,312.5,830,740,309.5,641,312,722,682,1050, - 1116,935,1000,430,577,540,550,560,570, + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 } --- All slot / Limit settings @@ -231,7 +235,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r3" +CSAR.version="0.1.3r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -325,9 +329,9 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH self.rescuedpilots = 0 -- counter for saved pilots - self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. - self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. - self.enableForAI = true -- set to false to disable AI units from being rescued. + self.csarOncrash = false -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined arms. + self.enableForAI = false -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal @@ -339,18 +343,18 @@ function CSAR:New(Coalition, Template, Alias) self.loadtimemax = 135 -- seconds self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase - self.max_units = 6 --number of pilots that can be carried + self.max_units = 6 --max number of pilots that can be carried self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below - self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near - -- WARNING - here'll be dragons + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) - self.useSRS = false -- Use FF's SRS integration + self.useSRS = false -- Use FF\'s SRS integration self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation @@ -403,7 +407,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Boarded" event. Downed pilot boarded heli. -- @function [parent=#CSAR] OnAfterBoarded @@ -412,7 +416,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -421,7 +425,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string Event Event. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. - -- @param #string Woundedgroupname Name of the downed pilot's group. + -- @param #string Woundedgroupname Name of the downed pilot\'s group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -628,7 +632,7 @@ end -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. --- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() @@ -877,7 +881,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) if _pilot.name == name then local group = _pilot.group -- Wrapper.Group#GROUP if group then - if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist found = true _pilot.desc = nil _pilot.frequency = nil @@ -933,13 +937,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _distance = self:_GetDistance(_heliCoord,_leaderCoord) if _distance < 3000 and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then - -- we're close, reschedule + -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end else self.heliVisibleMessage[_lookupKeyHeli] = nil - --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) end @@ -949,7 +953,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end end ---- Function to pop a smoke at a wounded pilot's positions. +--- Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. @@ -985,13 +989,13 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _groups = self.inTransitGroups[_heliName] end - -- if the heli can't pick them up, show a message and return + -- if the heli can\'t pick them up, show a message and return local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] if _maxUnits == nil then _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,7 +1014,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) @@ -1068,9 +1072,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1079,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG -- have we landed close enough? if not _heliUnit:InAir() then - -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + -- if you land on them, doesnt matter if they were heading to someone else as you\'re closer, you win! :) if self.pilotRuntoExtractPoint == true then if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] @@ -1134,7 +1138,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1290,7 +1294,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) end end ---- Function to get string of a group's position. +--- Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text @@ -1672,7 +1676,7 @@ function CSAR:_GenerateVHFrequencies() -- third range _start = 850000 - while _start <= 1250000 do + while _start <= 999000 do -- updated for Gazelle -- skip existing NDB frequencies local _found = false @@ -1819,7 +1823,7 @@ function CSAR:onbeforeStatus(From, Event, To) local name = entry.name local timestamp = entry.timestamp or 0 local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. self:_CheckWoundedGroupStatus(_sar,name) end end @@ -1894,7 +1898,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) @@ -1907,7 +1911,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) @@ -1920,7 +1924,7 @@ end -- @param #string Event Event triggered. -- @param #string To To state. -- @param #string Heliname Name of the helicopter group. --- @param #string Woundedgroupname Name of the downed pilot's group. +-- @param #string Woundedgroupname Name of the downed pilot\'s group. function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) self:T({From, Event, To, Heliname, Woundedgroupname}) self:_ScheduledSARFlight(Heliname,Woundedgroupname) From f235037cb9999beb6752d7de27b419c9bb235738 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:11 +0200 Subject: [PATCH 307/382] Create CTLD.lua (#1555) Player Heli Cargo and Troops Ops :) --- Moose Development/Moose/Ops/CTLD.lua | 2322 ++++++++++++++++++++++++++ 1 file changed, 2322 insertions(+) create mode 100644 Moose Development/Moose/Ops/CTLD.lua diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua new file mode 100644 index 000000000..38640518f --- /dev/null +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -0,0 +1,2322 @@ +--- **Ops** -- Combat Troops & Logistics Deployment. +-- +-- === +-- +-- **CTLD** - MOOSE based Helicopter CTLD Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE-based Helicopter CTLD Operations for Players. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CTLD +-- @image OPS_CTLD.jpg + +-- Date: June 2021 + +do +------------------------------------------------------ +--- **CTLD_CARGO** class, extends #Core.Base#BASE +-- @type CTLD_CARGO +-- @field #number ID ID of this cargo. +-- @field #string Name Name for menu. +-- @field #table Templates Table of #POSITIONABLE objects. +-- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #boolean HasBeenMoved Flag for moving. +-- @field #boolean LoadDirectly Flag for direct loading. +-- @field #number CratesNeeded Crates needed to build. +-- @field Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. +-- @field #boolean HasBeenDropped True if dropped from heli. +-- @extends Core.Fsm#FSM +CTLD_CARGO = { + ClassName = "CTLD_CARGO", + ID = 0, + Name = "none", + Templates = {}, + CargoType = "none", + HasBeenMoved = false, + LoadDirectly = false, + CratesNeeded = 0, + Positionable = nil, + HasBeenDropped = false, + } + + --- Define cargo types. + -- @type CTLD_CARGO.Enum + -- @field #string Type Type of Cargo. + CTLD_CARGO.Enum = { + VEHICLE = "Vehicle", -- #string vehicles + TROOPS = "Troops", -- #string troops + FOB = "FOB", -- #string FOB + CRATE = "CRATE", -- #string crate + } + + --- Function to create new CTLD_CARGO object. + -- @param #CTLD_CARGO self + -- @param #number ID ID of this #CTLD_CARGO + -- @param #string Name Name for menu. + -- @param #table Templates Table of #POSITIONABLE objects. + -- @param #CTLD_CARGO.Enum Sorte Enumerator of Type. + -- @param #boolean HasBeenMoved Flag for moving. + -- @param #boolean LoadDirectly Flag for direct loading. + -- @param #number CratesNeeded Crates needed to build. + -- @param Wrapper.Positionable#POSITIONABLE Positionable Representation of cargo in the mission. + -- @param #boolean Dropped Cargo/Troops have been unloaded from a chopper. + -- @return #CTLD_CARGO self + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped) + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #CTLD + self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) + self.ID = ID or math.random(100000,1000000) + self.Name = Name or "none" -- #string + self.Templates = Templates or {} -- #table + self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum + self.HasBeenMoved = HasBeenMoved or false -- #booolean + self.LoadDirectly = LoadDirectly or false -- #booolean + self.CratesNeeded = CratesNeeded or 0 -- #number + self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE + self.HasBeenDropped = Dropped or false --#boolean + return self + end + + --- Query ID. + -- @param #CTLD_CARGO self + -- @return #number ID + function CTLD_CARGO:GetID() + return self.ID + end + + --- Query Name. + -- @param #CTLD_CARGO self + -- @return #string Name + function CTLD_CARGO:GetName() + return self.Name + end + + --- Query Templates. + -- @param #CTLD_CARGO self + -- @return #table Templates + function CTLD_CARGO:GetTemplates() + return self.Templates + end + + --- Query has moved. + -- @param #CTLD_CARGO self + -- @return #boolean Has moved + function CTLD_CARGO:HasMoved() + return self.HasBeenMoved + end + + --- Query was dropped. + -- @param #CTLD_CARGO self + -- @return #boolean Has been dropped. + function CTLD_CARGO:WasDropped() + return self.HasBeenDropped + end + + --- Query directly loadable. + -- @param #CTLD_CARGO self + -- @return #boolean loadable + function CTLD_CARGO:CanLoadDirectly() + return self.LoadDirectly + end + + --- Query number of crates or troopsize. + -- @param #CTLD_CARGO self + -- @return #number Crates or size of troops. + function CTLD_CARGO:GetCratesNeeded() + return self.CratesNeeded + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return #CTLD_CARGO.Enum Type + function CTLD_CARGO:GetType() + return self.CargoType + end + + --- Query type. + -- @param #CTLD_CARGO self + -- @return Wrapper.Positionable#POSITIONABLE Positionable + function CTLD_CARGO:GetPositionable() + return self.Positionable + end + + --- Set HasMoved. + -- @param #CTLD_CARGO self + -- @param #boolean moved + function CTLD_CARGO:SetHasMoved(moved) + self.HasBeenMoved = moved or false + end + + --- Query if cargo has been loaded. + -- @param #CTLD_CARGO self + -- @param #boolean loaded + function CTLD_CARGO:Isloaded() + if self.HasBeenMoved and not self.WasDropped() then + return true + else + return false + end + end + --- Set WasDropped. + -- @param #CTLD_CARGO self + -- @param #boolean dropped + function CTLD_CARGO:SetWasDropped(dropped) + self.HasBeenDropped = dropped or false + end + +end + +do +------------------------------------------------------------------------- +--- **CTLD** class, extends #Core.Base#BASE, #Core.Fsm#FSM +-- @type CTLD +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat Troop & Logistics Deployment (CTLD): Everyone wants to be a POG, until there\'s POG stuff to be done.* (Mil Saying) +-- +-- === +-- +-- ![Banner Image](OPS_CTLD.jpg) +-- +-- # CTLD Concept +-- +-- * MOOSE-based CTLD for Players. +-- * Object oriented refactoring of Ciribob\'s fantastic CTLD script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- * ANY late activated group can serve as cargo, either as troops or crates, which have to be build on-location. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot\'s beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create the late-activated troops, vehicles (no statics at this point!) that will make up your deployable forces. +-- +-- ## 1. Basic Setup +-- +-- ## 1.1 Create and start a CTLD instance +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CTLD for the blue side, using helicopter groups named "Helicargo" and alias "Lufttransportbrigade I" +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo"},"Lufttransportbrigade I") +-- my_ctld:__Start(5) +-- +-- ## 1.2 Add cargo types available +-- +-- Add *generic* cargo types that you need for your missions, here infantry units, vehicles and a FOB. These need to be late-activated Wrapper.Group#GROUP objects: +-- +-- -- add infantry unit called "Anti-Tank Small" using template "ATS", of type TROOP with size 3 +-- -- infantry units will be loaded directly from LOAD zones into the heli (matching number of free seats needed) +-- my_ctld:AddTroopsCargo("Anti-Tank Small",{"ATS"},CTLD_CARGO.Enum.TROOPS,3) +-- +-- -- add infantry unit called "Anti-Tank" using templates "AA" and "AA"", of type TROOP with size 4 +-- my_ctld:AddTroopsCargo("Anti-Air",{"AA","AA2"},CTLD_CARGO.Enum.TROOPS,4) +-- +-- -- add vehicle called "Humvee" using template "Humvee", of type VEHICLE, size 2, i.e. needs two crates to be build +-- -- vehicles and FOB will be spawned as crates in a LOAD zone first. Once transported to DROP zones, they can be build into the objects +-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2) +-- +-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: +-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) +-- +-- ## 1.3 Add logistics zones +-- +-- Add zones for loading troops and crates and dropping, building crates +-- +-- -- Add a zone of type LOAD to our setup. Players can load troops and crates. +-- -- "Loadzone" is the name of the zone from the ME. Players can load, if they are inside of the zone. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Loadzone",CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- +-- -- Add a zone of type DROP. Players can drop crates here. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- -- NOTE: Troops can be unloaded anywhere, also when hovering in parameters. +-- my_ctld:AddCTLDZone("Dropzone",CTLD.CargoZoneType.DROP,SMOKECOLOR.Red,true,true) +-- +-- -- Add two zones of type MOVE. Dropped troops and vehicles will move to the nearest one. See options. +-- -- Smoke and Flare color for this zone is blue, it is active (can be used) and has a radio beacon. +-- my_ctld:AddCTLDZone("Movezone",CTLD.CargoZoneType.MOVE,SMOKECOLOR.Orange,false,false) +-- +-- my_ctld:AddCTLDZone("Movezone2",CTLD.CargoZoneType.MOVE,SMOKECOLOR.White,true,true) +-- +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults). Only set the ones you want changed: +-- +-- my_ctld.useprefix = true -- Adjust *before* starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. +-- my_ctld.hoverautoloading = true -- Crates in CrateDistance in a LOAD zone will be loaded automatically if space allows. +-- my_ctld.smokedistance = 2000 -- Smoke or flares can be request for zones this far away (in meters). +-- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... +-- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) +-- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- +-- ## 2.1 User functions +-- +-- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- +-- Use this function to adjust what a heli type can or cannot do: +-- +-- -- E.g. update unit capabilities for testing. Please stay realistic in your mission design. +-- -- Make a Gazelle into a heavy truck, this type can load both crates and troops and eight of each type: +-- my_ctld:UnitCapabilities("SA342L", true, true, 8, 8) +-- +-- Default unit type capabilities are: +-- +-- ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, +-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, +-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, +-- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, +-- +-- +-- ### 2.1.2 Activate and deactivate zones +-- +-- Activate a zone: +-- +-- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:ActivateZone(Name,ZoneType) +-- +-- Deactivate a zone: +-- +-- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: +-- my_ctld:DeactivateZone(Name,ZoneType) +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ## 3.1 OnAfterTroopsPickedUp +-- +-- This function is called when a player has loaded Troops: +-- +-- function CTLD:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.2 OnAfterCratesPickedUp +-- +-- This function is called when a player has picked up crates: +-- +-- function CTLD:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- ... your code here ... +-- end +-- +-- ## 3.3 OnAfterTroopsTroopsDeployed +-- +-- This function is called when a player has deployed troops into the field: +-- +-- function CTLD:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.4 OnAfterTroopsCratesDropped +-- +-- This function is called when a player has deployed crates to a DROP zone: +-- +-- function CTLD:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterTroopsCratesBuild +-- +-- This function is called when a player has build a vehicle or FOB: +-- +-- function CTLD:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... +-- end +-- +-- ## 4. F10 Menu structure +-- +-- CTLD management menu is under the F10 top menu and called "CTLD" +-- +-- ## 4.1 Manage Crates +-- +-- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- +-- ## 4.2 Manage Troops +-- +-- Use this entry to load and drop troops. +-- +-- ## 4.3 List boarded cargo +-- +-- Lists what you have loaded. Shows load capabilities for number of crates and number of seats for troops. +-- +-- ## 4.4 Smoke & Flare zones nearby +-- +-- Does what it says. +-- +-- ## 4.5 List active zone beacons +-- +-- Lists active radio beacons for all zones, where zones are both active and have a beacon. @see `CTLD:AddCTLDZone()` +-- +-- ## 4.6 Show hover parameters +-- +-- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. +-- +-- @field #CTLD +CTLD = { + ClassName = "CTLD", + verbose = 2, + lid = "", + coalition = 1, + coalitiontxt = "blue", + PilotGroups = {}, -- #GROUP_SET of heli pilots + CtldUnits = {}, -- Table of helicopter #GROUPs + FreeVHFFrequencies = {}, -- Table of VHF + FreeUHFFrequencies = {}, -- Table of UHF + FreeFMFrequencies = {}, -- Table of FM + CargoCounter = 0, + dropOffZones = {}, + wpZones = {}, + Cargo_Troops = {}, -- generic troops objects + Cargo_Crates = {}, -- generic crate objects + Loaded_Cargo = {}, -- cargo aboard units + Spawned_Crates = {}, -- Holds objects for crates spawned generally + Spawned_Cargo = {}, -- Binds together spawned_crates and their CTLD_CARGO objects + CrateDistance = 30, -- list crates in this radius + debug = false, + wpZones = {}, + pickupZones = {}, + dropOffZones = {}, +} + +------------------------------ +-- DONE: Zone Checks +-- DONE: TEST Hover load and unload +-- DONE: Crate unload +-- DONE: Hover (auto-)load +-- TODO: (More) Housekeeping +-- DONE: Troops running to WP Zone +-- DONE: Zone Radio Beacons +-- DONE: Stats Running +------------------------------ + +--- Radio Beacons +-- @type CTLD.ZoneBeacon +-- @field #string name -- Name of zone for the coordinate +-- @field #number frequency -- in mHz +-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM + +--- Zone Info. +-- @type CTLD.CargoZone +-- @field #string name Name of Zone. +-- @field #string color Smoke color for zone, e.g. SMOKECOLOR.Red. +-- @field #boolean active Active or not. +-- @field #string type Type of zone, i.e. load,drop,move +-- @field #boolean hasbeacon Create and run radio beacons if active. +-- @field #table fmbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table uhfbeacon Beacon info as #CTLD.ZoneBeacon +-- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon + +--- Zone Type Info. +-- @type CTLD. +CTLD.CargoZoneType = { + LOAD = "load", + DROP = "drop", + MOVE = "move", +} + +--- Buildable table info. +-- @type CTLD.Buildable +-- @field #string Name Name of the object. +-- @field #number Required Required crates. +-- @field #number Found Found crates. +-- @field #table Template Template names for this build. +-- @field #boolean CanBuild Is buildable or not. +-- @field #CTLD_CARGO.Enum Type Type enumerator (for moves). + +--- Unit capabilities. +-- @type CTLD.UnitCapabilities +-- @field #string type Unit type. +-- @field #boolean crates Can transport crate. +-- @field #boolean troops Can transport troops. +-- @field #number cratelimit Number of crates transportable. +-- @field #number trooplimit Number of troop units transportable. +CTLD.UnitTypes = { + ["SA342Mistral"] = {type="SA342Mistral", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342L"] = {type="SA342L", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, + ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, + ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, + ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, +} + +--- Updated and sorted known NDB beacons (in kHz!) from the available maps +-- @field #CTLD.SkipFrequencies +CTLD.SkipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 + } + +--- CTLD class version. +-- @field #string version +CTLD.version="0.1.1b1" + +--- Instantiate a new CTLD. +-- @param #CTLD self +-- @param #string Coalition Coalition of this CTLD. I.e. coalition.side.BLUE or coalition.side.RED or coalition.side.NEUTRAL +-- @param #table Prefixes Table of pilot prefixes. +-- @param #string Alias Alias of this CTLD for logging. +-- @return #CTLD self +function CTLD:New(Coalition, Prefixes, Alias) + -- TODO: CTLD Marker + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CTLD + + BASE:T({Coalition, Prefixes, Alias}) + + --set Coalition + if Coalition and type(Coalition)=="string" then + if Coalition=="blue" then + self.coalition=coalition.side.BLUE + self.coalitiontxt = Coalition + elseif Coalition=="red" then + self.coalition=coalition.side.RED + self.coalitiontxt = Coalition + elseif Coalition=="neutral" then + self.coalition=coalition.side.NEUTRAL + self.coalitiontxt = Coalition + else + self:E("ERROR: Unknown coalition in CTLD!") + end + else + self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) + end + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="UNHCR" + if self.coalition then + if self.coalition==coalition.side.RED then + self.alias="Red CTLD" + elseif self.coalition==coalition.side.BLUE then + self.alias="Blue CTLD" + end + end + end + + -- Set some string id for output to DCS.log file. + self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables + self.PilotGroups ={} + self.CtldUnits = {} + + -- Beacons + self.FreeVHFFrequencies = {} + self.FreeUHFFrequencies = {} + self.FreeFMFrequencies = {} + self.UsedVHFFrequencies = {} + self.UsedUHFFrequencies = {} + self.UsedFMFrequencies = {} + --self.jtacGeneratedLaserCodes = {} + + -- radio beacons + self.RadioSound = "beacon.ogg" + + -- zones stuff + self.pickupZones = {} + self.dropOffZones = {} + self.wpZones = {} + + -- Cargo + self.Cargo_Crates = {} + self.Cargo_Troops = {} + self.Loaded_Cargo = {} + self.Spawned_Crates = {} + self.Spawned_Cargo = {} + self.MenusDone = {} + self.DroppedTroops = {} + self.DroppedCrates = {} + self.CargoCounter = 0 + self.CrateCounter = 0 + self.TroopCounter = 0 + + -- setup + self.CrateDistance = 30 -- list/load crates in this radius + self.prefixes = Prefixes or {"cargoheli"} + self.useprefix = true + + self.maximumHoverHeight = 15 + self.minimumHoverHeight = 4 + self.forcehoverload = true + self.hoverautoloading = true + + self.smokedistance = 2000 + self.movetroopstowpzone = true + self.movetroopsdistance = 5000 + + for i=1,100 do + math.random() + end + + self:_GenerateVHFrequencies() + self:_GenerateUHFrequencies() + self:_GenerateFMFrequencies() + --self:_GenerateLaserCodes() -- curr unused + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] Start + -- @param #CTLD self + + --- Triggers the FSM event "Start" after a delay. Starts the CTLD. Initializes parameters and starts event handlers. + -- @function [parent=#CTLD] __Start + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CTLD and all its event handlers. + -- @param #CTLD self + + --- Triggers the FSM event "Stop" after a delay. Stops the CTLD and all its event handlers. + -- @function [parent=#CTLD] __Stop + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CTLD] Status + -- @param #CTLD self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CTLD] __Status + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- FSM Function OnAfterTroopsPickedUp. + -- @function [parent=#CTLD] OnAfterTroopsPickedUp + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterCratesPickedUp. + -- @function [parent=#CTLD] OnAfterCratesPickedUp + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + + --- FSM Function OnAfterTroopsDeployed. + -- @function [parent=#CTLD] OnAfterTroopsDeployed + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + + --- FSM Function OnAfterCratesDropped. + -- @function [parent=#CTLD] OnAfterCratesDropped + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + + --- FSM Function OnAfterCratesBuild. + -- @function [parent=#CTLD] OnAfterCratesBuild + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + + return self +end + +------------------------------------------------------------------- +-- Helper and User Functions +------------------------------------------------------------------- + +--- Function to generate valid UHF Frequencies +-- @param #CTLD self +function CTLD:_GenerateUHFrequencies() + self:T(self.lid .. " _GenerateUHFrequencies") + self.FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(self.FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return self +end + +--- Function to generate valid FM Frequencies +-- @param #CTLD sel +function CTLD:_GenerateFMFrequencies() + self:T(self.lid .. " _GenerateFMrequencies") + self.FreeFMFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + + _start = _start + 500000 + end + + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(self.FreeFMFrequencies, _frequency) + end + end + end + + return self +end + +--- Populate table with available VHF beacon frequencies. +-- @param #CTLD self +function CTLD:_GenerateVHFrequencies() + self:T(self.lid .. " _GenerateVHFrequencies") + local _skipFrequencies = self.SkipFrequencies + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(self.FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return self +end + +--- Function to generate valid laser codes. +-- @param #CTLD self +function CTLD:_GenerateLaserCodes() + self:T(self.lid .. " _GenerateLaserCodes") + self.jtacGeneratedLaserCodes = {} + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not self:_ContainsDigit(_code, 9) + and not self:_ContainsDigit(_code, 0) then + table.insert(self.jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end +end + +--- Helper function to generate laser codes. +-- @param #CTLD self +-- @param #number _number +-- @param #number _numberToFind +function CTLD:_ContainsDigit(_number, _numberToFind) + self:T(self.lid .. " _ContainsDigit") + local _thisNumber = _number + local _thisDigit = 0 + while _thisNumber ~= 0 do + _thisDigit = _thisNumber % 10 + _thisNumber = math.floor(_thisNumber / 10) + if _thisDigit == _numberToFind then + return true + end + end + return false +end + +--- Event handler function +-- @param #CTLD self +-- @param Core.Event#EVENTDATA EventData +function CTLD:_EventHandler(EventData) + -- TODO: events dead and playerleaveunit - nil table entries + self:T(string.format("%s Event = %d",self.lid, EventData.id)) + local event = EventData -- Core.Event#EVENTDATA + if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then + local _coalition = event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + -- check is Helicopter + local _unit = event.IniUnit + local _group = event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_RefreshF10Menus() + end + return + elseif event.id == EVENTS.PlayerLeaveUnit then + -- remove from pilot table + local unitname = event.IniUnitName or "none" + self.CtldUnits[unitname] = nil + end + return self +end + +--- Function to load troops into a heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargotype +function CTLD:_LoadTroops(Group, Unit, Cargotype) + self:T(self.lid .. " _LoadTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + -- check if we are in LOAD zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if not inzone then + local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + elseif not grounded and not hoverload then + local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + -- load troops into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + local cargotype = Cargotype -- #CTLD_CARGO + local cratename = cargotype:GetName() -- #string + self:T(self.lid .. string.format("Troops %s requested", cratename)) + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local troopsize = cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + return + else + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,Cargotype) + self.Loaded_Cargo[unitname] = loaded + local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:__TroopsPickedUp(1,Group, Unit, Cargotype) + end + return self +end + +--- Function to spawn crates in front of the heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #CTLD_CARGO Cargo +-- @param #number number Number of crates to generate (for dropping) +-- @param #boolean drop If true we\'re dropping from heli rather than loading. +function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) + self:T(self.lid .. " _GetCrates") + local cgoname = Cargo:GetName() + self:T({cgoname, number, drop}) + -- check if we are in LOAD zone + local inzone = true + + if drop then + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + else + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + end + + if not inzone then + local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then return self end + end + + -- avoid crate spam + local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local canloadcratesno = capabilities.cratelimit + local loaddist = self.CrateDistance or 30 + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) + if numbernearby >= canloadcratesno and not drop then + local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self + end + -- spawn crates in front of helicopter + local cargotype = Cargo -- #CTLD_CARGO + local number = number or cargotype:GetCratesNeeded() --#number + local cratesneeded = cargotype:GetCratesNeeded() --#number + local cratename = cargotype:GetName() + self:T(self.lid .. string.format("Crate %s requested", cratename)) + local cratetemplate = "Container"-- #string + -- get position and heading of heli + local position = Unit:GetCoordinate() + local heading = Unit:GetHeading() + 1 + local height = Unit:GetHeight() + local droppedcargo = {} + -- loop crates needed + for i=1,number do + local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) + local cratedistance = i*4 + 6 + local rheading = math.floor(math.random(90,270) * heading + 1 / 360) + local rheading = rheading + 180 -- mirror + if rheading > 360 then rheading = rheading - 360 end -- catch > 360 + local cratecoord = position:Translate(cratedistance,rheading) + local cratevec2 = cratecoord:GetVec2() + self.CrateCounter = self.CrateCounter + 1 + self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType("container_cargo","Cargos",country.id.GERMANY) + :InitCoordinate(cratecoord) + :Spawn(270,cratealias) + + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + self.CargoCounter = self.CargoCounter +1 + local realcargo = nil + if drop then + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true) + table.insert(droppedcargo,realcargo) + else + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter]) + end + table.insert(self.Spawned_Cargo, realcargo) + end + local text = string.format("Crates for %s have been positioned near you!",cratename) + if drop then + text = string.format("Crates for %s have been dropped!",cratename) + self:__CratesDropped(1, Group, Unit, droppedcargo) + end + local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + return self +end + +--- Function to find and list nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCratesNearby( _group, _unit) + self:T(self.lid .. " _ListCratesNearby") + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(_group,_unit, finddist) -- #table + if number > 0 then + local text = REPORT:New("Crates Found Nearby:") + text:Add("------------------------------------------------------------") + for _,_entry in pairs (crates) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + -- TODO Meaningful sorting/aggregation + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s",name)) + else + text:Add(string.format("Crate for %s",name)) + end + end + if text:GetCount() == 1 then + text:Add("--------- N O N E ------------") + end + text:Add("------------------------------------------------------------") + local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + else + local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + end + return self +end + +--- Return distance in meters between two coordinates. +-- @param #CTLD self +-- @param Core.Point#COORDINATE _point1 Coordinate one +-- @param Core.Point#COORDINATE _point2 Coordinate two +-- @return #number Distance in meters +function CTLD:_GetDistance(_point1, _point2) + self:T(self.lid .. " _GetDistance") + if _point1 and _point2 then + local distance = _point1:DistanceFromPointVec2(_point2) + return distance + else + return -1 + end +end + +--- Function to find and return nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP _group Group +-- @param Wrapper.Unit#UNIT _unit Unit +-- @param #number _dist Distance +-- @return #table Table of crates +-- @return #number Number Number of crates found +function CTLD:_FindCratesNearby( _group, _unit, _dist) + self:T(self.lid .. " _FindCratesNearby") + local finddist = _dist + local location = _group:GetCoordinate() + local existingcrates = self.Spawned_Cargo -- #table + -- cycle + local index = 0 + local found = {} + for _,_cargoobject in pairs (existingcrates) do + local cargo = _cargoobject -- #CTLD_CARGO + local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates + local staticid = cargo:GetID() + if static and static:IsAlive() then + local staticpos = static:GetCoordinate() + local distance = self:_GetDistance(location,staticpos) + if distance <= finddist and static then + index = index + 1 + table.insert(found, staticid, cargo) + end + end + end + self:T(string.format("Found crates = %d",index)) + -- table.sort(found) + --self:T({found}) + return found, index +end + +--- Function to get and load nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_LoadCratesNearby(Group, Unit) + self:T(self.lid .. " _LoadCratesNearby") + -- load crates into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + local grounded = not self:IsUnitInAir(Unit) + local canhoverload = self:CanHoverLoad(Unit) + --- cases ------------------------------- + -- Chopper can\'t do crates - bark & return + -- Chopper can do crates - + -- --> hover if forcedhover or bark and return + -- --> hover or land if not forcedhover + ----------------------------------------- + if not cancrates then + local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + elseif self.forcehoverload and not canhoverload then + local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + elseif not grounded and not canhoverload then + local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + else + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + -- get nearby crates + local finddist = self.CrateDistance or 30 + local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table + if number == 0 or numberonboard == cratelimit then + local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + return -- exit + else + -- go through crates and load + local capacity = cratelimit - numberonboard + local crateidsloaded = {} + local loops = 0 + while loaded.Cratesloaded < cratelimit and loops < number do + --for _ind,_crate in pairs (nearcrates) do + loops = loops + 1 + local crateind = 0 + -- get crate with largest index + for _ind,_crate in pairs (nearcrates) do + if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then + --crate = _crate + crateind = _crate:GetID() + end + end + -- load one if we found one + if crateind > 0 then + local crate = nearcrates[crateind] -- #CTLD_CARGO + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded,crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy() + crate.Positionable = nil + local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:__CratesPickedUp(1, Group, Unit, crate) + end + --if loaded.Cratesloaded == cratelimit then break end + end + self.Loaded_Cargo[unitname] = loaded + -- clean up real world crates + local existingcrates = self.Spawned_Cargo -- #table + local newexcrates = {} + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(crateidsloaded) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + end + end + return self +end + +--- Function to list loaded cargo. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @return #CTLD self +function CTLD:_ListCargo(Group, Unit) + self:T(self.lid .. " _ListCargo") + local unitname = Unit:GetName() + local unittype = Unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local trooplimit = capabilities.trooplimit -- #boolean + local cratelimit = capabilities.cratelimit -- #numbe + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + if self.Loaded_Cargo[unitname] then + local no_troops = loadedcargo.Troopsloaded or 0 + local no_crates = loadedcargo.Cratesloaded or 0 + local cargotable = loadedcargo.Cargo or {} -- #table + local report = REPORT:New("Transport Checkout Sheet") + report:Add("------------------------------------------------------------") + report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) + report:Add("------------------------------------------------------------") + report:Add("-- TROOPS --") + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + end + end + if report:GetCount() == 4 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + report:Add("-- CRATES --") + local cratecount = 0 + for _,_cargo in pairs(cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + cratecount = cratecount + 1 + end + end + if cratecount == 0 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local text = report:Text() + local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + else + local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to unload troops from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_UnloadTroops(Group, Unit) + self:T(self.lid .. " _UnloadTroops") + -- check if we are in LOAD zone + local droppingatbase = false + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if inzone then + droppingatbase = true + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type == CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload troops + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local position = Group:GetCoordinate() + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(10,30):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + end -- template loop + cargo:SetWasDropped(true) + local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, name, self.DroppedTroops[self.TroopCounter]) + end -- if type end + end -- cargotable loop + else -- droppingatbase + local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + local cargotable = loadedcargo.Cargo or {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local dropped = cargo:WasDropped() + --local moved = cargo:HasMoved() + if type ~= CTLD_CARGO.Enum.TROOP and not dropped then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + 1 + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to unload crates from heli. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_UnloadCrates(Group, Unit) + self:T(self.lid .. " _UnloadCrates") + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end + end + -- check for hover unload + local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + -- check if we\'re landed + local grounded = not self:IsUnitInAir(Unit) + -- Get what we have loaded + local unitname = Unit:GetName() + if self.Loaded_Cargo[unitname] and (grounded or hoverunload) then + local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo + -- looking for troops + local cargotable = loadedcargo.Cargo + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if type ~= CTLD_CARGO.Enum.TROOPS and not cargo:WasDropped() then + -- unload crates + self:_GetCrates(Group, Unit, cargo, 1, true) + cargo:SetWasDropped(true) + cargo:SetHasMoved(true) + --local name cargo:GetName() + --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) + end + end + -- cleanup load list + local loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + for _,_cargo in pairs (cargotable) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + local size = cargo:GetCratesNeeded() + if type == CTLD_CARGO.Enum.TROOP then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Troopsloaded + size + end + end + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end + return self +end + +--- Function to build nearby crates. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_BuildCrates(Group, Unit) + self:T(self.lid .. " _BuildCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + foundbuilds = true + end + self:T({buildables = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Buildable Crates") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + self:T({name,needed,found,txtok}) + local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_CleanUpCrates(crates,build,number) + self:_BuildObjectFromCrates(Group,Unit,build) + end + end + end + else + local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + end -- number > 0 + return self +end + +--- Function to actually SPAWN buildables in the mission. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Group#UNIT Unit +-- @param #CTLD.Buildable Build +function CTLD:_BuildObjectFromCrates(Group,Unit,Build) + self:T(self.lid .. " _BuildObjectFromCrates") + -- Spawn-a-crate-content + local position = Unit:GetCoordinate() or Group:GetCoordinate() + local unitname = Unit:GetName() or Group:GetName() + local name = Build.Name + local type = Build.Type -- #CTLD_CARGO.Enum + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + local temptable = Build.Template or {} + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) + local randomcoord = zone:GetRandomCoordinate(20,50):GetVec2() + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end -- template loop + return self +end + +--- Function to move group to WP zone. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The Group to move. +function CTLD:_MoveGroupToZone(Group) + self:T(self.lid .. " _MoveGroupToZone") + local groupname = Group:GetName() or "none" + local groupcoord = Group:GetCoordinate() + self:T(self.lid .. " _MoveGroupToZone for " .. groupname) + -- Get closest zone of type + local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + self:T(string.format("Closest WP zone %s is %d meters",name,distance)) + if (distance <= self.movetroopsdistance) and zone then + -- yes, we can ;) + local groupname = Group:GetName() + self:T(string.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE + local coordinate = zonecoord:GetVec2() + self:T({coordinate=coordinate}) + Group:SetAIOn() + Group:OptionAlarmStateAuto() + Group:OptionDisperseOnAttack(30) + Group:OptionROEOpenFirePossible() + Group:RouteToVec2(coordinate,5) + end + return self +end + +--- Housekeeping - Cleanup crates when build +-- @param #CTLD self +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_CleanUpCrates(Crates,Build,Number) + self:T(self.lid .. " _CleanUpCrates") + -- clean up real world crates + local build = Build -- #CTLD.Buildable + self:T({Build = Build}) + local existingcrates = self.Spawned_Cargo -- #table of exising crates + local newexcrates = {} + -- get right number of crates to destroy + local numberdest = Build.Required + local nametype = Build.Name + local found = 0 + local rounds = Number + local destIDs = {} + + -- loop and find matching IDs in the set + for _,_crate in pairs(Crates) do + local nowcrate = _crate -- #CTLD_CARGO + local name = nowcrate:GetName() + self:T(string.format("Looking for Crate for %s", name)) + local thisID = nowcrate:GetID() + if name == nametype then -- matching crate type + table.insert(destIDs,thisID) + found = found + 1 + nowcrate:GetPositionable():Destroy() + nowcrate.Positionable = nil + self:T(string.format("%s Found %d Need %d", name, found, numberdest)) + end + if found == numberdest then break end -- got enough + end + self:T({destIDs}) + -- loop and remove from real world representation + for _,_crate in pairs(existingcrates) do + local excrate = _crate -- #CTLD_CARGO + local ID = excrate:GetID() + for _,_ID in pairs(destIDs) do + if ID ~= _ID then + table.insert(newexcrates,_crate) + end + end + end + + -- reset Spawned_Cargo + self.Spawned_Cargo = nil + self.Spawned_Cargo = newexcrates + return self +end + +--- Housekeeping - Function to refresh F10 menus. +-- @param #CTLD self +-- @return #CTLD self +function CTLD:_RefreshF10Menus() + self:T(self.lid .. " _RefreshF10Menus") + local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP + local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects + + -- rebuild units table + local _UnitList = {} + for _key, _group in pairs (PlayerTable) do + local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players + if _unit then + if _unit:IsAlive() then + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end -- end isAlive + end -- end if _unit + end -- end for + self.CtldUnits = _UnitList + + -- build unit menus + + local menucount = 0 + local menus = {} + for _, _unitName in pairs(self.CtldUnits) do + if not self.MenusDone[_unitName] then + local _unit = UNIT:FindByName(_unitName) -- Wrapper.Unit#UNIT + if _unit then + local _group = _unit:GetGroup() -- Wrapper.Group#GROUP + if _group then + -- get chopper capabilities + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cantroops = capabilities.troops + local cancrates = capabilities.crates + -- top menu + local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + local topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) + local toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) + local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokemenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",topmenu, self.SmokeZoneNearBy, self, _unit, true):Refresh() + -- sub menus + -- sub menu crates management + if cancrates then + local loadmenu = MENU_GROUP_COMMAND:New(_group,"Load crates",topcrates, self._LoadCratesNearby, self, _group, _unit) + local cratesmenu = MENU_GROUP:New(_group,"Get Crates",topcrates) + for _,_entry in pairs(self.Cargo_Crates) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Get crate for %s",entry.Name) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end + listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) + local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + end + -- sub menu troops management + if cantroops then + local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) + for _,_entry in pairs(self.Cargo_Troops) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end + local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + end + local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + self.MenusDone[_unitName] = true + end -- end group + end -- end unit + else -- menu build check + self:T(self.lid .. " Menus already done for this group!") + end -- end menu build check + end -- end for + return self + end + +--- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. +-- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) + self:T(self.lid .. " AddTroopsCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Troops are directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops) + table.insert(self.Cargo_Troops,cargo) + return self +end + +--- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #Name Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. +-- @param #number NoCrates Number of crates needed to build this cargo. +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) + self:T(self.lid .. " AddCratesCargo") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates) + table.insert(self.Cargo_Crates,cargo) + return self +end + +--- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. +-- @param #CTLD self +-- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. +function CTLD:AddZone(Zone) + self:T(self.lid .. " AddZone") + local zone = Zone -- #CTLD.CargoZone + if zone.type == CTLD.CargoZoneType.LOAD then + table.insert(self.pickupZones,zone) + self:T("Registered LOAD zone " .. zone.name) + elseif zone.type == CTLD.CargoZoneType.DROP then + table.insert(self.dropOffZones,zone) + self:T("Registered DROP zone " .. zone.name) + else + table.insert(self.wpZones,zone) + self:T("Registered MOVE zone " .. zone.name) + end + return self +end + +--- User function - Activate Name #CTLD.CargoZone.Type ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +-- @param #boolean NewState (Optional) Set to true to activate, false to switch off. +function CTLD:ActivateZone(Name,ZoneType,NewState) + self:T(self.lid .. " AddZone") + local newstate = true + -- set optional in case we\'re deactivating + if not NewState or NewState == false then + newstate = false + end + -- get correct table + local zone = ZoneType -- #CTLD.CargoZone + local table = {} + if zone.type == CTLD.CargoZoneType.LOAD then + table = self.pickupZones + elseif zone.type == CTLD.CargoZoneType.DROP then + table = self.dropOffZones + else + table = self.wpZones + end + -- loop table + for _,_zone in pairs(table) do + local thiszone = _zone --#CTLD.CargoZone + if thiszone.name == Name then + thiszone.active = newstate + break + end + end + return self +end + +--- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. +-- @param #CTLD self +-- @param #string Name Name of the zone to change in the ME. +-- @param #CTLD.CargoZoneTyp ZoneType Type of zone this belongs to. +function CTLD:DeactivateZone(Name,ZoneType) + self:T(self.lid .. " AddZone") + self:ActivateZone(Name,ZoneType,false) + return self +end + +--- Function to obtain a valid FM frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetFMBeacon(Name) + self:T(self.lid .. " _GetFMBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeFMFrequencies <= 1 then + self.FreeFMFrequencies = self.UsedFMFrequencies + self.UsedFMFrequencies = {} + end + --random + local FM = table.remove(self.FreeFMFrequencies, math.random(#self.FreeFMFrequencies)) + table.insert(self.UsedFMFrequencies, FM) + beacon.name = Name + beacon.frequency = FM / 1000000 + beacon.modulation = radio.modulation.FM + + return beacon +end + +--- Function to obtain a valid UHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetUHFBeacon(Name) + self:T(self.lid .. " _GetUHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeUHFFrequencies <= 1 then + self.FreeUHFFrequencies = self.UsedUHFFrequencies + self.UsedUHFFrequencies = {} + end + --random + local UHF = table.remove(self.FreeUHFFrequencies, math.random(#self.FreeUHFFrequencies)) + table.insert(self.UsedUHFFrequencies, UHF) + beacon.name = Name + beacon.frequency = UHF / 1000000 + beacon.modulation = radio.modulation.AM + + return beacon +end + +--- Function to obtain a valid VHF frequency. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @return #CTLD.ZoneBeacon Beacon Beacon table. +function CTLD:_GetVHFBeacon(Name) + self:T(self.lid .. " _GetVHFBeacon") + local beacon = {} -- #CTLD.ZoneBeacon + if #self.FreeVHFFrequencies <= 3 then + self.FreeVHFFrequencies = self.UsedVHFFrequencies + self.UsedVHFFrequencies = {} + end + --get random + local VHF = table.remove(self.FreeVHFFrequencies, math.random(#self.FreeVHFFrequencies)) + table.insert(self.UsedVHFFrequencies, VHF) + beacon.name = Name + beacon.frequency = VHF / 1000000 + beacon.modulation = radio.modulation.FM + return beacon +end + + +--- User function - Crates and adds a #CTLD.CargoZone zone for this CTLD instance. +-- Zones of type LOAD: Players load crates and troops here. +-- Zones of type DROP: Players can drop crates here. Note that troops can be unloaded anywhere. +-- Zone of type MOVE: Dropped troops and vehicles will start moving to the nearest zone of this type (also see options). +-- @param #CTLD self +-- @param #string Name Name of this zone, as in Mission Editor. +-- @param #string Type Type of this zone, #CTLD.CargoZoneType +-- @param #number Color Smoke/Flare color e.g. #SMOKECOLOR.Red +-- @param #string Active Is this zone currently active? +-- @param #string HasBeacon Does this zone have a beacon if it is active? +-- @return #CTLD self +function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) + self:T(self.lid .. " AddCTLDZone") + + local ctldzone = {} -- #CTLD.CargoZone + ctldzone.active = Active or false + ctldzone.color = Color or SMOKECOLOR.Red + ctldzone.name = Name or "NONE" + ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType + ctldzone.hasbeacon = HasBeacon or false + + if HasBeacon then + ctldzone.fmbeacon = self:_GetFMBeacon(Name) + ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) + ctldzone.vhfbeacon = self:_GetVHFBeacon(Name) + else + ctldzone.fmbeacon = nil + ctldzone.uhfbeacon = nil + ctldzone.vhfbeacon = nil + end + + self:AddZone(ctldzone) + return self +end + +--- Function to show list of radio beacons +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +function CTLD:_ListRadioBeacons(Group, Unit) + self:T(self.lid .. " _ListRadioBeacons") + local report = REPORT:New("Active Zone Beacons") + report:Add("------------------------------------------------------------") + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency * 1000 -- KHz + local UHF = UHFbeacon.frequency -- MHz + report:AddIndent(string.format(" %s | FM %s Mhz | VHF %s KHz | UHF %s Mhz ", Name, FM, VHF, UHF),"|") + end + end + end + if report:GetCount() == 1 then + report:Add("--------- N O N E ------------") + end + report:Add("------------------------------------------------------------") + local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + return self +end + +--- Add radio beacon to zone. Runs 30 secs. +-- @param #CTLD self +-- @param #string Name Name of zone. +-- @param #string Sound Name of soundfile. +-- @param #number Mhz Frequency in Mhz. +-- @param #number Modulation Modulation AM or FM. +function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) + self:T(self.lid .. " _AddRadioBeacon") + local Zone = ZONE:FindByName(Name) + local Sound = Sound or "beacon.ogg" + if Zone then + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() + local Frequency = Mhz * 1000000 -- Freq in Hertz + local Sound = "l10n/DEFAULT/"..Sound + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + end + return self +end + +--- Function to refresh radio beacons +-- @param #CTLD self +function CTLD:_RefreshRadioBeacons() + self:I(self.lid .. " _RefreshRadioBeacons") + + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + -- Get Beacon object from zone + local czone = cargozone -- #CTLD.CargoZone + local Sound = self.RadioSound + if czone.active and czone.hasbeacon then + local FMbeacon = czone.fmbeacon -- #CTLD.ZoneBeacon + local VHFbeacon = czone.vhfbeacon -- #CTLD.ZoneBeacon + local UHFbeacon = czone.uhfbeacon -- #CTLD.ZoneBeacon + local Name = czone.name + local FM = FMbeacon.frequency -- MHz + local VHF = VHFbeacon.frequency -- KHz + local UHF = UHFbeacon.frequency -- MHz + self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM) + self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM) + end + end + end + return self +end + +--- function to see if a unit is in a specific zone type. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit Unit +-- @param #CTLD.CargoZoneType Zonetype Zonetype +-- @return #boolean Outcome Is in zone or not +-- @return #string name Closest zone name +-- @return #string zone Closest Core.Zone#ZONE object +-- @return #number distance Distance to closest zone +function CTLD:IsUnitInZone(Unit,Zonetype) + self:T(self.lid .. " IsUnitInZone") + local unitname = Unit:GetName() + self:T(string.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + local zonetable = {} + local outcome = false + if Zonetype == CTLD.CargoZoneType.LOAD then + zonetable = self.pickupZones -- #table + elseif Zonetype == CTLD.CargoZoneType.DROP then + zonetable = self.dropOffZones -- #table + else + zonetable = self.wpZones -- #table + end + --- now see if we\'re in + local zonecoord = nil + local colorret = nil + local maxdist = 1000000 -- 100km + local zoneret = nil + local zonenameret = nil + for _,_cargozone in pairs(zonetable) do + local czone = _cargozone -- #CTLD.CargoZone + local unitcoord = Unit:GetCoordinate() + local zonename = czone.name + local zone = ZONE:FindByName(zonename) + zonecoord = zone:GetCoordinate() + local active = czone.active + local color = czone.color + local zoneradius = zone:GetRadius() + local distance = self:_GetDistance(zonecoord,unitcoord) + self:T(string.format("Check distance: %d",distance)) + if distance <= zoneradius and active then + outcome = true + end + if maxdist > distance then + maxdist = distance + zoneret = zone + zonenameret = zonename + colorret = color + end + end + self:T({outcome, zonenameret, zoneret, maxdist}) + return outcome, zonenameret, zoneret, maxdist +end + +--- Userfunction - Start smoke in a zone close to the Unit. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The Unit. +-- @param #boolean Flare If true, flare instead. +function CTLD:SmokeZoneNearBy(Unit, Flare) + self:T(self.lid .. " SmokeZoneNearBy") + -- table of #CTLD.CargoZone table + local unitcoord = Unit:GetCoordinate() + local Group = Unit:GetGroup() + local smokedistance = self.smokedistance + local smoked = false + local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} + for i=1,3 do + for index,cargozone in pairs(zones[i]) do + local CZone = cargozone --#CTLD.CargoZone + local zonename = CZone.name + local zone = ZONE:FindByName(zonename) + local zonecoord = zone:GetCoordinate() + local active = CZone.active + local color = CZone.color + local distance = self:_GetDistance(zonecoord,unitcoord) + if distance < smokedistance and active then + -- smoke zone since we\'re nearby + if not Flare then + zonecoord:Smoke(color or SMOKECOLOR.White) + else + if color == SMOKECOLOR.Blue then color = FLARECOLOR.White end + zonecoord:Flare(color or FLARECOLOR.White) + end + local txt = "smoking" + if Flare then txt = "flaring" end + local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + smoked = true + end + end + end + if not smoked then + local distance = UTILS.MetersToNM(self.smkedistance) + local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + end + return self +end + --- User - Function to add/adjust unittype capabilities. + -- @param #CTLD self + -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. + -- @param #boolean Cancrates Unit can load crates. + -- @param #boolean Cantroops Unit can load troops. + -- @param #number Cratelimit Unit can carry number of crates. + -- @param #number Trooplimit Unit can carry number of troops. + function CTLD:UnitCapabilities(Unittype, Cancrates, Cantroops, Cratelimit, Trooplimit) + self:T(self.lid .. " UnitCapabilities") + local unittype = nil + local unit = nil + if type(Unittype) == "string" then + unittype = Unittype + elseif type(Unittype) == "table" then + unit = UNIT:FindByName(Unittype) -- Wrapper.Unit#UNIT + unittype = unit:GetTypeName() + else + return self + end + -- set capabilities + local capabilities = {} -- #CTLD.UnitCapabilities + capabilities.type = unittype + capabilities.crates = Cancrates or false + capabilities.troops = Cantroops or false + capabilities.cratelimit = Cratelimit or 0 + capabilities.trooplimit = Trooplimit or 0 + self.UnitTypes[unittype] = capabilities + return self + end + + --- Check if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectHover(Unit) + self:T(self.lid .. " IsCorrectHover") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.maximumHoverHeight -- 15 + local minh = self.minimumHoverHeight -- 5 + local mspeed = 2 -- 2 m/s + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (uspeed <= maxh) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- List if a unit is hovering *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowHoverParams(Group,Unit) + local inhover = self:IsCorrectHover(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local text = string.format("Hover parameter (autoload):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- Check if a unit is in a load zone and is hovering in parameters. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:CanHoverLoad(Unit) + self:T(self.lid .. " CanHoverLoad") + local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) + return outcome + end + + --- Check if a unit is above ground. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsUnitInAir(Unit) + -- get speed and height + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + if aheight >= self.minimumHoverHeight then + return true + else + return false + end + end + + --- Autoload if we can do crates, have capacity free and are in a load zone. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #CTLD self + function CTLD:AutoHoverLoad(Unit) + self:T(self.lid .. " AutoHoverLoad") + -- get capabilities and current load + local unittype = Unit:GetTypeName() + local unitname = Unit:GetName() + local Group = Unit:GetGroup() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + if cancrates then + -- get load + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + end + local load = cratelimit - numberonboard + local canload = self:CanHoverLoad(Unit) + if canload and load > 0 then + self:_LoadCratesNearby(Group,Unit) + end + end + return self + end + + --- Run through all pilots and see if we autoload. + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CheckAutoHoverload() + if self.hoverautoloading then + for _,_pilot in pairs (self.CtldUnits) do + local Unit = UNIT:FindByName(_pilot) + self:AutoHoverLoad(Unit) + end + end + return self + end + +------------------------------------------------------------------- +-- FSM functions +------------------------------------------------------------------- + + --- FSM Function onafterStart. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStart(From, Event, To) + self:I({From, Event, To}) + if self.useprefix then + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.prefixes):FilterCategoryHelicopter():FilterStart() + else + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + -- Events + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:__Status(-5) + return self + end + + --- FSM Function onbeforeStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + self:_RefreshF10Menus() + self:_RefreshRadioBeacons() + self:CheckAutoHoverload() + return self + end + + --- FSM Function onafterStatus. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStatus(From, Event, To) + self:I({From, Event, To}) + -- gather some stats + -- pilots + local pilots = 0 + for _,_pilot in pairs (self.CtldUnits) do + + pilots = pilots + 1 + end + + -- spawned cargo boxes curr in field + local boxes = 0 + for _,_pilot in pairs (self.Spawned_Cargo) do + boxes = boxes + 1 + end + + local cc = self.CargoCounter + local tc = self.TroopCounter + + if self.debug or self.verbose > 0 then + local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc) + local m = MESSAGE:New(text,10,"CTLD"):ToAll() + end + self:__Status(-30) + return self + end + + --- FSM Function onafterStop. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @return #CTLD self + function CTLD:onafterStop(From, Event, To) + self:T({From, Event, To}) + self:UnhandleEvent(EVENTS.PlayerEnterAircraft) + self:UnhandleEvent(EVENTS.PlayerEnterUnit) + self:UnhandleEvent(EVENTS.PlayerLeaveUnit) + return self + end + + --- FSM Function onbeforeTroopsPickedUp. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesPickedUp. + -- @param #CTLD self + -- @param #string From State . + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo crate. + -- @return #CTLD self + function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeTroopsDeployed. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesDropped. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @return #CTLD self + function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) + self:I({From, Event, To}) + return self + end + + --- FSM Function onbeforeCratesBuild. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. + -- @return #CTLD self + function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) + self:I({From, Event, To}) + return self + end + +end -- end do +------------------------------------------------------------------- +-- End Ops.CTLD.lua +------------------------------------------------------------------- From ee503a378ee711e2009858db73559d50c833e321 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:24 +0200 Subject: [PATCH 308/382] Update Modules.lua (#1557) added CTLD --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index fef4fc982..7ad1b1829 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -85,6 +85,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From b84c0aba593eb366dcb4a67320f610a14f718ff7 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:36 +0200 Subject: [PATCH 309/382] Update Moose.files (#1556) --- Moose Setup/Moose.files | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index e7bec2087..077e75168 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -81,6 +81,8 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua +Ops/CSAR.lua +Ops/CTLD.lua AI/AI_Balancer.lua AI/AI_Air.lua From 576281a61212c8440fb939bf38bea5c5a129b691 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:24:46 +0200 Subject: [PATCH 310/382] Update CSAR.lua (#1554) Added options to limit number of downed pilots via Events (mission designers can still "inject" downed pilots): `self.limitmaxdownedpilots = true self.maxdownedpilots = 10` --- Moose Development/Moose/Ops/CSAR.lua | 62 ++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ad6ae1a97..61c3c58bc 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -59,7 +59,7 @@ -- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") -- -- options -- my_csar.immortalcrew = true -- downed pilot spawn is immortal --- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- my_csar.invisiblecrew = false -- downed pilot spawn is visible -- -- start the FSM -- my_csar:__Start(5) -- @@ -86,6 +86,9 @@ -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- -- (added 0.1.4) limit amount of downed pilots spawned by ejection events +-- self.limitmaxdownedpilots = true, +-- self.maxdownedpilots = 10, -- -- ## 2.1 Experimental Features -- @@ -188,6 +191,8 @@ CSAR = { smokecolor = 4, rescues = 0, rescuedpilots = 0, + limitmaxdownedpilots = true, + maxdownedpilots = 10, } --- Downed pilots info. @@ -235,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r4" +CSAR.version="0.1.4r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -350,6 +355,9 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- added 0.1.4 + self.limitmaxdownedpilots = true + self.maxdownedpilots = 25 -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -774,7 +782,13 @@ function CSAR:_EventHandler(EventData) if self:_DoubleEjection(_unitname) then return end - + + -- limit no of pilots in the field. + if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then + return + end + + -- all checks passed, get going. local _freq = self:_GenerateADFFrequency() self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) @@ -1776,6 +1790,36 @@ function CSAR:_RefreshRadioBeacons() end end +--- Helper function to count active downed pilots. +-- @param #CSAR self +-- @return #number Number of pilots in the field. +function CSAR:_CountActiveDownedPilots() + self:T(self.lid .. " _CountActiveDownedPilots") + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + return PilotsInFieldN +end + +--- Helper to decide if we're over max limit. +-- @param #CSAR self +-- @return #boolean True or false. +function CSAR:_ReachedPilotLimit() + self:T(self.lid .. " _ReachedPilotLimit") + local limit = self.maxdownedpilots + local islimited = self.limitmaxdownedpilots + local count = self:_CountActiveDownedPilots() + if islimited and (count >= limit) then + return true + else + return false + end +end + ------------------------------ --- FSM internal Functions --- ------------------------------ @@ -1844,13 +1888,7 @@ function CSAR:onafterStatus(From, Event, To) NumberOfSARPilots = NumberOfSARPilots + 1 end - local PilotsInFieldN = 0 - for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then - PilotsInFieldN = PilotsInFieldN + 1 - end - end + local PilotsInFieldN = self:_CountActiveDownedPilots() local PilotsBoarded = 0 for _, _unitName in pairs(self.inTransitGroups) do @@ -1860,8 +1898,8 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", - self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d (max %d) | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,self.maxdownedpilots,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) if self.verbose < 2 then self:I(text) From 5021a1e1f3d1a833f2b97ed921b0ec2f0671ed71 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 22 Jun 2021 18:26:44 +0200 Subject: [PATCH 311/382] Update CTLD.lua --- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 38640518f..4d34aabe8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: June 2021 +-- Date: 22 June 2021 do ------------------------------------------------------ @@ -382,7 +382,7 @@ do -- @field #CTLD CTLD = { ClassName = "CTLD", - verbose = 2, + verbose = 0, lid = "", coalition = 1, coalitiontxt = "blue", From 5123ab0720d5efb27dd93a0518fec501d47a40c8 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 23 Jun 2021 09:47:05 +0200 Subject: [PATCH 312/382] Update CTLD.lua (#1558) corrected fsm function name headlines in documentation --- Moose Development/Moose/Ops/CTLD.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4d34aabe8..211932703 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -327,7 +327,7 @@ do -- ... your code here ... -- end -- --- ## 3.3 OnAfterTroopsTroopsDeployed +-- ## 3.3 OnAfterTroopsDeployed -- -- This function is called when a player has deployed troops into the field: -- @@ -335,7 +335,7 @@ do -- ... your code here ... -- end -- --- ## 3.4 OnAfterTroopsCratesDropped +-- ## 3.4 OnAfterCratesDropped -- -- This function is called when a player has deployed crates to a DROP zone: -- @@ -343,7 +343,7 @@ do -- ... your code here ... -- end -- --- ## 3.5 OnAfterTroopsCratesBuild +-- ## 3.5 OnAfterCratesBuild -- -- This function is called when a player has build a vehicle or FOB: -- From d4cdfcc48c6d05a79c7a48558631c751a7be6d48 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Jun 2021 14:08:34 +0200 Subject: [PATCH 313/382] Corrected Mi-8MTV2 Unit Type Name --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 61c3c58bc..6c6245ef4 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -234,7 +234,7 @@ CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 -CSAR.AircraftType["Mi-8MT"] = 12 +CSAR.AircraftType["Mi-8MTV2"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 From 978be4e38386138af72e2ce01b55d132b8f500c0 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 23 Jun 2021 17:20:51 +0200 Subject: [PATCH 314/382] OPS Transport --- Moose Development/Moose/Ops/FlightGroup.lua | 7 +- Moose Development/Moose/Ops/OpsGroup.lua | 41 +++++-- Moose Development/Moose/Ops/OpsTransport.lua | 101 ++++++++++++++++-- .../Moose/Wrapper/Positionable.lua | 5 +- 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index bcf8227e7..87b98c88b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2252,9 +2252,14 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() - if groupspeed <= 1 then self.RTBRecallCount = self.RTBRecallCount+1 end + if groupspeed<=1 and not self:IsParking() then + self.RTBRecallCount = self.RTBRecallCount+1 + end if self.RTBRecallCount > 6 then + self:I(self.lid..string.format("WARNING: Group is not moving and was called RTB %d times. Assuming a problem and despawning!", self.RTBRecallCount)) + self.RTBRecallCount=0 self:Despawn(5) + return end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2792b93f5..4c29c2338 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1104,6 +1104,9 @@ function OPSGROUP:GetVelocity(UnitName) local vel=UTILS.VecNorm(velvec3) return vel + + else + self:E(self.lid.."WARNING: Unit does not exist. Cannot get velocity!") end else @@ -1751,9 +1754,9 @@ function OPSGROUP:IsPickingup() return self.carrierStatus==OPSGROUP.CarrierStatus.PICKUP end ---- Check if the group is picking up cargo. +--- Check if the group is loading cargo. -- @param #OPSGROUP self --- @return #boolean If true, group is picking up. +-- @return #boolean If true, group is loading. function OPSGROUP:IsLoading() return self.carrierStatus==OPSGROUP.CarrierStatus.LOADING end @@ -1803,14 +1806,20 @@ end --- Check if the group is currently loaded into a carrier. -- @param #OPSGROUP self --- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into this particular carrier group. +-- @param #string CarrierGroupName (Optional) Additionally check if group is loaded into a particular carrier group(s). -- @return #boolean If true, group is loaded. function OPSGROUP:IsLoaded(CarrierGroupName) if CarrierGroupName then - local carrierGroup=self:_GetMyCarrierGroup() - if carrierGroup and carrierGroup.groupname~=CarrierGroupName then - return false + if type(CarrierGroupName)~="table" then + CarrierGroupName={CarrierGroupName} end + for _,CarrierName in pairs(CarrierGroupName) do + local carrierGroup=self:_GetMyCarrierGroup() + if carrierGroup and carrierGroup.groupname==CarrierName then + return true + end + end + return false end return self.cargoStatus==OPSGROUP.CargoStatus.LOADED end @@ -4881,16 +4890,23 @@ function OPSGROUP:_CheckCargoTransport() end end + + self:I(self.lid.."gotcargo="..tostring(gotcargo)) + self:I(self.lid.."boarding="..tostring(boarding)) + self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. - if gotcargo and not boarding then + if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then self:I(self.lid.."Boarding finished ==> Loaded") self:Loaded() + else + -- No cargo and no one is boarding ==> check again if we can make anyone board. + self:Loading() end -- No cargo and no one is boarding ==> check again if we can make anyone board. if not gotcargo and not boarding then - self:Loading() + --self:Loading() end elseif self:IsTransporting() then @@ -5671,10 +5687,10 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) end -- Trigger embarked event for cargo group. - CargoGroup:Embarked(self, Carrier) + CargoGroup:Embarked(self, carrier) -- Trigger "Loaded" event for current cargo transport. - self.cargoTransport:Loaded(CargoGroup, Carrier) + self.cargoTransport:Loaded(CargoGroup, carrier) else self:E(self.lid.."ERROR: Cargo has no carrier on Load event!") @@ -5904,6 +5920,9 @@ function OPSGROUP:onafterUnloading(From, Event, To) -- Delivered to another carrier group. --- + -- Debug info. + self:I(self.lid..string.format("Transferring cargo %s to new carrier group %s", cargo.opsgroup:GetName(), carrierGroup:GetName())) + -- Unload from this and directly load into the other carrier. self:Unload(cargo.opsgroup) carrierGroup:Load(cargo.opsgroup, carrier) @@ -6229,7 +6248,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local CargoIsArmyOrNavy=self:IsArmygroup() or self:IsNavygroup() -- Check that carrier is standing still. - if (CarrierIsArmyOrNavy and CarrierGroup:IsHolding()) or (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt()) then + if (CarrierIsArmyOrNavy and (CarrierGroup:IsHolding() and CarrierGroup:GetVelocity(Carrier.name)<=1)) or (CarrierGroup:IsFlightgroup() and (CarrierGroup:IsParking() or CarrierGroup:IsLandedAt())) then -- Board if group is mobile, not late activated and army or navy. Everything else is loaded directly. local board=self.speedMax>0 and CargoIsArmyOrNavy and self:IsAlive() and CarrierGroup:IsAlive() diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 4eca27af6..7d9e725c3 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -44,6 +44,7 @@ -- @field #boolean disembarkActivation Activation setting when group is disembared from carrier. -- @field #boolean disembarkInUtero Do not spawn the group in any any state but leave it "*in utero*". For example, to directly load it into another carrier. -- @field #table disembarkCarriers Table of carriers to which the cargo is disembared. This is a direct transfer from the old to the new carrier. +-- @field #table requiredCargos Table of cargo groups that must be loaded before the first transport is started. -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. @@ -70,6 +71,7 @@ OPSTRANSPORT = { carrierTransportStatus = {}, conditionStart = {}, pathsTransport = {}, + requiredCargos = {}, } --- Cargo transport status. @@ -266,7 +268,7 @@ end --- Set transfer carrier(s). These are carrier groups, where the cargo is directly loaded into when disembarked. -- @param #OPSTRANSPORT self --- @param Core.Set#SET_GROUP Carriers Carrier set. +-- @param Core.Set#SET_GROUP Carriers Carrier set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkCarriers(Carriers) @@ -313,6 +315,41 @@ function OPSTRANSPORT:SetDisembarkInUtero(InUtero) end +--- Set required cargo. This is a list of cargo groups that need to be loaded before the **first** transport will start. +-- @param #OPSTRANSPORT self +-- @param Core.Set#SET_GROUP Cargos Required cargo set. Can also be passed as a #GROUP, #OPSGROUP or #SET_OPSGROUP object. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:SetRequiredCargos(Cargos) + + self:I(self.lid.."Setting required cargos!") + + -- Create table. + self.requiredCargos=self.requiredCargos or {} + + if Cargos:IsInstanceOf("GROUP") or Cargos:IsInstanceOf("OPSGROUP") then + + local cargo=self:_GetOpsGroupFromObject(Cargos) + if cargo then + table.insert(self.requiredCargos, cargo) + end + + elseif Cargos:IsInstanceOf("SET_GROUP") or Cargos:IsInstanceOf("SET_OPSGROUP") then + + for _,object in pairs(Cargos:GetSet()) do + local cargo=self:_GetOpsGroupFromObject(object) + if cargo then + table.insert(self.requiredCargos, cargo) + end + end + + else + self:E(self.lid.."ERROR: Required Cargos must be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object!") + end + + return self +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self @@ -365,6 +402,22 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) return self end +--- Get a list of alive carriers. +-- @param #OPSTRANSPORT self +-- @return #table Names of all carriers +function OPSTRANSPORT:_GetCarrierNames() + + local names={} + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + if carrier:IsAlive()~=nil then + table.insert(names, carrier.groupname) + end + end + + return names +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -778,6 +831,31 @@ function OPSTRANSPORT:_CheckDelivered() end +--- Check if all required cargos are loaded. +-- @param #OPSTRANSPORT self +-- @return #boolean If true, all required cargos are loaded or there is no required cargo. +function OPSTRANSPORT:_CheckRequiredCargos() + + if self.requiredCargos==nil or #self.requiredCargos==0 then + return true + end + + local carrierNames=self:_GetCarrierNames() + + local gotit=true + for _,_cargo in pairs(self.requiredCargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP + + + if not cargo:IsLoaded(carrierNames) then + return false + end + + end + + return true +end + --- Check if all given condition are true. -- @param #OPSTRANSPORT self -- @param #table Conditions Table of conditions. @@ -820,17 +898,22 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) for _,_carrier in pairs(self.disembarkCarriers or {}) do local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP - -- Find an element of the group that has enough free space. - carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + -- First check if carrier is alive and loading cargo. + if carrierGroup and carrierGroup:IsAlive() and carrierGroup:IsLoading() then - if carrier then - if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then - return carrier, carrierGroup + -- Find an element of the group that has enough free space. + carrier=carrierGroup:FindCarrierForCargo(CargoGroup) + + if carrier then + if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then + return carrier, carrierGroup + else + self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + end else - self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + self:T3(self.lid.."No transfer carrier available!") end - else - self:T3(self.lid.."No transfer carrier available!") + end end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 86843e1c1..01a6269d9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1449,8 +1449,8 @@ do -- Cargo self:F({Desc=Desc}) local Weights = { - ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry., - ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry., + ["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. + ["C-130"] = 22000 --The real value cannot be used, because it loads way too much apcs and infantry. } self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) ) @@ -1467,6 +1467,7 @@ do -- Cargo ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! + ["speedboat"] = 500, -- 500 kg ~ 5 persons } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) From 3b44aba34173931d4ecd1f47e09d0b6391d0a777 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 24 Jun 2021 08:34:14 +0200 Subject: [PATCH 315/382] Updated frequency test --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 6c6245ef4..1f9d22f64 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r1" +CSAR.version="0.1.4r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1783,7 +1783,7 @@ function CSAR:_RefreshRadioBeacons() for _,_pilot in pairs (PilotTable) do local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group - local frequency = pilot.frequency + local frequency = pilot.frequency or 0 -- thanks to @Thrud if frequency and frequency > 0 then self:_AddBeaconToGroup(group,frequency) end From 2ff128f184b7a83b4834c6ee68c4003d8d696e40 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 25 Jun 2021 12:39:02 +0200 Subject: [PATCH 316/382] Added Docu for functions --- Moose Development/Moose/Ops/CSAR.lua | 116 +++++++++++++++------------ Moose Development/Moose/Ops/CTLD.lua | 93 ++++++++++----------- 2 files changed, 113 insertions(+), 96 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 1f9d22f64..7be6de481 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -25,7 +25,7 @@ -- Date: June 2021 ------------------------------------------------------------------------- ---- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CSAR -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. @@ -157,7 +157,7 @@ -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -- -- -- @field #CSAR @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r3" +CSAR.version="0.1.4r4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -452,7 +452,7 @@ end --- Helper Functions --- ------------------------ ---- Function to insert downed pilot tracker object. +--- (Internal) Function to insert downed pilot tracker object. -- @param #CSAR self -- @param Wrapper.Group#GROUP Group The #GROUP object -- @param #string Groupname Name of the spawned group. @@ -490,7 +490,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript self.downedpilotcounter = self.downedpilotcounter+1 end ---- Count pilots on board. +--- (Internal) Count pilots on board. -- @param #CSAR self -- @param #string _heliName -- @return #number count @@ -505,7 +505,7 @@ function CSAR:_PilotsOnboard(_heliName) return count end ---- Function to check for dupe eject events. +--- (Internal) Function to check for dupe eject events. -- @param #CSAR self -- @param #string _unitname Name of unit. -- @return #boolean Outcome @@ -521,7 +521,7 @@ function CSAR:_DoubleEjection(_unitname) return false end ---- Spawn a downed pilot +--- (Internal) Spawn a downed pilot -- @param #CSAR self -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. @@ -547,7 +547,7 @@ function CSAR:_SpawnPilotInField(country,point) return _spawnedGroup, alias -- Wrapper.Group#GROUP object end ---- Add options to a downed pilot +--- (Internal) Add options to a downed pilot -- @param #CSAR self -- @param Wrapper.Group#GROUP group Group to use. function CSAR:_AddSpecialOptions(group) @@ -581,7 +581,7 @@ function CSAR:_AddSpecialOptions(group) end ---- Function to spawn a CSAR object into the scene. +--- (Internal) Function to spawn a CSAR object into the scene. -- @param #CSAR self -- @param #number _coalition Coalition -- @param DCS#country.id _country Country ID @@ -634,7 +634,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end ---- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +--- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self -- @param #string _zone Name of the zone. -- @param #number _coalition Coalition. @@ -672,8 +672,24 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) end +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string Zone Name of the zone. +-- @param #number Coalition Coalition. +-- @param #string Description (optional) Description. +-- @param #boolean RandomPoint (optional) Random yes or no. +-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) + return self +end + -- TODO: Split in functions per Event type ---- Event handler. +--- (Internal) Event handler. -- @param #CSAR self function CSAR:_EventHandler(EventData) self:T(self.lid .. " _EventHandler") @@ -836,7 +852,7 @@ function CSAR:_EventHandler(EventData) end ---- Initialize the action for a pilot. +--- (Internal) Initialize the action for a pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _downedGroup The group to rescue. -- @param #string _GroupName Name of the Group @@ -864,7 +880,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) end ---- Check if a name is in downed pilot table +--- (Internal) Check if a name is in downed pilot table -- @param #CSAR self -- @param #string name Name to search for. -- @return #boolean Outcome. @@ -883,7 +899,7 @@ function CSAR:_CheckNameInDownedPilots(name) return found, table end ---- Check if a name is in downed pilot table and remove it. +--- (Internal) Check if a name is in downed pilot table and remove it. -- @param #CSAR self -- @param #string name Name to search for. -- @param #boolean force Force removal. @@ -914,7 +930,7 @@ function CSAR:_RemoveNameFromDownedPilots(name,force) return found end ---- Check state of wounded group. +--- (Internal) Check state of wounded group. -- @param #CSAR self -- @param #string heliname heliname -- @param #string woundedgroupname woundedgroupname @@ -967,7 +983,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end end ---- Function to pop a smoke at a wounded pilot\'s positions. +--- (Internal) Function to pop a smoke at a wounded pilot\'s positions. -- @param #CSAR self -- @param #string _woundedGroupName Name of the group. -- @param Wrapper.Group#GROUP _woundedLeader Object of the group. @@ -984,7 +1000,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) end end ---- Function to pickup the wounded pilot from the ground. +--- (Internal) Function to pickup the wounded pilot from the ground. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit Object of the group. -- @param #string _pilotName Name of the pilot. @@ -1035,7 +1051,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam return true end ---- Move group to destination. +--- (Internal) Move group to destination. -- @param #CSAR self -- @param Wrapper.Group#GROUP _leader -- @param Core.Point#COORDINATE _destination @@ -1048,7 +1064,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) group:RouteToVec2(coordinate,5) end ---- Function to check if heli is close to group. +--- (Internal) Function to check if heli is close to group. -- @param #CSAR self -- @param #number _distance -- @param Wrapper.Unit#UNIT _heliUnit @@ -1179,7 +1195,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- Check if group not KIA. +--- (Internal) Check if group not KIA. -- @param #CSAR self -- @param Wrapper.Group#GROUP _woundedGroup -- @param #string _woundedGroupName @@ -1209,7 +1225,7 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he return inTransit end ---- Monitor in-flight returning groups. +--- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name -- @param #string groupname Group name @@ -1245,7 +1261,7 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) self:__Returning(-5,heliname,_woundedGroupName) end ---- Mark pilot as rescued and remove from tables. +--- (Internal) Mark pilot as rescued and remove from tables. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heliUnit function CSAR:_RescuePilots(_heliUnit) @@ -1270,7 +1286,7 @@ function CSAR:_RescuePilots(_heliUnit) self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end ---- Check and return Wrappe.Unit#UNIT based on the name if alive. +--- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit -- @return #UNIT or nil @@ -1284,7 +1300,7 @@ function CSAR:_GetSARHeli(_unitName) end end ---- Display message to single Unit. +--- (Internal) Display message to single Unit. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. -- @param #string _text Text of message. @@ -1308,7 +1324,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) end end ---- Function to get string of a group\'s position. +--- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. -- @return #string Coordinates as Text @@ -1334,7 +1350,7 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) return _coordinatesText end ---- Display active SAR tasks to player. +--- (Internal) Display active SAR tasks to player. -- @param #CSAR self -- @param #string _unitName Unit to display to function CSAR:_DisplayActiveSAR(_unitName) @@ -1386,7 +1402,7 @@ function CSAR:_DisplayActiveSAR(_unitName) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) end ---- Find the closest downed pilot to a heli. +--- (Internal) Find the closest downed pilot to a heli. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @return #table Table of results @@ -1420,7 +1436,7 @@ function CSAR:_GetClosestDownedPilot(_heli) return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } end ---- Fire a flare at the point of a downed pilot. +--- (Internal) Fire a flare at the point of a downed pilot. -- @param #CSAR self -- @param #string _unitName Name of the unit. function CSAR:_SignalFlare(_unitName) @@ -1455,7 +1471,7 @@ function CSAR:_SignalFlare(_unitName) end end ---- Display info to all SAR groups. +--- (Internal) Display info to all SAR groups. -- @param #CSAR self -- @param #string _message Message to display. -- @param #number _side Coalition of message. @@ -1472,7 +1488,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) end end ----Request smoke at closest downed pilot. +---(Internal) Request smoke at closest downed pilot. --@param #CSAR self --@param #string _unitName Name of the helicopter function CSAR:_Reqsmoke( _unitName ) @@ -1504,7 +1520,7 @@ function CSAR:_Reqsmoke( _unitName ) end end ---- Determine distance to closest MASH. +--- (Internal) Determine distance to closest MASH. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @retunr @@ -1559,7 +1575,7 @@ function CSAR:_GetClosestMASH(_heli) end end ---- Display onboarded rescued pilots. +--- (Internal) Display onboarded rescued pilots. -- @param #CSAR self -- @param #string _unitName Name of the chopper function CSAR:_CheckOnboard(_unitName) @@ -1581,7 +1597,7 @@ function CSAR:_CheckOnboard(_unitName) end end ---- Populate F10 menu for CSAR players. +--- (Internal) Populate F10 menu for CSAR players. -- @param #CSAR self function CSAR:_AddMedevacMenuItem() self:T(self.lid .. " _AddMedevacMenuItem") @@ -1624,7 +1640,7 @@ function CSAR:_AddMedevacMenuItem() return end ---- Return distance in meters between two coordinates. +--- (Internal) Return distance in meters between two coordinates. -- @param #CSAR self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two @@ -1639,7 +1655,7 @@ function CSAR:_GetDistance(_point1, _point2) end end ---- Populate table with available beacon frequencies. +--- (Internal) Populate table with available beacon frequencies. -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") @@ -1710,7 +1726,7 @@ function CSAR:_GenerateVHFrequencies() self.FreeVHFFrequencies = FreeVHFFrequencies end ---- Pop frequency from prepopulated table. +--- (Internal) Pop frequency from prepopulated table. -- @param #CSAR self -- @return #number frequency function CSAR:_GenerateADFFrequency() @@ -1724,7 +1740,7 @@ function CSAR:_GenerateADFFrequency() return _vhf end ---- Function to determine clockwise direction for flares. +--- (Internal) Function to determine clockwise direction for flares. -- @param #CSAR self -- @param Wrapper.Unit#UNIT _heli The Helicopter -- @param Wrapper.Group#GROUP _group The downed Group @@ -1748,7 +1764,7 @@ function CSAR:_GetClockDirection(_heli, _group) return clock end ---- Function to add beacon to downed pilot. +--- (Internal) Function to add beacon to downed pilot. -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use @@ -1774,7 +1790,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) end end ---- Helper function to (re-)add beacon to downed pilot. +--- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self -- @param #table _args Arguments function CSAR:_RefreshRadioBeacons() @@ -1790,7 +1806,7 @@ function CSAR:_RefreshRadioBeacons() end end ---- Helper function to count active downed pilots. +--- (Internal) Helper function to count active downed pilots. -- @param #CSAR self -- @return #number Number of pilots in the field. function CSAR:_CountActiveDownedPilots() @@ -1805,7 +1821,7 @@ function CSAR:_CountActiveDownedPilots() return PilotsInFieldN end ---- Helper to decide if we're over max limit. +--- (Internal) Helper to decide if we're over max limit. -- @param #CSAR self -- @return #boolean True or false. function CSAR:_ReachedPilotLimit() @@ -1824,7 +1840,7 @@ end --- FSM internal Functions --- ------------------------------ ---- Function called after Start() event. +--- (Internal) Function called after Start() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1850,7 +1866,7 @@ function CSAR:onafterStart(From, Event, To) return self end ---- Function called before Status() event. +--- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1875,7 +1891,7 @@ function CSAR:onbeforeStatus(From, Event, To) return self end ---- Function called after Status() event. +--- (Internal) Function called after Status() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1912,7 +1928,7 @@ function CSAR:onafterStatus(From, Event, To) return self end ---- Function called after Stop() event. +--- (Internal) Function called after Stop() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1930,7 +1946,7 @@ function CSAR:onafterStop(From, Event, To) return self end ---- Function called before Approach() event. +--- (Internal) Function called before Approach() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1943,7 +1959,7 @@ function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Boarded() event. +--- (Internal) Function called before Boarded() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1956,7 +1972,7 @@ function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Returning() event. +--- (Internal) Function called before Returning() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1969,7 +1985,7 @@ function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) return self end ---- Function called before Rescued() event. +--- (Internal) Function called before Rescued() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. @@ -1984,7 +2000,7 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) return self end ---- Function called before PilotDown() event. +--- (Internal) Function called before PilotDown() event. -- @param #CSAR self. -- @param #string From From state. -- @param #string Event Event triggered. diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 211932703..6c62d5046 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -8,7 +8,7 @@ -- -- ## Missions: -- --- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/) +-- ### [CTLD - Combat Troop & Logistics Deployment](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CTLD) -- -- === -- @@ -180,7 +180,7 @@ end do ------------------------------------------------------------------------- ---- **CTLD** class, extends #Core.Base#BASE, #Core.Fsm#FSM +--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM -- @type CTLD -- @field #string ClassName Name of the class. -- @field #number verbose Verbosity level. @@ -489,7 +489,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.1b1" +CTLD.version="0.1.1b2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -703,7 +703,7 @@ end -- Helper and User Functions ------------------------------------------------------------------- ---- Function to generate valid UHF Frequencies +--- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") @@ -718,7 +718,7 @@ function CTLD:_GenerateUHFrequencies() return self end ---- Function to generate valid FM Frequencies +--- (Internal) Function to generate valid FM Frequencies -- @param #CTLD sel function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") @@ -742,7 +742,7 @@ function CTLD:_GenerateFMFrequencies() return self end ---- Populate table with available VHF beacon frequencies. +--- (Internal) Populate table with available VHF beacon frequencies. -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") @@ -806,7 +806,7 @@ function CTLD:_GenerateVHFrequencies() return self end ---- Function to generate valid laser codes. +--- (Internal) Function to generate valid laser codes. -- @param #CTLD self function CTLD:_GenerateLaserCodes() self:T(self.lid .. " _GenerateLaserCodes") @@ -828,7 +828,7 @@ function CTLD:_GenerateLaserCodes() end end ---- Helper function to generate laser codes. +--- (Internal) Helper function to generate laser codes. -- @param #CTLD self -- @param #number _number -- @param #number _numberToFind @@ -846,7 +846,7 @@ function CTLD:_ContainsDigit(_number, _numberToFind) return false end ---- Event handler function +--- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) @@ -873,7 +873,7 @@ function CTLD:_EventHandler(EventData) return self end ---- Function to load troops into a heli. +--- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -930,7 +930,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return self end ---- Function to spawn crates in front of the heli. +--- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1011,7 +1011,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) return self end ---- Function to find and list nearby crates. +--- (Internal) Function to find and list nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1045,7 +1045,7 @@ function CTLD:_ListCratesNearby( _group, _unit) return self end ---- Return distance in meters between two coordinates. +--- (Internal) Return distance in meters between two coordinates. -- @param #CTLD self -- @param Core.Point#COORDINATE _point1 Coordinate one -- @param Core.Point#COORDINATE _point2 Coordinate two @@ -1060,7 +1060,7 @@ function CTLD:_GetDistance(_point1, _point2) end end ---- Function to find and return nearby crates. +--- (Internal) Function to find and return nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP _group Group -- @param Wrapper.Unit#UNIT _unit Unit @@ -1094,7 +1094,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) return found, index end ---- Function to get and load nearby crates. +--- (Internal) Function to get and load nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1194,7 +1194,7 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end ---- Function to list loaded cargo. +--- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1249,7 +1249,7 @@ function CTLD:_ListCargo(Group, Unit) return self end ---- Function to unload troops from heli. +--- (Internal) Function to unload troops from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1326,7 +1326,7 @@ function CTLD:_UnloadTroops(Group, Unit) return self end ---- Function to unload crates from heli. +--- (Internal) Function to unload crates from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrappe.Unit#UNIT Unit @@ -1384,7 +1384,7 @@ function CTLD:_UnloadCrates(Group, Unit) return self end ---- Function to build nearby crates. +--- (Internal) Function to build nearby crates. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrappe.Unit#UNIT Unit @@ -1464,7 +1464,7 @@ function CTLD:_BuildCrates(Group, Unit) return self end ---- Function to actually SPAWN buildables in the mission. +--- (Internal) Function to actually SPAWN buildables in the mission. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#UNIT Unit @@ -1496,7 +1496,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) return self end ---- Function to move group to WP zone. +--- (Internal) Function to move group to WP zone. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group The Group to move. function CTLD:_MoveGroupToZone(Group) @@ -1523,7 +1523,7 @@ function CTLD:_MoveGroupToZone(Group) return self end ---- Housekeeping - Cleanup crates when build +--- (Internal) Housekeeping - Cleanup crates when build -- @param #CTLD self -- @param #table Crates Table of #CTLD_CARGO objects near the unit. -- @param #CTLD.Buildable Build Table build object. @@ -1575,7 +1575,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) return self end ---- Housekeeping - Function to refresh F10 menus. +--- (Internal) Housekeeping - Function to refresh F10 menus. -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() @@ -1747,7 +1747,7 @@ function CTLD:DeactivateZone(Name,ZoneType) return self end ---- Function to obtain a valid FM frequency. +--- (Internal) Function to obtain a valid FM frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1768,7 +1768,7 @@ function CTLD:_GetFMBeacon(Name) return beacon end ---- Function to obtain a valid UHF frequency. +--- (Internal) Function to obtain a valid UHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1789,7 +1789,7 @@ function CTLD:_GetUHFBeacon(Name) return beacon end ---- Function to obtain a valid VHF frequency. +--- (Internal) Function to obtain a valid VHF frequency. -- @param #CTLD self -- @param #string Name Name of zone. -- @return #CTLD.ZoneBeacon Beacon Beacon table. @@ -1845,7 +1845,7 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon) return self end ---- Function to show list of radio beacons +--- (Internal) Function to show list of radio beacons -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -1878,7 +1878,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) return self end ---- Add radio beacon to zone. Runs 30 secs. +--- (Internal) Add radio beacon to zone. Runs 30 secs. -- @param #CTLD self -- @param #string Name Name of zone. -- @param #string Sound Name of soundfile. @@ -1898,7 +1898,7 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation) return self end ---- Function to refresh radio beacons +--- (Internal) Function to refresh radio beacons -- @param #CTLD self function CTLD:_RefreshRadioBeacons() self:I(self.lid .. " _RefreshRadioBeacons") @@ -1926,7 +1926,7 @@ function CTLD:_RefreshRadioBeacons() return self end ---- function to see if a unit is in a specific zone type. +--- (Internal) Function to see if a unit is in a specific zone type. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit Unit -- @param #CTLD.CargoZoneType Zonetype Zonetype @@ -1978,7 +1978,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) return outcome, zonenameret, zoneret, maxdist end ---- Userfunction - Start smoke in a zone close to the Unit. +--- User function - Start smoke in a zone close to the Unit. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The Unit. -- @param #boolean Flare If true, flare instead. @@ -2020,6 +2020,7 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end return self end + --- User - Function to add/adjust unittype capabilities. -- @param #CTLD self -- @param #string Unittype The unittype to adjust. If passed as Wrapper.Unit#UNIT, it will search for the unit in the mission. @@ -2050,7 +2051,7 @@ end return self end - --- Check if a unit is hovering *in parameters*. + --- (Internal) Check if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2077,7 +2078,7 @@ end return outcome end - --- List if a unit is hovering *in parameters*. + --- (Internal) List if a unit is hovering *in parameters*. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit @@ -2090,7 +2091,7 @@ end return self end - --- Check if a unit is in a load zone and is hovering in parameters. + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2100,7 +2101,7 @@ end return outcome end - --- Check if a unit is above ground. + --- (Internal) Check if a unit is above ground. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome @@ -2117,7 +2118,7 @@ end end end - --- Autoload if we can do crates, have capacity free and are in a load zone. + --- (Internal) Autoload if we can do crates, have capacity free and are in a load zone. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self @@ -2147,7 +2148,7 @@ end return self end - --- Run through all pilots and see if we autoload. + --- (Internal) Run through all pilots and see if we autoload. -- @param #CTLD self -- @return #CTLD self function CTLD:CheckAutoHoverload() @@ -2164,7 +2165,7 @@ end -- FSM functions ------------------------------------------------------------------- - --- FSM Function onafterStart. + --- (Internal) FSM Function onafterStart. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2185,7 +2186,7 @@ end return self end - --- FSM Function onbeforeStatus. + --- (Internal) FSM Function onbeforeStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2199,7 +2200,7 @@ end return self end - --- FSM Function onafterStatus. + --- (Internal) FSM Function onafterStatus. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2232,7 +2233,7 @@ end return self end - --- FSM Function onafterStop. + --- (Internal) FSM Function onafterStop. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2246,7 +2247,7 @@ end return self end - --- FSM Function onbeforeTroopsPickedUp. + --- (Internal) FSM Function onbeforeTroopsPickedUp. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2260,7 +2261,7 @@ end return self end - --- FSM Function onbeforeCratesPickedUp. + --- (Internal) FSM Function onbeforeCratesPickedUp. -- @param #CTLD self -- @param #string From State . -- @param #string Event Trigger. @@ -2274,7 +2275,7 @@ end return self end - --- FSM Function onbeforeTroopsDeployed. + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2288,7 +2289,7 @@ end return self end - --- FSM Function onbeforeCratesDropped. + --- (Internal) FSM Function onbeforeCratesDropped. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. @@ -2302,7 +2303,7 @@ end return self end - --- FSM Function onbeforeCratesBuild. + --- (Internal) FSM Function onbeforeCratesBuild. -- @param #CTLD self -- @param #string From State. -- @param #string Event Trigger. From 36669c80da151c85f91a3d318945a8d053abfa3a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Jun 2021 13:23:56 +0200 Subject: [PATCH 317/382] OPSTRANSPORT --- Moose Development/Moose/Ops/FlightGroup.lua | 20 +++-- Moose Development/Moose/Ops/OpsGroup.lua | 79 ++++++++++++++----- Moose Development/Moose/Ops/OpsTransport.lua | 44 ++++++++++- Moose Development/Moose/Wrapper/Airbase.lua | 12 +++ .../Moose/Wrapper/Positionable.lua | 19 ++--- 5 files changed, 139 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 87b98c88b..61ccc80d7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1474,7 +1474,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) -- Trigger element airborne event. - self:__ElementAirborne(2, Element) + self:__ElementAirborne(0.1, Element) end --- On after "ElementAirborne" event. @@ -1749,7 +1749,7 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self:LandAtAirbase(airbase) end else - self:_CheckGroupDone(1) + self:_CheckGroupDone() end else self:_UpdateMenu() @@ -2194,7 +2194,8 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Send flight to destination. if destbase then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") - self:__RTB(-3, destbase) + --self:RTB(destbase) + self:__RTB(-0.1, destbase) elseif destzone then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:__RTZ(-3, destzone) @@ -2378,6 +2379,9 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Holding points. local c0=self.group:GetCoordinate() + local zone=airbase:GetZone() + env.info("FF landatairbase zone:") + self:I({zone=zone}) local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil @@ -3075,8 +3079,8 @@ function FLIGHTGROUP:_InitGroup() self:I(self.lid..text) end - env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) - env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) + --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true @@ -3711,6 +3715,8 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) + env.info("FF Get Parking spot for element "..element.name) + -- Coordinate of unit landed local coord=element.unit:GetCoordinate() @@ -3724,7 +3730,8 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) if airbase and airbase:IsShip() then coord.x=0 coord.z=0 - maxdist=100 + maxdist=500 -- 100 meters was not enough, e.g. on the Seawise Giant, where the spot is 139 meters from the "center" + env.info("FF Airbase is ship") end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot @@ -3733,6 +3740,7 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot dist=coord:Get2DDistance(parking.Coordinate) + env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) if dist repeating boarding call in 10 sec") self:__Board(-10, CarrierGroup, Carrier) + + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. + self:_SetMyCarrier(CarrierGroup, Carrier, true) + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -8085,7 +8126,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Airborne(-0.5) + self:__Airborne(-0.1) end elseif newstatus==OPSGROUP.ElementStatus.LANDED then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 7d9e725c3..5c093a086 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -103,7 +103,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.6" +OPSTRANSPORT.version="0.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -418,6 +418,23 @@ function OPSTRANSPORT:_GetCarrierNames() return names end +--- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. +-- @param #OPSTRANSPORT self +-- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. +-- @return #table Ops groups. +function OPSTRANSPORT:GetCargoOpsGroups(Delivered) + + local opsgroups={} + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + if Delivered==nil or cargo.delivered==Delivered then + table.insert(opsgroups, cargo.opsgroup) + end + end + + return opsgroups +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -574,8 +591,33 @@ function OPSTRANSPORT:GetCarrierTransportStatus(CarrierGroup) return self.carrierTransportStatus[CarrierGroup.groupname] end +--- Get unique ID of the transport assignment. +-- @param #OPSTRANSPORT self +-- @return #number UID. +function OPSTRANSPORT:GetUID() + return self.uid +end +--- Get number of delivered cargo groups. +-- @param #OPSTRANSPORT self +-- @return #number Total number of delivered cargo groups. +function OPSTRANSPORT:GetNcargoDelivered() + return self.Ndelivered +end +--- Get number of cargo groups. +-- @param #OPSTRANSPORT self +-- @return #number Total number of cargo groups. +function OPSTRANSPORT:GetNcargoTotal() + return self.Ncargo +end + +--- Get number of carrier groups assigned for this transport. +-- @param #OPSTRANSPORT self +-- @return #number Total number of carrier groups. +function OPSTRANSPORT:GetNcarrier() + return self.Ncarrier +end --- Check if an OPS group is assigned as carrier for this transport. -- @param #OPSTRANSPORT self diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index ca6ba1ca1..aba50cabd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -533,6 +533,9 @@ function AIRBASE:Register(AirbaseName) -- Get descriptors. self.descriptors=self:GetDesc() + + -- Debug info. + --self:I({airbase=AirbaseName, descriptors=self.descriptors}) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME @@ -544,12 +547,21 @@ function AIRBASE:Register(AirbaseName) self.isHelipad=true elseif self.category==Airbase.Category.SHIP then self.isShip=true + -- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects() + if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then + self.isHelipad=true + self.isShip=false + self.category=Airbase.Category.HELIPAD + _DATABASE:AddStatic(AirbaseName) + end else self:E("ERROR: Unknown airbase category!") end + -- Init parking spots. self:_InitParkingSpots() + -- Get 2D position vector. local vec2=self:GetVec2() -- Init coordinate. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 01a6269d9..6589193df 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1459,15 +1459,16 @@ do -- Cargo 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, - ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). - ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. - ["LST_Mk2"] =2100000, -- Can carry 2100 tons according to wiki source! - ["speedboat"] = 500, -- 500 kg ~ 5 persons + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). + ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. + ["LST_Mk2"] = 2100000, -- Can carry 2100 tons according to wiki source! + ["speedboat"] = 500, -- 500 kg ~ 5 persons + ["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns. } self.__.CargoBayWeightLimit = ( Weights[Desc.typeName] or 50000 ) From a861f8d9d46ffa5ca43841c834442e8595065165 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Jun 2021 21:13:24 +0200 Subject: [PATCH 318/382] OPS --- Moose Development/Moose/Ops/FlightGroup.lua | 8 +------- Moose Development/Moose/Ops/OpsGroup.lua | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 61ccc80d7..2ec865140 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2379,9 +2379,6 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Holding points. local c0=self.group:GetCoordinate() - local zone=airbase:GetZone() - env.info("FF landatairbase zone:") - self:I({zone=zone}) local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil @@ -3715,8 +3712,6 @@ end -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) - env.info("FF Get Parking spot for element "..element.name) - -- Coordinate of unit landed local coord=element.unit:GetCoordinate() @@ -3731,7 +3726,6 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) coord.x=0 coord.z=0 maxdist=500 -- 100 meters was not enough, e.g. on the Seawise Giant, where the spot is 139 meters from the "center" - env.info("FF Airbase is ship") end local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot @@ -3740,7 +3734,7 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot dist=coord:Get2DDistance(parking.Coordinate) - env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) + --env.info(string.format("FF parking %d dist=%.1f", parking.TerminalID, dist)) if dist pickup") -- Initiate the cargo transport process. - self:Pickup() + self:__Pickup(-1) elseif self:IsPickingup() then @@ -4897,9 +4900,10 @@ function OPSGROUP:_CheckCargoTransport() end - self:I(self.lid.."gotcargo="..tostring(gotcargo)) - self:I(self.lid.."boarding="..tostring(boarding)) - self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) + -- Debug. + --self:I(self.lid.."gotcargo="..tostring(gotcargo)) + --self:I(self.lid.."boarding="..tostring(boarding)) + --self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then @@ -5090,7 +5094,7 @@ function OPSGROUP:AddOpsTransport(OpsTransport) table.insert(self.cargoqueue, OpsTransport) -- Debug message. - self:I(self.lid.."FF adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) + self:T(self.lid.."Adding transport to carrier, #self.cargoqueue="..#self.cargoqueue) return self end @@ -8708,7 +8712,7 @@ function OPSGROUP:_AddElementByName(unitname) -- Descriptors and type/category. element.descriptors=unit:GetDesc() - self:I({desc=element.descriptors}) + --self:I({desc=element.descriptors}) element.category=unit:GetUnitCategory() element.categoryname=unit:GetCategoryName() @@ -8762,7 +8766,7 @@ function OPSGROUP:_AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, life=%.1f/%.1f category=%s (%d), type=%s, size=%.1f (L=%.1f H=%.1f W=%.1f), weight=%.1f/%.1f (cargo=%.1f/%.1f)", element.name, element.status, element.skill, element.life, element.life0, element.categoryname, element.category, element.typename, element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) - self:I(self.lid..text) + self:T(self.lid..text) -- Debug text. --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", From 3f5e322948ee7a4b1567482d62b41a63f5458d1e Mon Sep 17 00:00:00 2001 From: Celso Dantas Date: Sat, 26 Jun 2021 07:33:51 -0400 Subject: [PATCH 319/382] Update Airboss debug msg with BRC/Final heading (#1559) Including the BRC and Final Heading on the debug message is useful for squadrons relying on that msg to know what is the end heading of the carrier. The carrier tends to turns a few times before ending up on the final heading as it adjust into its track. Thus, having the BRC/Final Heading on the last message "Starting aircraft recovery Case %d ops." is useful. --- Moose Development/Moose/Ops/Airboss.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index a6924682d..f2ad2df53 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -15526,8 +15526,12 @@ function AIRBOSS:_MarshalCallRecoveryStart(case) -- Debug output. local text=string.format("Starting aircraft recovery Case %d ops.", case) - if case>1 then - text=text..string.format(" Marshal radial %03d°.", radial) + if case==1 then + text=text..string.format(" BRC %03d°.", self:GetBRC()) + elseif case==2 then + text=text..string.format(" Marshal radial %03d°. BRC %03d°.", radial, self:GetBRC()) + elseif case==3 then + text=text..string.format(" Marshal radial %03d°. Final heading %03d°.", radial, self:GetFinalBearing(false)) end self:T(self.lid..text) From b012c5b2aa64f6cc18c3ec3728ef7c4dd4f5ae7f Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 28 Jun 2021 13:17:55 +0200 Subject: [PATCH 320/382] OPS Transport - Improved cargo bay weight calculation. --- Moose Development/Moose/Ops/OpsGroup.lua | 282 +++++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 13 +- 2 files changed, 234 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 5032bcc29..13d1464e5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -219,6 +219,7 @@ OPSGROUP = { -- @field #number weightMaxCargo Max. cargo weight in kg. -- @field #number weightCargo Current cargo weight in kg. -- @field #number weight Current weight including cargo in kg. +-- @field #table cargoBay Cargo bay. --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -420,12 +421,17 @@ OPSGROUP.CargoStatus={ -- @field #number length Length of (un-)loading zone in meters. -- @field #number width Width of (un-)loading zone in meters. ---- Cargo transport data. +--- Data of the carrier that has loaded this group. -- @type OPSGROUP.MyCarrier -- @field #OPSGROUP group The carrier group. -- @field #OPSGROUP.Element element The carrier element. -- @field #boolean reserved If `true`, the carrier has caro space reserved for me. +--- Element cargo bay data. +-- @type OPSGROUP.MyCargo +-- @field #OPSGROUP group The cargo group. +-- @field #boolean reserved If `true`, the cargo bay space is reserved but cargo has not actually been loaded yet. + --- Cargo group data. -- @type OPSGROUP.CargoGroup -- @field #OPSGROUP opsgroup The cargo opsgroup. @@ -4562,6 +4568,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end -- Check cargo bay and declare cargo groups dead. + --[[ for groupname, carriername in pairs(self.cargoBay or {}) do if Element.name==carriername then local opsgroup=_DATABASE:GetOpsGroup(groupname) @@ -4578,6 +4585,26 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end end + ]] + + -- Check cargo bay and declare cargo groups dead. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + + end + end + end + end if self:IsCarrier() then if self.cargoTransport then @@ -4800,6 +4827,7 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. + --[[ if self.verbose>=3 then local text="" for cargogroupname, carriername in pairs(self.cargoBay) do @@ -4809,6 +4837,22 @@ function OPSGROUP:_CheckCargoTransport() self:I(self.lid.."Cargo bay:"..text) end end + ]] + + -- Check cargo bay and declare cargo groups dead. + if self.verbose>=0 then + local text="" + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + text=text..string.format("\n- %s in carrier %s, reserved=%s", tostring(cargo.group:GetName()), tostring(element.name), tostring(cargo.reserved)) + end + end + if text~="" then + self:I(self.lid.."Cargo bay:"..text) + end + end -- Cargo queue debug info. if self.verbose>=3 then @@ -4958,49 +5002,157 @@ function OPSGROUP:_CheckCargoTransport() return self end + +--- Check if a group is in the cargo bay. +-- @param #OPSGROUP self +-- @param #OPSGROUP OpsGroup Group to check. +-- @return #boolean If `true`, group is in the cargo bay. +function OPSGROUP:_IsInCargobay(OpsGroup) + + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group.groupname==OpsGroup.groupname then + return true + end + end + end + + return false +end + --- Add OPSGROUP to cargo bay of a carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. -- @param #OPSGROUP.Element CarrierElement The element of the carrier. -function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement) +-- @param #boolean Reserved Only reserve the cargo bay space. +function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement, Reserved) --TODO: Check group is not already in cargobay of this carrier or any other carrier. + + local cargo=self:_GetCargobay(CargoGroup) + + if cargo then + cargo.reserved=Reserved + else + cargo={} --#OPSGROUP.MyCargo + cargo.group=CargoGroup + cargo.reserved=Reserved + + table.insert(CarrierElement.cargoBay, cargo) + end + + + + -- Set my carrier. + CargoGroup:_SetMyCarrier(self, CarrierElement, Reserved) + + -- Fill cargo bay (obsolete). + self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + if not Reserved then + -- Cargo weight. local weight=CargoGroup:GetWeightTotal() -- Add weight to carrier. self:AddWeightCargo(CarrierElement.name, weight) - - -- Fill cargo bay. - self.cargoBay[CargoGroup.groupname]=CarrierElement.name + + end return self end +--- Get cargo bay item. +-- @param #OPSGROUP self +-- @param #OPSGROUP CargoGroup Cargo group. +-- @return #OPSGROUP.MyCargo Cargo bay item or `nil` if the group is not in the carrier. +-- @return #number CargoBayIndex Index of item in the cargo bay table. +-- @return #OPSGROUP.Element Carrier element. +function OPSGROUP:_GetCargobay(CargoGroup) + + -- Loop over elements and their cargo bay items. + local CarrierElement=nil --#OPSGROUP.Element + local cargobayIndex=nil + local reserved=nil + for i,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for j,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and cargo.group.groupname==CargoGroup.groupname then + return cargo, j, element + end + end + end + + return nil, nil, nil +end + --- Remove OPSGROUP from cargo bay of a carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup Cargo group. --- @param #OPSGROUP.Element CarrierElement The element of the carrier. -- @return #boolean If `true`, cargo could be removed. -function OPSGROUP:_DelCargobay(CargoGroup, CarrierElement) +function OPSGROUP:_DelCargobay(CargoGroup) if self.cargoBay[CargoGroup.groupname] then -- Not in cargo bay any more. self.cargoBay[CargoGroup.groupname]=nil - -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - - -- Get carrier of group. - local carrier=CargoGroup:_GetMyCarrierElement() - - if carrier then - self:RedWeightCargo(carrier.name, weight) + end + + + --[[ + local MyCarrierGroup, MyCarrierElement, MyIsReserved=CargoGroup:_GetMyCarrier() + + if MyCarrierGroup and MyCarrierGroup.groupname==self.groupname then + if not IsReserved then + + -- Reduce carrier weight. + local weight=CargoGroup:GetWeightTotal() + + self:RedWeightCargo(CarrierElement.name, weight) + + end + + end + ]] + + + -- Loop over elements and their cargo bay items. + --[[ + local CarrierElement=nil --#OPSGROUP.Element + local cargobayIndex=nil + local reserved=nil + for i,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for j,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if cargo.group and cargo.group.groupname==CargoGroup.groupname then + CarrierElement=element + cargobayIndex=j + reserved=cargo.reserved + end + end + end + ]] + + local cargoBayItem, cargoBayIndex, CarrierElement=self:_GetCargobay(CargoGroup) + + if cargoBayItem and cargoBayIndex then + + -- Remove + table.remove(CarrierElement.cargoBay, cargoBayIndex) + + -- Reduce weight (if cargo space was not just reserved). + if not cargoBayItem.reserved then + local weight=CargoGroup:GetWeightTotal() + self:RedWeightCargo(CarrierElement.name, weight) end - return true + return true end env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") @@ -5117,11 +5269,12 @@ function OPSGROUP:DelCargoTransport(CargoTransport) end ---- Get total weight of the group including cargo. +--- Get total weight of the group including cargo. Optionally, the total weight of a specific unit can be requested. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Total weight in kg. -function OPSGROUP:GetWeightTotal(UnitName) +function OPSGROUP:GetWeightTotal(UnitName, IncludeReserved) local weight=0 for _,_element in pairs(self.elements) do @@ -5129,7 +5282,21 @@ function OPSGROUP:GetWeightTotal(UnitName) if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then - weight=weight+element.weight + weight=weight+element.weightEmpty + + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + + local wcargo=0 + + -- Count cargo that is not reserved or if reserved cargo should be included. + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + wcargo=cargo.group:GetWeightTotal(element.name) + end + + weight=weight+wcargo + + end end @@ -5141,24 +5308,15 @@ end --- Get free cargo bay weight. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Free cargo bay in kg. -function OPSGROUP:GetFreeCargobay(UnitName) +function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) - local Free=0 - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - - if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then - local free=element.weightMaxCargo-element.weightCargo - --[[ - for _,_opsgroup in pairs(element.reservedCargos or {}) do - local opsgroup=_opsgroup --#OPSGROUP - free=free-opsgroup:GetWeightTotal() - end - ]] - Free=Free+free - end - end + local weightCargoMax=self:GetWeightCargoMax(UnitName) + + local weightCargo=self:GetWeightCargo(UnitName, IncludeReserved) + + local Free=weightCargoMax-weightCargo self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) return Free @@ -5198,8 +5356,9 @@ end --- Get weight of the internal cargo the group is carriing right now. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. -- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargo(UnitName) +function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) local weight=0 for _,_element in pairs(self.elements) do @@ -5213,34 +5372,36 @@ function OPSGROUP:GetWeightCargo(UnitName) end - local gewicht=0 - for groupname, carriername in pairs(self.cargoBay) do - local element=self:GetElementByName(carriername) - if (UnitName==nil or UnitName==carriername) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then - local opsgroup=_DATABASE:FindOpsGroup(groupname) - if opsgroup then - gewicht=gewicht+opsgroup:GetWeightTotal() + local gewicht=0 + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if (UnitName==nil or UnitName==element.name) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + gewicht=gewicht+cargo.group:GetWeightTotal() + end end end end - - if gewicht~=weight then + if IncludeReserved==false and gewicht~=weight then self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) end - return weight + return gewicht end ---- Get max weight of the internal cargo the group can carry. +--- Get max weight of the internal cargo the group can carry. Optionally, the max cargo weight of a specific unit can be requested. -- @param #OPSGROUP self --- @return #number Cargo weight in kg. -function OPSGROUP:GetWeightCargoMax() +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @return #number Max cargo weight in kg. This does **not** include any cargo loaded or reserved currently. +function OPSGROUP:GetWeightCargoMax(UnitName) local weight=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element - if element.status~=OPSGROUP.ElementStatus.DEAD then + if (UnitName==nil or UnitName==element.name) and element.status~=OPSGROUP.ElementStatus.DEAD then weight=weight+element.weightMaxCargo @@ -5263,6 +5424,9 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) -- Add weight. element.weightCargo=element.weightCargo+Weight + + -- Debug info. + self:I(self.lid..string.format("FF %s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. if self.isFlightgroup then @@ -5613,6 +5777,8 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Find a carrier that has enough free cargo bay for this group. local carrier=_findCarrier(weight) + + local carrier=self:FindCarrierForCargo(cargo.opsgroup) if carrier then @@ -5694,9 +5860,6 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if carrier then - -- Add into carrier bay. - self:_AddCargobay(CargoGroup, carrier) - --- -- Embark Cargo --- @@ -5710,8 +5873,8 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Clear all waypoints. CargoGroup:ClearWaypoints() - -- Set carrier (again). - CargoGroup:_SetMyCarrier(self, carrier, false) + -- Add into carrier bay. + self:_AddCargobay(CargoGroup, carrier, false) -- Despawn this group. if CargoGroup:IsAlive() then @@ -6308,7 +6471,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) end -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. - self:_SetMyCarrier(CarrierGroup, Carrier, true) + CarrierGroup:_AddCargobay(self, Carrier, true) else @@ -6337,8 +6500,9 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:T(self.lid.."Carrier not ready for boarding yet ==> repeating boarding call in 10 sec") self:__Board(-10, CarrierGroup, Carrier) - -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. - self:_SetMyCarrier(CarrierGroup, Carrier, true) + -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. + CarrierGroup:_AddCargobay(self, Carrier, true) + --self:_SetMyCarrier(CarrierGroup, Carrier, true) end @@ -8750,6 +8914,8 @@ function OPSGROUP:_AddElementByName(unitname) -- Max cargo weight: unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit + + element.cargoBay={} -- FLIGHTGROUP specific. if self.isFlightgroup then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 5c093a086..3b5434add 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -59,7 +59,12 @@ -- -- # The OPSTRANSPORT Concept -- --- This class simulates troop transport using carriers such as APCs, ships helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classed). +-- This class simulates troop transport using carriers such as APCs, ships, helicopters or airplanes. The carriers and transported groups need to be OPSGROUPS (see ARMYGROUP, NAVYGROUP and FLIGHTGROUP classes). +-- +-- **IMPORTANT NOTES** +-- +-- * Cargo groups are **not** split and distributed into different carrier *units*. That means that the whole cargo group **must fit** into one of the carrier units. +-- * Cargo groups must be inside the pickup zones to be considered for loading. Groups not inside the pickup zone will not get the command to board. -- -- -- @field #OPSTRANSPORT @@ -714,8 +719,10 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) text=text..string.format("\nCarriers:") for _,_carrier in pairs(self.carriers) do local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s [%s], cargo=%d/%d kg, free cargo bay %d/%d kg", - carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), carrier:GetFreeCargobayMax(true), carrier:GetFreeCargobayMax()) + text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), + carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), + carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) end self:I(self.lid..text) From 5a022a2246a76a6b8186f540ec5c29ef06d376e0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jun 2021 08:50:46 +0200 Subject: [PATCH 321/382] Messages going to SAR flights only --- Moose Development/Moose/Ops/CSAR.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7be6de481..07b8e103e 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.4r4" +CSAR.version="0.1.5r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -601,7 +601,8 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if not _freq then @@ -759,8 +760,9 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + end + self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!", self.coalition, 10) + --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) else self:T(self.lid .. " Pilot has not taken off, ignore") end From 3289ad281794f15089caf43d2ff3a7f0aa4f93e9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Jun 2021 17:49:42 +0200 Subject: [PATCH 322/382] Added basic support for Hercules mod --- Moose Development/Moose/Ops/CTLD.lua | 173 ++++++++++++++++++++++++--- 1 file changed, 156 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6c62d5046..4adaa0c24 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Date: 22 June 2021 +-- Date: July 2021 do ------------------------------------------------------ @@ -379,6 +379,27 @@ do -- -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. -- +-- ## 5. Support for Hercules mod by Anubis +-- +-- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can +-- be loaded from the Rearm/Refuel menu, i.e. you can drop them into the field, but you cannot use them in functions scripted with this class. +-- +-- local my_ctld = CTLD:New(coalition.side.BLUE,{"Helicargo", "Hercules"},"Lufttransportbrigade I") +-- +-- Enable these options for Hercules support: +-- +-- my_ctld.enableHercules = true +-- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- +-- Also, the following options need to be set to `true`: +-- +-- my_ctld.useprefix = true -- this is true by default +-- +-- Standard transport capabilities as per the real Hercules are: +-- +-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -415,6 +436,7 @@ CTLD = { -- DONE: Troops running to WP Zone -- DONE: Zone Radio Beacons -- DONE: Stats Running +-- DONE: Added support for Hercules ------------------------------ --- Radio Beacons @@ -468,6 +490,7 @@ CTLD.UnitTypes = { ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, + ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers } --- Updated and sorted known NDB beacons (in kHz!) from the available maps @@ -590,7 +613,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- setup self.CrateDistance = 30 -- list/load crates in this radius - self.prefixes = Prefixes or {"cargoheli"} + self.prefixes = Prefixes or {"Cargoheli"} + --self.I({prefixes = self.prefixes}) self.useprefix = true self.maximumHoverHeight = 15 @@ -602,6 +626,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movetroopstowpzone = true self.movetroopsdistance = 5000 + -- added support Hercules Mod + self.enableHercules = true + self.HercMinAngels = 165 -- for troop/cargo drop via chute + self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + for i=1,100 do math.random() end @@ -863,7 +892,12 @@ function CTLD:_EventHandler(EventData) local _group = event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then self:_RefreshF10Menus() - end + end + -- Herc support + self:I(_unit:GetTypeName()) + if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self:_RefreshF10Menus() + end return elseif event.id == EVENTS.PlayerLeaveUnit then -- remove from pilot table @@ -965,6 +999,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) return self end -- spawn crates in front of helicopter + local IsHerc = self:IsHercules(Unit) -- Herc local cargotype = Cargo -- #CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -980,8 +1015,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) local cratedistance = i*4 + 6 + if IsHerc then + -- wider radius + cratedistance = i*4 + 12 + end local rheading = math.floor(math.random(90,270) * heading + 1 / 360) - local rheading = rheading + 180 -- mirror + if not IsHerc then + rheading = rheading + 180 -- mirror for Helis + end if rheading > 360 then rheading = rheading - 360 end -- catch > 360 local cratecoord = position:Translate(cratedistance,rheading) local cratevec2 = cratecoord:GetVec2() @@ -1249,6 +1290,18 @@ function CTLD:_ListCargo(Group, Unit) return self end +--- (Internal) Function to check if a unit is a Hercules C-130. +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHercules(Unit) + if Unit:GetTypeName() == "Hercules" then + return true + else + return false + end +end + --- (Internal) Function to unload troops from heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1263,6 +1316,11 @@ function CTLD:_UnloadTroops(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded @@ -1280,8 +1338,14 @@ function CTLD:_UnloadTroops(Group, Unit) local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local position = Group:GetCoordinate() - local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(10,30):GetVec2() + local zoneradius = 100 -- drop zone radius + local factor = 1 + if IsHerc then + factor = cargo:GetCratesNeeded() or 1 -- spread a bit more if airdropping + zoneradius = Unit:GetVelocityMPS() or 100 + end + local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,zoneradius*factor) + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) @@ -1321,7 +1385,11 @@ function CTLD:_UnloadTroops(Group, Unit) self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + if IsHerc then + local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end end return self end @@ -1342,6 +1410,11 @@ function CTLD:_UnloadCrates(Group, Unit) end -- check for hover unload local hoverunload = self:IsCorrectHover(Unit) --if true we\'re hovering in parameters + local IsHerc = self:IsHercules(Unit) + if IsHerc then + -- no hover but airdrop here + hoverunload = self:IsCorrectFlightParameters(Unit) + end -- check if we\'re landed local grounded = not self:IsUnitInAir(Unit) -- Get what we have loaded @@ -1379,7 +1452,11 @@ function CTLD:_UnloadCrates(Group, Unit) self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + if IsHerc then + local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + else + local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + end end return self end @@ -1579,10 +1656,10 @@ end -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:T(self.lid .. " _RefreshF10Menus") + self:I(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - + --self:I({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1644,7 +1721,11 @@ function CTLD:_RefreshF10Menus() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() end local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + if unittype == "Hercules" then + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + else + local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show hover parameters",topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() + end self.MenusDone[_unitName] = true end -- end group end -- end unit @@ -1901,7 +1982,7 @@ end --- (Internal) Function to refresh radio beacons -- @param #CTLD self function CTLD:_RefreshRadioBeacons() - self:I(self.lid .. " _RefreshRadioBeacons") + self:T(self.lid .. " _RefreshRadioBeacons") local zones = {[1] = self.pickupZones, [2] = self.wpZones, [3] = self.dropOffZones} for i=1,3 do @@ -2070,7 +2151,35 @@ end local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) - if (uspeed <= maxh) and (aheight <= maxh) and (aheight >= minh) then + if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then + -- yep within parameters + outcome = true + end + end + return outcome + end + + --- (Internal) Check if a Hercules is flying *in parameters* for air drops. + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD:IsCorrectFlightParameters(Unit) + self:T(self.lid .. " IsCorrectFlightParameters") + local outcome = false + -- see if we are in air and within parameters. + if self:IsUnitInAir(Unit) then + -- get speed and height + local uspeed = Unit:GetVelocityMPS() + local uheight = Unit:GetHeight() + local ucoord = Unit:GetCoordinate() + local gheight = ucoord:GetLandHeight() + local aheight = uheight - gheight -- height above ground + local maxh = self.HercMinAngels-- 1500m + local minh = self.HercMaxAngels -- 5000m + local mspeed = 2 -- 2 m/s + -- TODO:Add speed test for Herc, should not be above 280kph/150kn + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + if (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true end @@ -2086,17 +2195,34 @@ end local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end - local text = string.format("Hover parameter (autoload):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end + --- (Internal) List if a Herc unit is flying *in parameters*. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + function CTLD:_ShowFlightParams(Group,Unit) + local inhover = self:IsCorrectFlightParameters(Unit) + local htxt = "true" + if not inhover then htxt = "false" end + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + return self + end + + --- (Internal) Check if a unit is in a load zone and is hovering in parameters. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") + if self:IsHercules(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) return outcome end @@ -2107,11 +2233,15 @@ end -- @return #boolean Outcome function CTLD:IsUnitInAir(Unit) -- get speed and height + local minheight = self.minimumHoverHeight + if self.enableHercules and Unit:GetTypeName() == "Hercules" then + minheight = 5.1 -- herc is 5m AGL on the ground + end local uheight = Unit:GetHeight() local ucoord = Unit:GetCoordinate() local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - if aheight >= self.minimumHoverHeight then + if aheight >= minheight then return true else return false @@ -2174,8 +2304,17 @@ end function CTLD:onafterStart(From, Event, To) self:I({From, Event, To}) if self.useprefix then - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.prefixes):FilterCategoryHelicopter():FilterStart() + local prefix = self.prefixes + self:I({prefix=prefix}) + if self.enableHercules then + --self:I("CTLD with prefixes and Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() + else + --self:I("CTLD with prefixes NO Hercules") + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() + end else + --self:I("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end -- Events @@ -2320,4 +2459,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- +------------------------------------------------------------------- \ No newline at end of file From dca626bbcbabf1edf7e9bbd622b6e700416dd70e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 30 Jun 2021 17:59:43 +0200 Subject: [PATCH 323/382] CTLD - added option to suppress messaging, added event for `OnAfterTroopsRTB` CSAR - minor bugfix --- Moose Development/Moose/Ops/CSAR.lua | 9 +- Moose Development/Moose/Ops/CTLD.lua | 224 ++++++++++++++++++--------- 2 files changed, 152 insertions(+), 81 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 07b8e103e..4a9344a54 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -187,7 +187,7 @@ CSAR = { useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below csarPrefix = {}, template = nil, - bluemash = {}, + mash = {}, smokecolor = 4, rescues = 0, rescuedpilots = 0, @@ -240,13 +240,14 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r1" +CSAR.version="0.1.5r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- DONE: SRS Integration (to be tested) +-- TODO: Maybe - add option to smoke/flare closest MASH ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -353,7 +354,7 @@ function CSAR:New(Coalition, Template, Alias) self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON\'T use # in names! self.template = Template or "generic" -- template for downed pilot self.mashprefix = {"MASH"} -- prefixes used to find MASHes - self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near -- added 0.1.4 self.limitmaxdownedpilots = true @@ -1528,7 +1529,7 @@ end -- @retunr function CSAR:_GetClosestMASH(_heli) self:T(self.lid .. " _GetClosestMASH") - local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashset = self.mash -- Core.Set#SET_GROUP local _mashes = _mashset:GetSetObjects() -- #table local _shortestDistance = -1 local _distance = 0 diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4adaa0c24..78edc5a75 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -270,6 +270,7 @@ do -- my_ctld.movetroopstowpzone = true -- Troops and vehicles will move to the nearest MOVE zone... -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) +-- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. -- -- ## 2.1 User functions -- @@ -512,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.1b2" +CTLD.version="0.1.2b1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -521,7 +522,6 @@ CTLD.version="0.1.1b2" -- @param #string Alias Alias of this CTLD for logging. -- @return #CTLD self function CTLD:New(Coalition, Prefixes, Alias) - -- TODO: CTLD Marker -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CTLD @@ -568,14 +568,15 @@ function CTLD:New(Coalition, Prefixes, Alias) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- CTLD status update. + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. + self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables self.PilotGroups ={} @@ -627,10 +628,13 @@ function CTLD:New(Coalition, Prefixes, Alias) self.movetroopsdistance = 5000 -- added support Hercules Mod - self.enableHercules = true + self.enableHercules = false self.HercMinAngels = 165 -- for troop/cargo drop via chute self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + -- message suppression + self.suppressmessages = false + for i=1,100 do math.random() end @@ -678,7 +682,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo crate. + -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. @@ -725,6 +729,15 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self + --- FSM Function OnAfterTroopsRTB. + -- @function [parent=#CTLD] OnAfterTroopsRTB + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + return self end @@ -894,7 +907,7 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - self:I(_unit:GetTypeName()) + --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then self:_RefreshF10Menus() end @@ -907,6 +920,20 @@ function CTLD:_EventHandler(EventData) return self end +--- (Internal) Function to message a group. +-- @param #CTLD self +-- @param #string Text The text to display. +-- @param #number Time Number of seconds to display the message. +-- @param #boolean Clearscreen Clear screen or not. +-- @param Wrapper.Group#GROUP Group The group receiving the message. +function CTLD:_SendMessage(Text, Time, Clearscreen, Group) + self:T(self.lid .. " _SendMessage") + if not self.suppressmessages then + local m = MESSAGE:New(Text,Time,"CTLD",Clearscreen):ToGroup(Group) + end + return self +end + --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -920,10 +947,11 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) -- check if we are in LOAD zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then - local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) if not self.debug then return self end elseif not grounded and not hoverload then - local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end -- load troops into heli @@ -932,7 +960,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local unitname = unit:GetName() local cargotype = Cargotype -- #CTLD_CARGO local cratename = cargotype:GetName() -- #string - self:T(self.lid .. string.format("Troops %s requested", cratename)) + --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities @@ -952,13 +980,15 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) loaded.Cargo = {} end if troopsize + numberonboard > trooplimit then - local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,Cargotype) self.Loaded_Cargo[unitname] = loaded - local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) + self:_SendMessage("Troops boarded!", 10, false, Group) + --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) end return self @@ -974,7 +1004,7 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") local cgoname = Cargo:GetName() - self:T({cgoname, number, drop}) + --self:T{cgoname, number, drop}) -- check if we are in LOAD zone local inzone = true @@ -985,7 +1015,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end if not inzone then - local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end @@ -995,7 +1026,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then - local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) + --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) return self end -- spawn crates in front of helicopter @@ -1004,7 +1036,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() - self:T(self.lid .. string.format("Crate %s requested", cratename)) + --self:Tself.lid .. string.format("Crate %s requested", cratename)) local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() @@ -1047,8 +1079,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) if drop then text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) - end - local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) + end + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) return self end @@ -1079,9 +1112,11 @@ function CTLD:_ListCratesNearby( _group, _unit) text:Add("--------- N O N E ------------") end text:Add("------------------------------------------------------------") - local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) + self:_SendMessage(text:Text(), 30, true, _group) + --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) else - local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) end return self end @@ -1129,7 +1164,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) end end end - self:T(string.format("Found crates = %d",index)) + --self:Tstring.format("Found crates = %d",index)) -- table.sort(found) --self:T({found}) return found, index @@ -1160,11 +1195,14 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) elseif self.forcehoverload and not canhoverload then - local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) elseif not grounded and not canhoverload then - local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -1182,7 +1220,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) local finddist = self.CrateDistance or 30 local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 or numberonboard == cratelimit then - local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) + --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) return -- exit else -- go through crates and load @@ -1210,7 +1249,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- destroy crate crate:GetPositionable():Destroy() crate.Positionable = nil - local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) self:__CratesPickedUp(1, Group, Unit, crate) end --if loaded.Cratesloaded == cratelimit then break end @@ -1283,9 +1323,11 @@ function CTLD:_ListCargo(Group, Unit) end report:Add("------------------------------------------------------------") local text = report:Text() - local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) else - local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) + --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) end return self end @@ -1358,12 +1400,15 @@ function CTLD:_UnloadTroops(Group, Unit) end end -- template loop cargo:SetWasDropped(true) - local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) - self:__TroopsDeployed(1, Group, Unit, name, self.DroppedTroops[self.TroopCounter]) + self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) + --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase - local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("Troops have returned to base!", 10, false, Group) + --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) + self:__TroopsRTB(1, Group, Unit) end -- cleanup load list local loaded = {} -- #CTLD.LoadedCargo @@ -1386,9 +1431,11 @@ function CTLD:_UnloadTroops(Group, Unit) self.Loaded_Cargo[unitname] = loaded else if IsHerc then - local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1403,7 +1450,8 @@ function CTLD:_UnloadCrates(Group, Unit) -- check if we are in DROP zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then - local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end @@ -1453,9 +1501,11 @@ function CTLD:_UnloadCrates(Group, Unit) self.Loaded_Cargo[unitname] = loaded else if IsHerc then - local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else - local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1501,7 +1551,7 @@ function CTLD:_BuildCrates(Group, Unit) end foundbuilds = true end - self:T({buildables = buildables}) + --self:T{buildables = buildables}) end -- end dropped end -- end crate loop -- ok let\'s list what we have @@ -1516,14 +1566,15 @@ function CTLD:_BuildCrates(Group, Unit) if build.CanBuild then txtok = "YES" end - self:T({name,needed,found,txtok}) + --self:T{name,needed,found,txtok}) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables if not foundbuilds then report:Add(" --- None Found ---") end report:Add("------------------------------------------------------------") local text = report:Text() - local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) + self:_SendMessage(text, 30, true, Group) + --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) -- let\'s get going if canbuild then -- loop again @@ -1536,7 +1587,8 @@ function CTLD:_BuildCrates(Group, Unit) end end else - local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) end -- number > 0 return self end @@ -1580,17 +1632,17 @@ function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() - self:T(self.lid .. " _MoveGroupToZone for " .. groupname) + --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) - self:T(string.format("Closest WP zone %s is %d meters",name,distance)) + --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() - self:T(string.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) + --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() - self:T({coordinate=coordinate}) + --self:T{coordinate=coordinate}) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) @@ -1609,7 +1661,7 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable - self:T({Build = Build}) + --self:T{Build = Build}) local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy @@ -1623,18 +1675,18 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() - self:T(string.format("Looking for Crate for %s", name)) + --self:Tstring.format("Looking for Crate for %s", name)) local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy() nowcrate.Positionable = nil - self:T(string.format("%s Found %d Need %d", name, found, numberdest)) + --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) end if found == numberdest then break end -- got enough end - self:T({destIDs}) + --self:T{destIDs}) -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO @@ -1656,10 +1708,10 @@ end -- @param #CTLD self -- @return #CTLD self function CTLD:_RefreshF10Menus() - self:I(self.lid .. " _RefreshF10Menus") + self:T(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - --self:I({PlayerTable=PlayerTable}) + --self:T({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1774,13 +1826,13 @@ function CTLD:AddZone(Zone) local zone = Zone -- #CTLD.CargoZone if zone.type == CTLD.CargoZoneType.LOAD then table.insert(self.pickupZones,zone) - self:T("Registered LOAD zone " .. zone.name) + --self:T"Registered LOAD zone " .. zone.name) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) - self:T("Registered DROP zone " .. zone.name) + --self:T"Registered DROP zone " .. zone.name) else table.insert(self.wpZones,zone) - self:T("Registered MOVE zone " .. zone.name) + --self:T"Registered MOVE zone " .. zone.name) end return self end @@ -1955,7 +2007,8 @@ function CTLD:_ListRadioBeacons(Group, Unit) report:Add("--------- N O N E ------------") end report:Add("------------------------------------------------------------") - local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) + self:_SendMessage(report:Text(), 30, true, Group) + --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) return self end @@ -2018,7 +2071,7 @@ end function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") local unitname = Unit:GetName() - self:T(string.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) + --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) local zonetable = {} local outcome = false if Zonetype == CTLD.CargoZoneType.LOAD then @@ -2044,7 +2097,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local color = czone.color local zoneradius = zone:GetRadius() local distance = self:_GetDistance(zonecoord,unitcoord) - self:T(string.format("Check distance: %d",distance)) + --self:Tstring.format("Check distance: %d",distance)) if distance <= zoneradius and active then outcome = true end @@ -2055,7 +2108,7 @@ function CTLD:IsUnitInZone(Unit,Zonetype) colorret = color end end - self:T({outcome, zonenameret, zoneret, maxdist}) + --self:T{outcome, zonenameret, zoneret, maxdist}) return outcome, zonenameret, zoneret, maxdist end @@ -2090,14 +2143,16 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) end local txt = "smoking" if Flare then txt = "flaring" end - local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) + self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) + --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) smoked = true end end end if not smoked then - local distance = UTILS.MetersToNM(self.smkedistance) - local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) + local distance = UTILS.MetersToNM(self.smokedistance) + self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) + --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) end return self end @@ -2150,7 +2205,7 @@ end local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2178,7 +2233,7 @@ end local minh = self.HercMaxAngels -- 5000m local mspeed = 2 -- 2 m/s -- TODO:Add speed test for Herc, should not be above 280kph/150kn - self:T(string.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) + --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2196,7 +2251,8 @@ end local htxt = "true" if not inhover then htxt = "false" end local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) - local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end @@ -2211,7 +2267,8 @@ end local minheight = UTILS.MetersToFeet(self.HercMinAngels) local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) - local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) + self:_SendMessage(text, 10, false, Group) + --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self end @@ -2302,19 +2359,19 @@ end -- @param #string To State. -- @return #CTLD self function CTLD:onafterStart(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) if self.useprefix then local prefix = self.prefixes - self:I({prefix=prefix}) + --self:T{prefix=prefix}) if self.enableHercules then - --self:I("CTLD with prefixes and Hercules") + --self:T("CTLD with prefixes and Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - --self:I("CTLD with prefixes NO Hercules") + --self:T("CTLD with prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() end else - --self:I("CTLD NO prefixes NO Hercules") + --self:T("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end -- Events @@ -2346,7 +2403,7 @@ end -- @param #string To State. -- @return #CTLD self function CTLD:onafterStatus(From, Event, To) - self:I({From, Event, To}) + self:T({From, Event, To}) -- gather some stats -- pilots local pilots = 0 @@ -2396,7 +2453,7 @@ end -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeTroopsPickedUp(From, Event, To, Group, Unit, Cargo) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2410,7 +2467,7 @@ end -- @param #CTLD_CARGO Cargo Cargo crate. -- @return #CTLD self function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2424,7 +2481,7 @@ end -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. -- @return #CTLD self function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2438,7 +2495,7 @@ end -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) - self:I({From, Event, To}) + self:T({From, Event, To}) return self end @@ -2452,7 +2509,20 @@ end -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) - self:I({From, Event, To}) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsRTB. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + self:T({From, Event, To}) return self end From fa3e387dd1c133d7e0ccead9d496aa1a8d89069e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 30 Jun 2021 23:23:41 +0200 Subject: [PATCH 324/382] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 17 -- Moose Development/Moose/Ops/FlightGroup.lua | 62 +---- Moose Development/Moose/Ops/NavyGroup.lua | 16 -- Moose Development/Moose/Ops/OpsGroup.lua | 258 +++++++++----------- 4 files changed, 120 insertions(+), 233 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index fc82983ac..f2953ede0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1055,23 +1055,6 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) end ---- On after "Stop" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function ARMYGROUP:onafterStop(From, Event, To) - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.Dead) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2ec865140..c68ae0edc 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2079,26 +2079,6 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) end ---- On after "Respawn" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #table Template The template used to respawn the group. -function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) - - self:T(self.lid.."Respawning group!") - - local template=UTILS.DeepCopy(Template or self.template) - - if self.group and self.group:InAir() then - template.lateActivation=false - self.respawning=true - self.group=self.group:Respawn(template) - end - -end - --- On after "OutOfMissilesAA" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -2856,44 +2836,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) end end ---- On after "Stop" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function FLIGHTGROUP:onafterStop(From, Event, To) - - -- Check if group is still alive. - if self:IsAlive() then - - -- Set element parking spot to FREE (after arrived for example). - if self.flightcontrol then - for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element - self:_SetElementParkingFree(element) - end - end - - end - - self.currbase=nil - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.EngineStartup) - self:UnHandleEvent(EVENTS.Takeoff) - self:UnHandleEvent(EVENTS.Land) - self:UnHandleEvent(EVENTS.EngineShutdown) - self:UnHandleEvent(EVENTS.PilotDead) - self:UnHandleEvent(EVENTS.Ejection) - self:UnHandleEvent(EVENTS.Crash) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3433,14 +3375,14 @@ function FLIGHTGROUP:InitWaypoints() self.currbase=self:GetHomebaseFromWaypoints() -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. - if self.destbase then + if self.destbase and #self.waypoints>1 then table.remove(self.waypoints, #self.waypoints) else self.destbase=self.homebase end -- Debug info. - self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) + self:I(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) -- Update route. if #self.waypoints>0 then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index fb836d489..d78b0a6a5 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1062,22 +1062,6 @@ function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) self.collisionwarning=true end ---- On after Start event. Starts the NAVYGROUP FSM and event handlers. --- @param #NAVYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function NAVYGROUP:onafterStop(From, Event, To) - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.Dead) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) - -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 13d1464e5..5273b0176 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -378,6 +378,7 @@ OPSGROUP.TaskType={ -- @field #boolean detour If true, this waypoint is not part of the normal route. -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. +-- @field #boolean temp If true, this is a temporary waypoint and will be deleted when passed. Also the passing waypoint FSM event is not triggered. -- @field #number npassed Number of times a groups passed this waypoint. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. -- @field Core.Point#COORDINATE roadcoord Closest point to road. @@ -2292,50 +2293,34 @@ function OPSGROUP:OnEventBirth(EventData) local group=EventData.IniGroup local unitname=EventData.IniUnitName - if self.respawning then - self:I(self.lid.."Respawning unit "..tostring(unitname)) + -- Set homebase if not already set. + if self.isFlightgroup then - local function reset() - self.respawning=nil - self:_CheckGroupDone() - end - - -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. - -- TODO: Can I do this more rigorously? - self:ScheduleOnce(1, reset) - - else - - -- Set homebase if not already set. - if self.isFlightgroup then - - if EventData.Place then - self.homebase=self.homebase or EventData.Place - self.currbase=EventData.Place - else - self.currbase=nil - end - - if self.homebase and not self.destbase then - self.destbase=self.homebase - end - - self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + if EventData.Place then + self.homebase=self.homebase or EventData.Place + self.currbase=EventData.Place else - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) + self.currbase=nil end - -- Get element. - local element=self:GetElementByName(unitname) - - -- Set element to spawned state. - self:ElementSpawned(element) + if self.homebase and not self.destbase then + self.destbase=self.homebase + end + self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + else + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end - end + -- Get element. + local element=self:GetElementByName(unitname) + -- Set element to spawned state. + self:ElementSpawned(element) + + end + end --- Event function handling the crash of a unit. @@ -3755,7 +3740,7 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "PassingWaypoint" event. +--- Check if group is currently waiting. -- @param #OPSGROUP self -- @param #boolean If true, group is currently waiting. function OPSGROUP:IsWaiting() @@ -3776,8 +3761,10 @@ function OPSGROUP:onafterWait(From, Event, To, Duration) -- Order Group to hold. self:FullStop() + -- Set time stamp. self.Twaiting=timer.getAbsTime() + -- Max waiting self.dTwait=Duration end @@ -4567,25 +4554,6 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - -- Check cargo bay and declare cargo groups dead. - --[[ - for groupname, carriername in pairs(self.cargoBay or {}) do - if Element.name==carriername then - local opsgroup=_DATABASE:GetOpsGroup(groupname) - if opsgroup and not (opsgroup:IsDead() or opsgroup:IsStopped()) then - for _,element in pairs(opsgroup.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..element.name) - - -- Trigger dead event. - opsgroup:ElementDead(element) - - end - end - end - end - ]] -- Check cargo bay and declare cargo groups dead. for _,_element in pairs(self.elements) do @@ -4593,25 +4561,31 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) for _,_cargo in pairs(element.cargoBay) do local cargo=_cargo --#OPSGROUP.MyCargo if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then - for _,cargoelement in pairs(cargo.group.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..cargoelement.name) - - -- Trigger dead event. - cargo.group:ElementDead(cargoelement) - + + -- Remove my carrier + cargo.group:_RemoveMyCarrier() + + if cargo.reserved then + -- This group was not loaded yet ==> Not cargo any more. + cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + else + + -- Carrier dead ==> cargo dead. + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + + end end + end end end - if self:IsCarrier() then - if self.cargoTransport then - self.cargoTransport:DeadCarrierGroup(self) - end - end - end --- On after "Respawn" event. @@ -4713,6 +4687,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Debug output. self:I({Template=Template}) + + --if self:IsStopped() then + --self:InitWaypoints() + --end -- Spawn new group. _DATABASE:Spawn(Template) @@ -4720,6 +4698,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Set activation and controlled state. self.isLateActivated=Template.lateActivation self.isUncontrolled=Template.uncontrolled + -- Reset events. --self:ResetEvents() @@ -4772,20 +4751,45 @@ function OPSGROUP:onafterDead(From, Event, To) end + -- Delete waypoints so they are re-initialized at the next spawn. + self:ClearWaypoints() + self.groupinitialized=false + + -- Set cargo status to NOTCARGO. + self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER + + -- Remove from cargo bay of carrier. + local mycarrier=self:_GetMyCarrierGroup() + if mycarrier and not mycarrier:IsDead() then + mycarrier:_DelCargobay(self) + self:_RemoveMyCarrier() + end + -- Inform all transports in the queue that this carrier group is dead now. for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT - transport:DeadCarrierGroup(self) + transport:__DeadCarrierGroup(1, self) end - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false -- Stop in a sec. self:__Stop(-5) end +--- On before "Stop" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeStop(From, Event, To) + + if self:IsAlive() then + return false + end + + return true +end + --- On after "Stop" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -4793,6 +4797,23 @@ end -- @param #string To To state. function OPSGROUP:onafterStop(From, Event, To) + -- Handle events: + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.RemoveUnit) + + -- Handle events: + if self.isFlightgroup then + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.PilotDead) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.Crash) + self.currbase=nil + end + -- Stop check timers. self.timerCheckZone:Stop() self.timerQueueUpdate:Stop() @@ -4827,18 +4848,6 @@ function OPSGROUP:_CheckCargoTransport() local Time=timer.getAbsTime() -- Cargo bay debug info. - --[[ - if self.verbose>=3 then - local text="" - for cargogroupname, carriername in pairs(self.cargoBay) do - text=text..string.format("\n- %s in carrier %s", tostring(cargogroupname), tostring(carriername)) - end - if text~="" then - self:I(self.lid.."Cargo bay:"..text) - end - end - ]] - -- Check cargo bay and declare cargo groups dead. if self.verbose>=0 then local text="" @@ -5044,7 +5053,6 @@ function OPSGROUP:_AddCargobay(CargoGroup, CarrierElement, Reserved) table.insert(CarrierElement.cargoBay, cargo) end - -- Set my carrier. CargoGroup:_SetMyCarrier(self, CarrierElement, Reserved) @@ -5500,22 +5508,6 @@ function OPSGROUP:FindCarrierForCargo(CargoGroup) return nil end ---- Reserve cargo space for a cargo group. --- @param #OPSGROUP self --- @param #OPSGROUP CargoGroup Cargo group, which needs a carrier. --- @return #OPSGROUP.Element Carrier able to transport the cargo. -function OPSGROUP:ReserveCargoSpace(CargoGroup) - - local element=self:FindCarrierForCargo(CargoGroup) - - if element then - element.reservedCargo=element.reservedCargo or {} - table.insert(element.reservedCargo, CargoGroup) - end - - return nil -end - --- Set my carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CarrierGroup Carrier group. @@ -5688,8 +5680,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -5733,26 +5724,6 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Loading time stamp. self.Tloading=timer.getAbsTime() - -- Create a temp array and monitor the free cargo space for each element. - local cargobay={} - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - cargobay[element.name]=element.weightMaxCargo-element.weightCargo - end - - - --- Find a carrier which can load a given weight. - local function _findCarrier(weight) - local carrier=nil --#OPSGROUP.Element - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - if cargobay[element.name]>=weight then - return element - end - end - return nil - end - --TODO: sort cargos wrt weight. -- Loop over all cargos. @@ -5772,19 +5743,11 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Cargo MUST be inside zone or it will not be loaded! if inzone then - -- Weight of cargo. - local weight=cargo.opsgroup:GetWeightTotal() - - -- Find a carrier that has enough free cargo bay for this group. - local carrier=_findCarrier(weight) - + -- Find a carrier for this cargo. local carrier=self:FindCarrierForCargo(cargo.opsgroup) if carrier then - -- Decrease free cargo bay. - cargobay[carrier.name]=cargobay[carrier.name]-weight - -- Set cargo status. cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED @@ -5918,6 +5881,23 @@ function OPSGROUP:onafterLoaded(From, Event, To) end +--- On before "Transport" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeTransport(From, Event, To) + + if self.cargoTransport==nil then + return false + elseif self.cargoTransport:IsDelivered() then --could be if all cargo was dead on boarding + return false + end + + return true +end + + --- On after "Transport" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -6011,8 +5991,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -6461,12 +6440,10 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:ClearWaypoints() if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 self:Cruise() else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) - waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 self:Cruise() end @@ -7322,6 +7299,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) opsgroup:Cruise() else opsgroup:E("ERROR: waypoint.detour should be 0 or 1") + opsgroup:FullStop() end end From 76a53ab1543c70c89026bf73a4f0571ed65b01bb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 1 Jul 2021 09:07:01 +0200 Subject: [PATCH 325/382] Added extra checks for Beacon refresh --- Moose Development/Moose/Ops/CSAR.lua | 86 +++++++++++++++++----------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 4a9344a54..a42384db2 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r2" +CSAR.version="0.1.5r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -489,6 +489,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript self.downedPilots = PilotTable -- Increase counter self.downedpilotcounter = self.downedpilotcounter+1 + return self end --- (Internal) Count pilots on board. @@ -579,7 +580,7 @@ function CSAR:_AddSpecialOptions(group) group:OptionAlarmStateGreen() group:OptionROEHoldFire() - + return self end --- (Internal) Function to spawn a CSAR object into the scene. @@ -634,6 +635,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + return self end --- (Internal) Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -672,6 +674,8 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ end self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) + + return self end --- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. @@ -852,7 +856,7 @@ function CSAR:_EventHandler(EventData) return true end - + return self end --- (Internal) Initialize the action for a pilot. @@ -881,6 +885,8 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) -- trigger FSM event self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) + + return self end --- (Internal) Check if a name is in downed pilot table @@ -984,6 +990,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...Downed Pilot KIA?!") self:_RemoveNameFromDownedPilots(_downedpilot.name) end + return self end --- (Internal) Function to pop a smoke at a wounded pilot\'s positions. @@ -1001,6 +1008,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) _smokecoord:Smoke(_smokecolor) self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time end + return self end --- (Internal) Function to pickup the wounded pilot from the ground. @@ -1065,6 +1073,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) group:SetAIOn() group:RouteToVec2(coordinate,5) + return self end --- (Internal) Function to check if heli is close to group. @@ -1235,33 +1244,34 @@ end function CSAR:_ScheduledSARFlight(heliname,groupname) self:T(self.lid .. " _ScheduledSARFlight") self:T({heliname,groupname}) - local _heliUnit = self:_GetSARHeli(heliname) - local _woundedGroupName = groupname + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname - if (_heliUnit == nil) then - --helicopter crashed? - self.inTransitGroups[heliname] = nil - return - end + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end - if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then - -- Groups already rescued - return - end + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end - local _dist = self:_GetClosestMASH(_heliUnit) + local _dist = self:_GetClosestMASH(_heliUnit) - if _dist == -1 then - return - end + if _dist == -1 then + return + end - if _dist < 200 and _heliUnit:InAir() == false then - self:_RescuePilots(_heliUnit) - return - end + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end - --queue up - self:__Returning(-5,heliname,_woundedGroupName) + --queue up + self:__Returning(-5,heliname,_woundedGroupName) + return self end --- (Internal) Mark pilot as rescued and remove from tables. @@ -1287,6 +1297,7 @@ function CSAR:_RescuePilots(_heliUnit) self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) + return self end --- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. @@ -1325,6 +1336,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) local msrs = MSRS:New(path,channel,modulation) msrs:PlaySoundText(srstext, 2) end + return self end --- (Internal) Function to get string of a group\'s position. @@ -1403,6 +1415,7 @@ function CSAR:_DisplayActiveSAR(_unitName) end self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) + return self end --- (Internal) Find the closest downed pilot to a heli. @@ -1472,6 +1485,7 @@ function CSAR:_SignalFlare(_unitName) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end --- (Internal) Display info to all SAR groups. @@ -1489,6 +1503,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) end end end + return self end ---(Internal) Request smoke at closest downed pilot. @@ -1521,6 +1536,7 @@ function CSAR:_Reqsmoke( _unitName ) end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end + return self end --- (Internal) Determine distance to closest MASH. @@ -1598,6 +1614,7 @@ function CSAR:_CheckOnboard(_unitName) end self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end + return self end --- (Internal) Populate F10 menu for CSAR players. @@ -1640,7 +1657,7 @@ function CSAR:_AddMedevacMenuItem() end end end - return + return self end --- (Internal) Return distance in meters between two coordinates. @@ -1727,6 +1744,7 @@ function CSAR:_GenerateVHFrequencies() _start = _start + 50000 end self.FreeVHFFrequencies = FreeVHFFrequencies + return self end --- (Internal) Pop frequency from prepopulated table. @@ -1791,6 +1809,7 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight end + return self end --- (Internal) Helper function to (re-)add beacon to downed pilot. @@ -1798,15 +1817,18 @@ end -- @param #table _args Arguments function CSAR:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") - local PilotTable = self.downedPilots - for _,_pilot in pairs (PilotTable) do - local pilot = _pilot -- #CSAR.DownedPilot - local group = pilot.group - local frequency = pilot.frequency or 0 -- thanks to @Thrud - if frequency and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) + if self:_CountActiveDownedPilots() > 0 then + local PilotTable = self.downedPilots + for _,_pilot in pairs (PilotTable) do + local pilot = _pilot -- #CSAR.DownedPilot + local group = pilot.group + local frequency = pilot.frequency or 0.0 -- thanks to @Thrud + if group:IsAlive() and frequency > 0.0 then + self:_AddBeaconToGroup(group,frequency) + end end end + return self end --- (Internal) Helper function to count active downed pilots. From 3c6e8d6d01e31f1ce06c0cc690e8a2e2a7d99aa1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 1 Jul 2021 12:29:29 +0200 Subject: [PATCH 326/382] OPS - Transport - Respawn --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 177 +++++++++---------- Moose Development/Moose/Ops/OpsTransport.lua | 70 +++++--- 3 files changed, 121 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c68ae0edc..5534aa11e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -863,7 +863,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - + -- Update position. self:_UpdatePosition() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 5273b0176..fc0022178 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -525,7 +525,7 @@ function OPSGROUP:New(group) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. - self:AddTransition("*", "Respawn", "*") -- Respawn group. + self:AddTransition("*", "Respawn", "InUtero") -- Respawn group. self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -4473,7 +4473,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementInUtero(From, Event, To, Element) - self:I(self.lid..string.format("Element in utero %s", Element.name)) + self:T(self.lid..string.format("Element in utero %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.INUTERO) @@ -4503,9 +4503,6 @@ function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) -- Element is dead. self:ElementDead(Element) - -- Set element status. - --self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) - end --- On after "ElementDead" event. @@ -4554,36 +4551,40 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end end - - -- Check cargo bay and declare cargo groups dead. - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - for _,_cargo in pairs(element.cargoBay) do - local cargo=_cargo --#OPSGROUP.MyCargo - if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + + -- Clear cargo bay of element. + --for _,_cargo in pairs(Element.cargoBay) do + for i=#Element.cargoBay,1,-1 do + local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo --_cargo --#OPSGROUP.MyCargo + + -- Remove from cargo bay. + self:_DelCargobay(cargo.group) + + if cargo.group and not (cargo.group:IsDead() or cargo.group:IsStopped()) then + + -- Remove my carrier + cargo.group:_RemoveMyCarrier() - -- Remove my carrier - cargo.group:_RemoveMyCarrier() + if cargo.reserved then + + -- This group was not loaded yet ==> Not cargo any more. + cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - if cargo.reserved then - -- This group was not loaded yet ==> Not cargo any more. - cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO - else - - -- Carrier dead ==> cargo dead. - for _,cargoelement in pairs(cargo.group.elements) do - - -- Debug info. - self:T2(self.lid.."Cargo element dead "..cargoelement.name) - - -- Trigger dead event. - cargo.group:ElementDead(cargoelement) - - end + else + + -- Carrier dead ==> cargo dead. + for _,cargoelement in pairs(cargo.group.elements) do + + -- Debug info. + self:T2(self.lid.."Cargo element dead "..cargoelement.name) + + -- Trigger dead event. + cargo.group:ElementDead(cargoelement) + end - - end - end + end + + end end end @@ -4596,14 +4597,15 @@ end -- @param #table Template The template used to respawn the group. Default is the inital template of the group. function OPSGROUP:onafterRespawn(From, Event, To, Template) + -- Debug info. self:I(self.lid.."Respawning group!") + -- Copy template. local template=UTILS.DeepCopy(Template or self.template) + -- Late activation off. template.lateActivation=false - --self.respawning=true - self:_Respawn(0, template) end @@ -4686,12 +4688,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end -- Debug output. - self:I({Template=Template}) + self:T({Template=Template}) - --if self:IsStopped() then - --self:InitWaypoints() - --end - -- Spawn new group. _DATABASE:Spawn(Template) @@ -4699,6 +4697,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self.isLateActivated=Template.lateActivation self.isUncontrolled=Template.uncontrolled + -- Not dead or destroyed any more. + self.isDead=false + self.isDestroyed=false + self.Ndestroyed=0 -- Reset events. --self:ResetEvents() @@ -4765,7 +4767,7 @@ function OPSGROUP:onafterDead(From, Event, To) mycarrier:_DelCargobay(self) self:_RemoveMyCarrier() end - + -- Inform all transports in the queue that this carrier group is dead now. for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT @@ -4783,7 +4785,9 @@ end -- @param #string To To state. function OPSGROUP:onbeforeStop(From, Event, To) + -- We check if if self:IsAlive() then + self:E(self.lid..string.format("WARNING: Group is still alive! Will not stop the FSM. Use :Despawn() instead")) return false end @@ -4849,7 +4853,7 @@ function OPSGROUP:_CheckCargoTransport() -- Cargo bay debug info. -- Check cargo bay and declare cargo groups dead. - if self.verbose>=0 then + if self.verbose>=1 then local text="" for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element @@ -4858,9 +4862,10 @@ function OPSGROUP:_CheckCargoTransport() text=text..string.format("\n- %s in carrier %s, reserved=%s", tostring(cargo.group:GetName()), tostring(element.name), tostring(cargo.reserved)) end end - if text~="" then - self:I(self.lid.."Cargo bay:"..text) - end + if text=="" then + text=" empty" + end + self:I(self.lid.."Cargo bay:"..text) end -- Cargo queue debug info. @@ -5110,47 +5115,15 @@ function OPSGROUP:_DelCargobay(CargoGroup) self.cargoBay[CargoGroup.groupname]=nil end - - - --[[ - local MyCarrierGroup, MyCarrierElement, MyIsReserved=CargoGroup:_GetMyCarrier() - - if MyCarrierGroup and MyCarrierGroup.groupname==self.groupname then - if not IsReserved then - - -- Reduce carrier weight. - local weight=CargoGroup:GetWeightTotal() - - self:RedWeightCargo(CarrierElement.name, weight) - - end - - end - ]] - - - -- Loop over elements and their cargo bay items. - --[[ - local CarrierElement=nil --#OPSGROUP.Element - local cargobayIndex=nil - local reserved=nil - for i,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - for j,_cargo in pairs(element.cargoBay) do - local cargo=_cargo --#OPSGROUP.MyCargo - if cargo.group and cargo.group.groupname==CargoGroup.groupname then - CarrierElement=element - cargobayIndex=j - reserved=cargo.reserved - end - end - end - ]] - + + -- Get cargo bay info. local cargoBayItem, cargoBayIndex, CarrierElement=self:_GetCargobay(CargoGroup) if cargoBayItem and cargoBayIndex then + -- Debug info. + self:T(self.lid..string.format("Removing cargo group %s from cargo bay (index=%d) of carrier %s", CargoGroup:GetName(), cargoBayIndex, CarrierElement.name)) + -- Remove table.remove(CarrierElement.cargoBay, cargoBayIndex) @@ -5163,7 +5136,7 @@ function OPSGROUP:_DelCargobay(CargoGroup) return true end - env.error(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") + self:E(self.lid.."ERROR: Group is not in cargo bay. Cannot remove it!") return false end @@ -5320,13 +5293,18 @@ end -- @return #number Free cargo bay in kg. function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) + -- Max cargo weight. local weightCargoMax=self:GetWeightCargoMax(UnitName) + -- Current cargo weight. local weightCargo=self:GetWeightCargo(UnitName, IncludeReserved) + -- Free cargo. local Free=weightCargoMax-weightCargo - self:I(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) + -- Debug info. + self:T(self.lid..string.format("Free cargo bay=%d kg (unit=%s)", Free, (UnitName or "whole group"))) + return Free end @@ -5368,6 +5346,7 @@ end -- @return #number Cargo weight in kg. function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) + -- Calculate weight based on actual cargo weight. local weight=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element @@ -5380,20 +5359,28 @@ function OPSGROUP:GetWeightCargo(UnitName, IncludeReserved) end + -- Calculate weight from stuff in cargo bay. By default this includes the reserved weight if a cargo group was assigned and is currently boarding. local gewicht=0 for _,_element in pairs(self.elements) do local element=_element --#OPSGROUP.Element if (UnitName==nil or UnitName==element.name) and (element and element.status~=OPSGROUP.ElementStatus.DEAD) then for _,_cargo in pairs(element.cargoBay) do local cargo=_cargo --#OPSGROUP.MyCargo - if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then - gewicht=gewicht+cargo.group:GetWeightTotal() + if (not cargo.reserved) or (cargo.reserved==true and (IncludeReserved==true or IncludeReserved==nil)) then + local cargoweight=cargo.group:GetWeightTotal() + gewicht=gewicht+cargoweight + --self:I(self.lid..string.format("unit=%s (reserved=%s): cargo=%s weight=%d, total weight=%d", tostring(UnitName), tostring(IncludeReserved), cargo.group:GetName(), cargoweight, weight)) end end end end + + -- Debug info. + self:T2(self.lid..string.format("Unit=%s (reserved=%s): weight=%d, gewicht=%d", tostring(UnitName), tostring(IncludeReserved), weight, gewicht)) + + -- Quick check. if IncludeReserved==false and gewicht~=weight then - self:I(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) + self:E(self.lid..string.format("ERROR: FF weight!=gewicht: weight=%.1f, gewicht=%.1f", weight, gewicht)) end return gewicht @@ -5434,7 +5421,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) element.weightCargo=element.weightCargo+Weight -- Debug info. - self:I(self.lid..string.format("FF %s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) + self:T(self.lid..string.format("%s: Adding %.1f kg cargo weight. New cargo weight=%.1f kg", UnitName, Weight, element.weightCargo)) -- For airborne units, we set the weight in game. if self.isFlightgroup then @@ -5516,7 +5503,7 @@ end function OPSGROUP:_SetMyCarrier(CarrierGroup, CarrierElement, Reserved) -- Debug info. - self:I(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s", CarrierGroup:GetName(), tostring(CarrierElement.name), tostring(Reserved))) + self:T(self.lid..string.format("Setting My Carrier: %s (%s), reserved=%s", CarrierGroup:GetName(), tostring(CarrierElement.name), tostring(Reserved))) self.mycarrier.group=CarrierGroup self.mycarrier.element=CarrierElement @@ -5572,6 +5559,7 @@ end -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:_RemoveMyCarrier() + self:I(self.lid..string.format("Removing my carrier!")) self.mycarrier.group=nil self.mycarrier.element=nil self.mycarrier.reserved=nil @@ -5635,8 +5623,6 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then - - env.info("FF pickup is flightgroup") if airbasePickup then @@ -5651,13 +5637,10 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Activate uncontrolled group. if self:IsParking() then - env.info("FF pickup start uncontrolled while parking at current airbase") self:StartUncontrolled() end else - - env.info("FF pickup land at airbase") -- Order group to land at an airbase. self:LandAtAirbase(airbasePickup) @@ -5669,12 +5652,9 @@ function OPSGROUP:onafterPickup(From, Event, To) --- -- Helo can also land in a zone (NOTE: currently VTOL cannot!) --- - - env.info("FF pickup helo addwaypoint") -- Activate uncontrolled group. if self:IsParking() then - env.info("FF pickup start uncontrolled while parking airbase") self:StartUncontrolled() end @@ -5809,6 +5789,7 @@ end -- @param #OPSGROUP CargoGroup The OPSGROUP loaded as cargo. -- @param #OPSGROUP.Element Carrier The carrier element/unit. function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) + -- Debug info. self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) @@ -5904,6 +5885,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) + -- Debug info. self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) @@ -6107,7 +6089,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) --- -- Issue warning. - env.info("ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") + self:E(self.lid.."ERROR: Deploy/disembark zone is a ZONE_AIRBASE of a ship! Where to put the cargo? Dumping into the sea, sorry!") --TODO: Dumb into sea. else @@ -6479,7 +6461,6 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) -- Set carrier. As long as the group is not loaded, we only reserve the cargo space. CarrierGroup:_AddCargobay(self, Carrier, true) - --self:_SetMyCarrier(CarrierGroup, Carrier, true) end diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 3b5434add..c8d69a5ca 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -32,9 +32,11 @@ -- @field #table cargos Cargos. Each element is a @{Ops.OpsGroup#OPSGROUP.CargoGroup}. -- @field #table carriers Carriers assigned for this transport. -- @field #number prio Priority of this transport. Should be a number between 0 (high prio) and 100 (low prio). +-- @field #boolean urgent If true, transport is urgent. -- @field #number importance Importance of this transport. Smaller=higher. -- @field #number Tstart Start time in *abs.* seconds. -- @field #number Tstop Stop time in *abs.* seconds. Default `#nil` (never stops). +-- @field #number duration Duration (`Tstop-Tstart`) of the transport in seconds. -- @field #table conditionStart Start conditions. -- @field Core.Zone#ZONE pickupzone Zone where the cargo is picked up. -- @field Core.Zone#ZONE deployzone Zone where the cargo is dropped off. @@ -70,7 +72,7 @@ -- @field #OPSTRANSPORT OPSTRANSPORT = { ClassName = "OPSTRANSPORT", - verbose = 1, + verbose = 0, cargos = {}, carriers = {}, carrierTransportStatus = {}, @@ -108,12 +110,13 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.7" +OPSTRANSPORT.version="0.0.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Stop/abort transport. -- DONE: Add start conditions. -- DONE: Check carrier(s) dead. @@ -140,7 +143,6 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- Defaults. self.uid=_OPSTRANSPORTID - --self.status=OPSTRANSPORT.Status.PLANNING self.pickupzone=Pickupzone self.deployzone=Deployzone @@ -277,7 +279,8 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetDisembarkCarriers(Carriers) - self:I(self.lid.."Setting transfer carriers!") + -- Debug info. + self:T(self.lid.."Setting transfer carriers!") -- Create table. self.disembarkCarriers=self.disembarkCarriers or {} @@ -326,7 +329,8 @@ end -- @return #OPSTRANSPORT self function OPSTRANSPORT:SetRequiredCargos(Cargos) - self:I(self.lid.."Setting required cargos!") + -- Debug info. + self:T(self.lid.."Setting required cargos!") -- Create table. self.requiredCargos=self.requiredCargos or {} @@ -390,11 +394,11 @@ function OPSTRANSPORT:_DelCarrier(CarrierGroup) if self:IsCarrier(CarrierGroup) then - for i,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP + for i=#self.carriers,1,-1 do + local carrier=self.carriers[i] --Ops.OpsGroup#OPSGROUP if carrier.groupname==CarrierGroup.groupname then - self:I(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) - table.remove(self.carriers, i) + self:T(self.lid..string.format("Removing carrier %s", CarrierGroup.groupname)) + table.remove(self.carriers, i) end end @@ -704,29 +708,37 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) -- Current FSM state. local fsmstate=self:GetState() - -- Info text. - local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) + if self.verbose>=1 then - text=text..string.format("\nCargos:") - for _,_cargo in pairs(self.cargos) do - local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup - local carrier=cargo.opsgroup:_GetMyCarrierElement() - local name=carrier and carrier.name or "none" - local cstate=carrier and carrier.status or "N/A" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s]", cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate) + -- Info text. + local text=string.format("%s [%s --> %s]: Ncargo=%d/%d, Ncarrier=%d/%d", fsmstate:upper(), self.pickupzone:GetName(), self.deployzone:GetName(), self.Ncargo, self.Ndelivered, #self.carriers,self.Ncarrier) + + -- Info about cargo and carrier. + if self.verbose>=2 then + + text=text..string.format("\nCargos:") + for _,_cargo in pairs(self.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + local carrier=cargo.opsgroup:_GetMyCarrierElement() + local name=carrier and carrier.name or "none" + local cstate=carrier and carrier.status or "N/A" + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s", + cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered)) + end + + text=text..string.format("\nCarriers:") + for _,_carrier in pairs(self.carriers) do + local carrier=_carrier --Ops.OpsGroup#OPSGROUP + text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", + carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), + carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), + carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) + end + end + + self:I(self.lid..text) end - text=text..string.format("\nCarriers:") - for _,_carrier in pairs(self.carriers) do - local carrier=_carrier --Ops.OpsGroup#OPSGROUP - text=text..string.format("\n- %s: %s [%s], Cargo Bay [current/reserved/total]=%d/%d/%d kg [free %d/%d/%d kg]", - carrier:GetName(), carrier.carrierStatus:upper(), carrier:GetState(), - carrier:GetWeightCargo(nil, false), carrier:GetWeightCargo(), carrier:GetWeightCargoMax(), - carrier:GetFreeCargobay(nil, false), carrier:GetFreeCargobay(), carrier:GetFreeCargobayMax()) - end - - self:I(self.lid..text) - -- Check if all cargo was delivered (or is dead). self:_CheckDelivered() From c80cebb82485a1f7f8ee79f8e2faaaad9f50b45f Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 1 Jul 2021 23:54:24 +0200 Subject: [PATCH 327/382] WAREHOUSE, AIRWING & SQUADRON - Added function for hot start to SQUADRON - Addressed bug #1560 --- Moose Development/Moose/Core/Point.lua | 8 +- .../Moose/Functional/Warehouse.lua | 45 +++-- Moose Development/Moose/Ops/AirWing.lua | 170 ++++++++---------- Moose Development/Moose/Ops/Squadron.lua | 39 +++- 4 files changed, 148 insertions(+), 114 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 061d61ed5..354b584c1 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1607,8 +1607,12 @@ do -- COORDINATE roadtype="railroads" end local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) - local vec2={ x = x, y = y } - return COORDINATE:NewFromVec2(vec2) + local coord=nil + if x and y then + local vec2={ x = x, y = y } + coord=COORDINATE:NewFromVec2(vec2) + end + return coord end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0d4f074c4..1d4e76d2e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5790,21 +5790,32 @@ 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(alias, asset, request, parking, uncontrolled, hotstart) +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled) 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) + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + uncontrolled=false + end + + -- Set route points. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then -- Get flight path if the group goes to another warehouse by itself. if request.toself then - local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, false, self.airbase, {}, "Parking") + local wp=self.airbase:GetCoordinate():WaypointAir("RADIO", _type, _action, 0, false, self.airbase, {}, "Parking") template.route.points={wp} else template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) @@ -5812,18 +5823,8 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol 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") + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", _type, _action, 0, true, self.airbase, nil, "Spawnpoint") end @@ -9040,9 +9041,23 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) local wp={} local c={} + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then + env.info("FF hot") + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + else + env.info("FF cold") + end + + --- Departure/Take-off c[#c+1]=Pdeparture - wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb*3.6, true, departure, nil, "Departure") + wp[#wp+1]=Pdeparture:WaypointAir("RADIO", _type, _action, VxClimb*3.6, true, departure, nil, "Departure") --- Begin of Cruise local Pcruise=Pdeparture:Translate(d_climb, heading) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 202eb631d..4493264c9 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -32,6 +32,7 @@ -- @field #table pointsCAP Table of CAP points. -- @field #table pointsTANKER Table of Tanker points. -- @field #table pointsAWACS Table of AWACS points. +-- @field #boolean markpoints Display markers on the F10 map. -- @field Ops.WingCommander#WINGCOMMANDER wingcommander The wing commander responsible for this airwing. -- -- @field Ops.RescueHelo#RESCUEHELO rescuehelo The rescue helo. @@ -153,7 +154,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.5.1" +AIRWING.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -210,7 +211,7 @@ function AIRWING:New(warehousename, airwingname) self.nflightsTANKERprobe=0 self.nflightsRecoveryTanker=0 self.nflightsRescueHelo=0 - self.markpoints = false + self.markpoints=false ------------------------ --- Pseudo Functions --- @@ -1551,6 +1552,9 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) asset.nunits=squad.ngrouping end + + -- Set takeoff type. + asset.takeoffType=squad.takeoffType -- Create callsign and modex (needs to be after grouping). squad:GetCallsign(asset) @@ -1616,84 +1620,86 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Call parent warehouse function first. self:GetParent(self).onafterAssetSpawned(self, From, Event, To, group, asset, request) - - -- Create a flight group. - local flightgroup=self:_CreateFlightGroup(asset) - - - --- - -- Asset - --- - - -- Set asset flightgroup. - asset.flightgroup=flightgroup - - -- Not requested any more. - asset.requested=nil - - -- Did not return yet. - asset.Treturned=nil - - --- - -- Squadron - --- -- Get the SQUADRON of the asset. local squadron=self:GetSquadronOfAsset(asset) - -- Get TACAN channel. - local Tacan=squadron:FetchTacan() - if Tacan then - asset.tacan=Tacan - end - - -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() - if radioFreq then - flightgroup:SwitchRadio(radioFreq, radioModu) - end + -- Check if we have a squadron or if this was some other request. + if squadron then + + -- Create a flight group. + local flightgroup=self:_CreateFlightGroup(asset) + + --- + -- Asset + --- - if squadron.fuellow then - flightgroup:SetFuelLowThreshold(squadron.fuellow) - end - - if squadron.fuellowRefuel then - flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) - end - - --- - -- Mission - --- - - -- Get Mission (if any). - local mission=self:GetMissionByID(request.assignment) - - -- Add mission to flightgroup queue. - if mission then + -- Set asset flightgroup. + asset.flightgroup=flightgroup + + -- Not requested any more. + asset.requested=nil + + -- Did not return yet. + asset.Treturned=nil + --- + -- Squadron + --- + + -- Get TACAN channel. + local Tacan=squadron:FetchTacan() if Tacan then - mission:SetTACAN(Tacan, Morse, UnitName, Band) + asset.tacan=Tacan + end + + -- Set radio frequency and modulation + local radioFreq, radioModu=squadron:GetRadio() + if radioFreq then + flightgroup:SwitchRadio(radioFreq, radioModu) end - -- Add mission to flightgroup queue. - asset.flightgroup:AddMission(mission) - - -- Trigger event. - self:FlightOnMission(flightgroup, mission) - - else - - if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + if squadron.fuellow then + flightgroup:SetFuelLowThreshold(squadron.fuellow) end - - end - + if squadron.fuellowRefuel then + flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + end - -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander and self.wingcommander.chief then - self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + --- + -- Mission + --- + + -- Get Mission (if any). + local mission=self:GetMissionByID(request.assignment) + + -- Add mission to flightgroup queue. + if mission then + + if Tacan then + mission:SetTACAN(Tacan, Morse, UnitName, Band) + end + + -- Add mission to flightgroup queue. + asset.flightgroup:AddMission(mission) + + -- Trigger event. + self:FlightOnMission(flightgroup, mission) + + else + + if Tacan then + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + end + + end + + -- Add group to the detection set of the WINGCOMMANDER. + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) + end + end end @@ -1822,34 +1828,6 @@ function AIRWING:_CreateFlightGroup(asset) -- Set home base. flightgroup.homebase=self.airbase - --[[ - - --- Check if out of missiles. For A2A missions ==> RTB. - function flightgroup:OnAfterOutOfMissiles() - local airwing=flightgroup:GetAirWing() - - end - - --- Check if out of missiles. For A2G missions ==> RTB. But need to check A2G missiles, rockets as well. - function flightgroup:OnAfterOutOfBombs() - local airwing=flightgroup:GetAirWing() - - end - - --- Mission started. - function flightgroup:OnAfterMissionStart(From, Event, To, Mission) - local airwing=flightgroup:GetAirWing() - - end - - --- Flight is DEAD. - function flightgroup:OnAfterFlightDead(From, Event, To) - local airwing=flightgroup:GetAirWing() - - end - - ]] - return flightgroup end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 5ed71fba7..c0f53d960 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -45,6 +45,7 @@ -- @field #table tacanChannel List of TACAN channels available to the squadron. -- @field #number radioFreq Radio frequency in MHz the squad uses. -- @field #number radioModu Radio modulation the squad uses. +-- @field #number takeoffType Take of type. -- @extends Core.Fsm#FSM --- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland @@ -87,12 +88,13 @@ SQUADRON = { --- SQUADRON class version. -- @field #string version -SQUADRON.version="0.5.0" +SQUADRON.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Parking spots for squadrons? -- DONE: Engage radius. -- DONE: Modex. -- DONE: Call signs. @@ -282,6 +284,41 @@ function SQUADRON:SetGrouping(nunits) return self end + +--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines. +-- Spawning on runways is not supported. +-- @param #SQUADRON self +-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on. +-- @return #SQUADRON self +function SQUADRON:SetTakeoffType(TakeoffType) + TakeoffType=TakeoffType or "Cold" + if TakeoffType:lower()=="hot" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot + elseif TakeoffType:lower()=="cold" then + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + else + self.takeoffType=COORDINATE.WaypointType.TakeOffParking + end + return self +end + +--- Set takeoff type cold (default). +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffCold() + self:SetTakeoffType("Cold") + return self +end + +--- Set takeoff type hot. +-- @param #SQUADRON self +-- @return #SQUADRON self +function SQUADRON:SetTakeoffHot() + self:SetTakeoffType("Hot") + return self +end + + --- Set mission types this squadron is able to perform. -- @param #SQUADRON self -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. From 353d6dfec0ed1d574945c537ca6c3e7b87e9f3ea Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 2 Jul 2021 08:51:51 +0200 Subject: [PATCH 328/382] Update Airbase.lua - Corrected Mariana Islands airbase names - Fixed bug in Syria airbase name `["Thalah"]="Tha'lah"` --- Moose Development/Moose/Wrapper/Airbase.lua | 49 +++++++++++---------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 615cf6870..e63218ac5 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -73,7 +73,7 @@ AIRBASE = { --- Enumeration to identify the airbases in the Caucasus region. -- --- These are all airbases of Caucasus: +-- Airbases of the Caucasus map: -- -- * AIRBASE.Caucasus.Gelendzhik -- * AIRBASE.Caucasus.Krasnodar_Pashkovsky @@ -122,7 +122,7 @@ AIRBASE.Caucasus = { ["Beslan"] = "Beslan", } ---- These are all airbases of Nevada: +--- Airbases of the Nevada map: -- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB @@ -141,6 +141,7 @@ AIRBASE.Caucasus = { -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield +-- -- @field Nevada AIRBASE.Nevada = { ["Creech_AFB"] = "Creech AFB", @@ -162,7 +163,7 @@ AIRBASE.Nevada = { ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield", } ---- These are all airbases of Normandy: +--- Airbases of the Normandy map: -- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles @@ -195,6 +196,7 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Funtington -- * AIRBASE.Normandy.Tangmere -- * AIRBASE.Normandy.Ford_AF +-- -- @field Normandy AIRBASE.Normandy = { ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", @@ -237,7 +239,7 @@ AIRBASE.Normandy = { ["Conches"] = "Conches", } ---- These are all airbases of the Persion Gulf Map: +--- Airbases of the Persion Gulf Map: -- -- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport @@ -268,6 +270,7 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak +-- -- @field PersianGulf AIRBASE.PersianGulf = { ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", @@ -301,7 +304,7 @@ AIRBASE.PersianGulf = { ["Tunb_Kochak"] = "Tunb Kochak", } ---- These are all airbases of the The Channel Map: +--- Airbases of The Channel Map: -- -- * AIRBASE.TheChannel.Abbeville_Drucat -- * AIRBASE.TheChannel.Merville_Calonne @@ -326,7 +329,7 @@ AIRBASE.TheChannel = { ["High_Halden"] = "High Halden", } ---- Airbases of Syria +--- Airbases of the Syria map: -- -- * AIRBASE.Syria.Kuweires -- * AIRBASE.Syria.Marj_Ruhayyil @@ -348,7 +351,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Pinarbashi -- * AIRBASE.Syria.Paphos -- * AIRBASE.Syria.Kingsfield --- * AIRBASE.Syria.Tha'lah +-- * AIRBASE.Syria.Thalah -- * AIRBASE.Syria.Haifa -- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Megiddo @@ -404,7 +407,7 @@ AIRBASE.Syria={ ["Pinarbashi"]="Pinarbashi", ["Paphos"]="Paphos", ["Kingsfield"]="Kingsfield", - ["Tha'lah"]="Tha'lah", + ["Thalah"]="Tha'lah", ["Haifa"]="Haifa", ["Khalkhalah"]="Khalkhalah", ["Megiddo"]="Megiddo", @@ -416,7 +419,6 @@ AIRBASE.Syria={ ["Akrotiri"]="Akrotiri", ["Naqoura"]="Naqoura", ["Gaziantep"]="Gaziantep", - ["CVN_71"]="CVN-71", ["Sayqal"]="Sayqal", ["Tiyas"]="Tiyas", ["Shayrat"]="Shayrat", @@ -441,22 +443,23 @@ AIRBASE.Syria={ ---- Airbases of the Mariana Islands map. +--- Airbases of the Mariana Islands map: -- --- * AIRBASE.MarianaIslands.Rota_International_Airport --- * AIRBASE.MarianaIslands.Andersen --- * AIRBASE.MarianaIslands.Northwest_Field --- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_International_Airport --- * AIRBASE.MarianaIslands.Saipan_International_Airport --- * AIRBASE.MarianaIslands.Tinian_International_Airport --- @field MarianaIslands +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Andersen_AFB +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.Olf_Orote +-- +--@field MarianaIslands AIRBASE.MarianaIslands={ - ["Rota_International_Airport"]="Rota International Airport", - ["Andersen"]="Andersen", - ["Northwest_Field"]="Northwest_Field", - ["Antonio_B_Won_Pat_International_Airport"]="Antonio B. Won Pat International Airport", - ["Saipan_International_Airport"]="Saipan International Airport", - ["Tinian_International_Airport"]="Tinian International Airport", + ["Rota_Intl"]="Rota Intl", + ["Andersen_AFB"]="Andersen AFB", + ["Antonio_B_Won_Pat_Intl"]="Antonio B. Won Pat Intl", + ["Saipan_Intl"]="Saipan Intl", + ["Tinian_Intl"]="Tinian Intl", + ["Olf_Orote"]="Olf Orote", } From 12555a6ff1e6e8fa45af8d27f16a06ef98cbb3b4 Mon Sep 17 00:00:00 2001 From: "wob3155@posteo.de" Date: Fri, 2 Jul 2021 08:56:28 +0200 Subject: [PATCH 329/382] Reactivate the :destroy() event on clients with scoring below thresmark --- Moose Development/Moose/Functional/Scoring.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b5c8da81b..b92c7e789 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -667,8 +667,6 @@ function SCORING:_AddPlayerFromUnit( UnitData ) self.Players[PlayerName].ThreatLevel = UnitThreatLevel self.Players[PlayerName].ThreatType = UnitThreatType - -- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed. - --[[ if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then if self.Players[PlayerName].PenaltyWarning < 1 then MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty, @@ -684,8 +682,6 @@ function SCORING:_AddPlayerFromUnit( UnitData ) ):ToAll() UnitData:GetGroup():Destroy() end - --]] - end end From 299e08f53d713482e251fd538c04a5b6b3d8c782 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 17:52:52 +0200 Subject: [PATCH 330/382] additional checks to ensure only human players, extra checks in the logic, some logic errors corrected. --- Moose Development/Moose/Ops/CSAR.lua | 40 +++++++----- Moose Development/Moose/Ops/CTLD.lua | 96 ++++++++++++++++++---------- 2 files changed, 86 insertions(+), 50 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a42384db2..ef181a759 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -18,11 +18,11 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: June 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.5r3" +CSAR.version="0.1.6r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -290,7 +290,7 @@ function CSAR:New(Coalition, Template, Alias) self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then - self.alias="Спасение" + self.alias="Ã�¡Ã�¿Ã�°Ñ�Ã�µÃ�½Ã�¸Ã�µ" elseif self.coalition==coalition.side.BLUE then self.alias="CSAR" end @@ -527,15 +527,18 @@ end -- @param #CSAR self -- @param #number country Country for template. -- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @param #number frequency Frequency of the pilot's beacon -- @return Wrapper.Group#GROUP group The #GROUP object. -- @return #string alias The alias name. -function CSAR:_SpawnPilotInField(country,point) - self:T({country,point}) +function CSAR:_SpawnPilotInField(country,point,frequency) + self:T({country,point,frequency}) + local freq = frequency or 1000 + local freq = freq / 1000 -- kHz for i=1,10 do math.random(i,10000) end local template = self.template - local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,10000)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -545,7 +548,7 @@ function CSAR:_SpawnPilotInField(country,point) :InitAIOnOff(pilotcacontrol) :InitDelayOff() :SpawnFromCoordinate(point) - + return _spawnedGroup, alias -- Wrapper.Group#GROUP object end @@ -599,19 +602,21 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) local template = self.template - - local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = "333250" end --noob catch + end + + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) + local _typeName = _typeName or "PoW" + if not noMessage then self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end - if not _freq then - _freq = self:_GenerateADFFrequency() - if not _freq then _freq = "333.25" end --noob catch - end - if _freq then self:_AddBeaconToGroup(_spawnedGroup, _freq) end @@ -1631,7 +1636,7 @@ function CSAR:_AddMedevacMenuItem() for _key, _group in pairs (_allHeliGroups) do local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players if _unit then - if _unit:IsAlive() then + if _unit:IsAlive() and _unit:IsPlayer() then local unitName = _unit:GetName() _UnitList[unitName] = unitName end -- end isAlive @@ -1820,10 +1825,11 @@ function CSAR:_RefreshRadioBeacons() if self:_CountActiveDownedPilots() > 0 then local PilotTable = self.downedPilots for _,_pilot in pairs (PilotTable) do + self:T({_pilot}) local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group local frequency = pilot.frequency or 0.0 -- thanks to @Thrud - if group:IsAlive() and frequency > 0.0 then + if group and group:IsAlive() and frequency > 0.0 then self:_AddBeaconToGroup(group,frequency) end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 78edc5a75..9808d16da 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -18,7 +18,7 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) -- @module Ops.CTLD -- @image OPS_CTLD.jpg @@ -260,7 +260,7 @@ do -- -- The following options are available (with their defaults). Only set the ones you want changed: -- --- my_ctld.useprefix = true -- Adjust *before* starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. +-- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. @@ -316,7 +316,7 @@ do -- -- This function is called when a player has loaded Troops: -- --- function CTLD:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) +-- function my_ctld:OnAfterTroopsPickedUp(From, Event, To, Group, Unit, Cargo) -- ... your code here ... -- end -- @@ -324,7 +324,7 @@ do -- -- This function is called when a player has picked up crates: -- --- function CTLD:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) +-- function my_ctld:OnAfterCratesPickedUp(From, Event, To, Group, Unit, Cargo) -- ... your code here ... -- end -- @@ -332,7 +332,7 @@ do -- -- This function is called when a player has deployed troops into the field: -- --- function CTLD:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) +-- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) -- ... your code here ... -- end -- @@ -340,7 +340,7 @@ do -- -- This function is called when a player has deployed crates to a DROP zone: -- --- function CTLD:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... -- end -- @@ -348,7 +348,7 @@ do -- -- This function is called when a player has build a vehicle or FOB: -- --- function CTLD:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... -- end -- @@ -395,7 +395,7 @@ do -- -- Also, the following options need to be set to `true`: -- --- my_ctld.useprefix = true -- this is true by default +-- my_ctld.useprefix = true -- this is true by default and MUST BE ON. -- -- Standard transport capabilities as per the real Hercules are: -- @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.2b1" +CTLD.version="0.1.3r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -745,6 +745,28 @@ end -- Helper and User Functions ------------------------------------------------------------------- +--- (Internal) Function to get capabilities of a chopper +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit +-- @return #table Capabilities Table of caps +function CTLD:_GetUnitCapabilities(Unit) + self:T(self.lid .. " _GetUnitCapabilities") + local _unit = Unit -- Wrapper.Unit#UNIT + local unittype = _unit:GetTypeName() + local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + if not capabilities or capabilities == {} then + -- e.g. ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, + capabilities = {} + capabilities.troops = false + capabilities.crates = false + capabilities.cratelimit = 0 + capabilities.trooplimit = 0 + capabilities.type = "generic" + end + return capabilities +end + + --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self function CTLD:_GenerateUHFrequencies() @@ -963,7 +985,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number local troopsize = cargotype:GetCratesNeeded() -- #number @@ -1006,12 +1029,12 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local cgoname = Cargo:GetName() --self:T{cgoname, number, drop}) -- check if we are in LOAD zone - local inzone = true - - if drop then - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + local inzone = false + local drop = drop or false + if not drop then + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) else - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) end if not inzone then @@ -1021,7 +1044,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) end -- avoid crate spam - local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) @@ -1183,7 +1207,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) local unitname = unit:GetName() -- see if this heli can load crates local unittype = unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) @@ -1284,9 +1309,10 @@ function CTLD:_ListCargo(Group, Unit) self:T(self.lid .. " _ListCargo") local unitname = Unit:GetName() local unittype = Unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean - local cratelimit = capabilities.cratelimit -- #numbe + local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 @@ -1715,11 +1741,13 @@ function CTLD:_RefreshF10Menus() -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do - local _unit = _group:GetUnit(1) -- Asume that there is only one unit in the flight for players + local _unit = _group:GetUnit(1) -- Wrapper.Unit#UNIT Asume that there is only one unit in the flight for players if _unit then - if _unit:IsAlive() then - local unitName = _unit:GetName() - _UnitList[unitName] = unitName + if _unit:IsAlive() and _unit:IsPlayer() then + if _unit:IsHelicopter() or (_unit:GetTypeName() == "Hercules" and self.enableHercules) then --ensure no stupid unit entries here + local unitName = _unit:GetName() + _UnitList[unitName] = unitName + end end -- end isAlive end -- end if _unit end -- end for @@ -1737,7 +1765,8 @@ function CTLD:_RefreshF10Menus() if _group then -- get chopper capabilities local unittype = _unit:GetTypeName() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates -- top menu @@ -1790,8 +1819,8 @@ function CTLD:_RefreshF10Menus() --- User function - Add *generic* troop type loadable as cargo. This type will load directly into the heli without crates. -- @param #CTLD self --- @param #Name Name Unique name of this type of troop. E.g. "Anti-Air Small". --- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. +-- @param #string Name Unique name of this type of troop. E.g. "Anti-Air Small". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP making up this troop. -- @param #CTLD_CARGO.Enum Type Type of cargo, here TROOPS - these will move to a nearby destination zone when dropped/build. -- @param #number NoTroops Size of the group in number of Units across combined templates (for loading). function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops) @@ -1805,8 +1834,8 @@ end --- User function - Add *generic* crate-type loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. -- @param #CTLD self --- @param #Name Name Unique name of this type of cargo. E.g. "Humvee". --- @param #Table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #table Templates Table of #string names of late activated Wrapper.Group#GROUP building this cargo. -- @param #CTLD_CARGO.Enum Type Type of cargo. I.e. VEHICLE or FOB. VEHICLE will move to destination zones when dropped/build, FOB stays put. -- @param #number NoCrates Number of crates needed to build this cargo. function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) @@ -2315,7 +2344,8 @@ end local unittype = Unit:GetTypeName() local unitname = Unit:GetName() local Group = Unit:GetGroup() - local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -2360,7 +2390,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) - if self.useprefix then + if self.useprefix or self.enableHercules then local prefix = self.prefixes --self:T{prefix=prefix}) if self.enableHercules then @@ -2368,11 +2398,11 @@ end self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else --self:T("CTLD with prefixes NO Hercules") - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategoryHelicopter():FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else --self:T("CTLD NO prefixes NO Hercules") - self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) @@ -2529,4 +2559,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------- From 39c46dcab0803d8464140a5e25d2565827d08cc3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 20:15:12 +0200 Subject: [PATCH 331/382] Changed frequency logic, some documentation changes --- Moose Development/Moose/Ops/CSAR.lua | 8 ++++---- Moose Development/Moose/Ops/CTLD.lua | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ef181a759..39e6f0654 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -290,7 +290,7 @@ function CSAR:New(Coalition, Template, Alias) self.alias="Red Cross" if self.coalition then if self.coalition==coalition.side.RED then - self.alias="Ã�¡Ã�¿Ã�°Ñ�Ã�µÃ�½Ã�¸Ã�µ" + self.alias="IFRC" elseif self.coalition==coalition.side.BLUE then self.alias="CSAR" end @@ -538,7 +538,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency) math.random(i,10000) end local template = self.template - local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,10000)) + local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99)) local coalition = self.coalition local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN @@ -1828,8 +1828,8 @@ function CSAR:_RefreshRadioBeacons() self:T({_pilot}) local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group - local frequency = pilot.frequency or 0.0 -- thanks to @Thrud - if group and group:IsAlive() and frequency > 0.0 then + local frequency = pilot.frequency or 0 -- thanks to @Thrud + if group and group:IsAlive() and frequency > 0 then self:_AddBeaconToGroup(group,frequency) end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9808d16da..6ae0e4137 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -300,12 +300,12 @@ do -- Activate a zone: -- -- -- Activate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:ActivateZone(Name,ZoneType) +-- my_ctld:ActivateZone(Name,CTLD.CargoZoneType.MOVE) -- -- Deactivate a zone: -- -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: --- my_ctld:DeactivateZone(Name,ZoneType) +-- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) -- -- ## 3. Events -- From 9591c6217525b43b8f7dc37c3fbe06726a4ed935 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Jul 2021 20:22:43 +0200 Subject: [PATCH 332/382] corrected autovalue for frequency - thanks to shadowze --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 39e6f0654..15318de07 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -605,7 +605,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then _freq = "333250" end --noob catch + if not _freq then _freq = 333250 end --noob catch end local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) @@ -818,7 +818,7 @@ function CSAR:_EventHandler(EventData) -- all checks passed, get going. local _freq = self:_GenerateADFFrequency() - self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none") return true From 74bdeaf4f73224c7c813e925f476ce86fe667108 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 2 Jul 2021 23:11:51 +0200 Subject: [PATCH 333/382] AIRWING --- Moose Development/Moose/Functional/Warehouse.lua | 5 ++--- Moose Development/Moose/Ops/AirWing.lua | 11 +++++++---- Moose Development/Moose/Ops/Auftrag.lua | 7 +++++++ Moose Development/Moose/Ops/FlightGroup.lua | 6 +++++- Moose Development/Moose/Ops/OpsGroup.lua | 6 ++++++ Moose Development/Moose/Ops/Squadron.lua | 8 +++++--- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1d4e76d2e..cf02c728f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2631,9 +2631,8 @@ end --- Check parking ID. -- @param #WAREHOUSE self -- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot. --- @param Wrapper.Airbase#AIRBASE airbase The airbase. -- @return #boolean If true, parking is valid. -function WAREHOUSE:_CheckParkingValid(spot, airbase) +function WAREHOUSE:_CheckParkingValid(spot) if self.parkingIDs==nil then return true @@ -7841,7 +7840,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) 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) and self:_CheckParkingValid(parkingspot, airbase) and airbase:_CheckParkingLists(parkingspot.TerminalID) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and airbase:_CheckParkingLists(parkingspot.TerminalID) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 62207fbb6..4b44b6b91 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1429,7 +1429,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) asset.flightgroup:AddMission(Mission) -- Trigger event. - self:FlightOnMission(asset.flightgroup, Mission) + self:__FlightOnMission(5, asset.flightgroup, Mission) else self:E(self.lid.."ERROR: flight group for asset does NOT exist!") @@ -1662,6 +1662,8 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) local Tacan=squadron:FetchTacan() if Tacan then asset.tacan=Tacan + --flightgroup:SetDefaultTACAN(Tacan,Morse,UnitName,Band,OffSwitch) + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) end -- Set radio frequency and modulation @@ -1689,19 +1691,19 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) if mission then if Tacan then - mission:SetTACAN(Tacan, Morse, UnitName, Band) + --mission:SetTACAN(Tacan, Morse, UnitName, Band) end -- Add mission to flightgroup queue. asset.flightgroup:AddMission(mission) -- Trigger event. - self:FlightOnMission(flightgroup, mission) + self:__FlightOnMission(5, flightgroup, mission) else if Tacan then - flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + --flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) end end @@ -1743,6 +1745,7 @@ end -- @param #string To To state. function AIRWING:onafterDestroyed(From, Event, To) + -- Debug message. self:I(self.lid.."Airwing warehouse destroyed!") -- Cancel all missions. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7a952cba8..06183c53d 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1823,6 +1823,8 @@ function AUFTRAG:AssignSquadrons(Squadrons) end self.squadrons=Squadrons + + return self end --- Add a required payload for this mission. Only these payloads will be used for this mission. If they are not available, the mission cannot start. Only available for use with an AIRWING. @@ -1835,12 +1837,14 @@ function AUFTRAG:AddRequiredPayload(Payload) table.insert(self.payloads, Payload) + return self end --- Add a Ops group to the mission. -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. +-- @return #AUFTRAG self function AUFTRAG:AddOpsGroup(OpsGroup) self:T(self.lid..string.format("Adding Ops group %s", OpsGroup.groupname)) @@ -1853,11 +1857,13 @@ function AUFTRAG:AddOpsGroup(OpsGroup) self.groupdata[OpsGroup.groupname]=groupdata + return self end --- Remove an Ops group from the mission. -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. +-- @return #AUFTRAG self function AUFTRAG:DelOpsGroup(OpsGroup) self:T(self.lid..string.format("Removing OPS group %s", OpsGroup and OpsGroup.groupname or "nil (ERROR)!")) @@ -1870,6 +1876,7 @@ function AUFTRAG:DelOpsGroup(OpsGroup) end + return self end --- Check if mission is PLANNED. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5534aa11e..7d487a335 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1020,6 +1020,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self:IsAlive() and self.group:IsAirborne(true) then local fuelmin=self:GetFuelMin() + + self:I(self.lid..string.format("Fuel state=%d", fuelmin)) if fuelmin>=self.fuellowthresh then self.fuellow=false @@ -2751,8 +2753,10 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFuelLow(From, Event, To) + local fuel=self:GetFuelMin() or 0 + -- Debug message. - local text=string.format("Low fuel for flight group %s", self.groupname) + local text=string.format("Low fuel %d for flight group %s", fuel, self.groupname) self:I(self.lid..text) -- Set switch to true. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index fc0022178..29ee92ccd 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -7663,6 +7663,12 @@ function OPSGROUP:GetTACAN() return self.tacan.Channel, self.tacan.Morse, self.tacan.Band, self.tacan.On, self.tacan.BeaconName end +--- Get current TACAN parameters. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Beacon TACAN beacon. +function OPSGROUP:GetBeaconTACAN() + return self.tacan +end --- Set default ICLS parameters. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index c0f53d960..266fe7f4c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -302,7 +302,7 @@ function SQUADRON:SetTakeoffType(TakeoffType) return self end ---- Set takeoff type cold (default). +--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold). -- @param #SQUADRON self -- @return #SQUADRON self function SQUADRON:SetTakeoffCold() @@ -310,7 +310,7 @@ function SQUADRON:SetTakeoffCold() return self end ---- Set takeoff type hot. +--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot). -- @param #SQUADRON self -- @return #SQUADRON self function SQUADRON:SetTakeoffHot() @@ -801,7 +801,8 @@ end -- @param #string To To state. function SQUADRON:onafterStop(From, Event, To) - self:I(self.lid.."STOPPING Squadron!") + -- Debug info. + self:I(self.lid.."STOPPING Squadron and removing all assets!") -- Remove all assets. for i=#self.assets,1,-1 do @@ -809,6 +810,7 @@ function SQUADRON:onafterStop(From, Event, To) self:DelAsset(asset) end + -- Clear call scheduler. self.CallScheduler:Clear() end From e14d655447d2d1a2b39028ac578c2f9abe2f6472 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 3 Jul 2021 09:30:04 +0200 Subject: [PATCH 334/382] Changed default fallback freq to 333.00Khz --- Moose Development/Moose/Ops/CSAR.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 15318de07..2422b8c14 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -240,7 +240,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.6r1" +CSAR.version="0.1.6r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -359,12 +359,14 @@ function CSAR:New(Coalition, Template, Alias) -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 - + -- generate Frequencies + self:_GenerateVHFrequencies() + -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua -- needs SRS => 1.9.6 to work (works on the *server* side) self.useSRS = false -- Use FF\'s SRS integration - self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) self.SRSchannel = 300 -- radio channel self.SRSModulation = radio.modulation.AM -- modulation @@ -605,7 +607,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla if not _freq then _freq = self:_GenerateADFFrequency() - if not _freq then _freq = 333250 end --noob catch + if not _freq then _freq = 333000 end --noob catch end local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) @@ -1886,7 +1888,6 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - self:_GenerateVHFrequencies() if self.useprefix then local prefixes = self.csarPrefix or {} self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() From a7e07af24f1e2bb0abf0b518009ad4cddb19d246 Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Sat, 3 Jul 2021 23:24:04 +1000 Subject: [PATCH 335/382] Correcting typo for debug message Small PR --- Moose Development/Moose/Wrapper/Marker.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..098e0d4ba 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -646,7 +646,7 @@ function MARKER:OnEventMarkRemoved(EventData) local MarkID=EventData.MarkID - self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID))) + self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID))) if MarkID==self.mid then From a14bca1059fa47770eeacb5bd66b276ef6b1626b Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Sat, 3 Jul 2021 23:30:45 +1000 Subject: [PATCH 336/382] Synchronize Text with Wrapper State Bug - text is not synchronized with the wrapper state, hence the `GetText()` will be incorrect. Method `TextChanged` does not exist, resulting `nil` reference errors when the players update markers. Current implementation of `MARKER:OnEventMarkChange(EventData)` is not implemented the same as its siblings of `OnEventMarkRemoved` and `OnEventMarkAdded`. The siblings would move the FSM accordingly -- aligned implementation --- Moose Development/Moose/Wrapper/Marker.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 88a868501..1027e030a 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -673,9 +673,9 @@ function MARKER:OnEventMarkChange(EventData) if MarkID==self.mid then - self:Changed(EventData) + self.text=tostring(EventData.MarkText) - self:TextChanged(tostring(EventData.MarkText)) + self:Changed(EventData) end From 4ba52212a9fc7480180258952a156b26e956d820 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jul 2021 18:04:08 +0200 Subject: [PATCH 337/382] ATIS - addede rainy presets 1-3 CTLD - avoid "preloading" when pilot leaves/crashes and rejoins CSAR - added option for autosmokedistance --- Moose Development/Moose/Ops/CTLD.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6ae0e4137..9f9885068 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.3r1" +CTLD.version="0.1.4r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -926,6 +926,8 @@ function CTLD:_EventHandler(EventData) local _unit = event.IniUnit local _group = event.IniGroup if _unit:IsHelicopter() or _group:IsHelicopter() then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end -- Herc support @@ -938,6 +940,7 @@ function CTLD:_EventHandler(EventData) -- remove from pilot table local unitname = event.IniUnitName or "none" self.CtldUnits[unitname] = nil + self.Loaded_Cargo[unitname] = nil end return self end @@ -2559,4 +2562,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- +------------------------------------------------------------------- \ No newline at end of file From 48aa841adddf1bdd2924a519263ad853f50f688e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 4 Jul 2021 18:04:27 +0200 Subject: [PATCH 338/382] ATIS - addede rainy presets 1-3 CTLD - avoid "preloading" when pilot leaves/crashes and rejoins CSAR - added option for autosmokedistance --- Moose Development/Moose/Ops/ATIS.lua | 24 ++++++++++++++++++++++++ Moose Development/Moose/Ops/CSAR.lua | 8 +++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e8b475195..f865f98be 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1617,6 +1617,30 @@ function ATIS:onafterBroadcast(From, Event, To) -- Scattered 4 clouddens=4 elseif cloudspreset:find("RainyPreset") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset1") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset2") then + -- Overcast + Rain + clouddens=9 + if temperature>5 then + precepitation=1 -- rain + else + precepitation=3 -- snow + end + elseif cloudspreset:find("RainyPreset3") then -- Overcast + Rain clouddens=9 if temperature>5 then diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 2422b8c14..7c3b47850 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -70,6 +70,7 @@ -- self.allowDownedPilotCAcontrol = false -- Set to false if you don\'t want to allow control by Combined Arms. -- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! -- self.autosmoke = false -- automatically smoke a downed pilot\'s location when a heli is near. +-- self.autosmokedistance = 1000 -- distance for autosmoke -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. -- self.csarOncrash = false -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. -- self.enableForAI = false -- set to false to disable AI pilots from being rescued. @@ -240,7 +241,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.6r2" +CSAR.version="0.1.7r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -356,6 +357,7 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + self.autosmokedistance = 1000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 @@ -987,7 +989,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end - else + elseif _distance >= 3000 and _distance < 5000 then self.heliVisibleMessage[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() @@ -1103,7 +1105,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _reset = true - if (self.autosmoke == true) and (_distance < 500) then + if (self.autosmoke == true) and (_distance < self.autosmokedistance) then self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) end From a3d8a48b92b80293c97d2374188893e6aece2575 Mon Sep 17 00:00:00 2001 From: Julien B Date: Mon, 5 Jul 2021 13:09:34 +0200 Subject: [PATCH 339/382] Add ATC Ground Mariana Islands support Added ATC_GROUND_MARIANAISLANDS Supported optional airbase ZONE_POLYGON boundaries (like initially intented) when there is provided, fallback remain a ZONE from the center of the airfield. Some airports are bigger than the default radius area and need custom boundaries to work properly on the edge of it. --- .../Moose/Functional/ATC_Ground.lua | 313 +++++++++++++++++- 1 file changed, 312 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index 455f3eaf5..e5d17ea14 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -59,7 +59,13 @@ function ATC_GROUND:New( Airbases, AirbaseList ) for AirbaseID, Airbase in pairs( self.Airbases ) do - Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + -- Specified ZoneBoundary is used if setted or Airbase radius by default + if Airbase.ZoneBoundary then + Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary ) + else + Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone() + end + Airbase.ZoneRunways = {} for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway ) @@ -3263,5 +3269,310 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) end + --- @type ATC_GROUND_MARIANAISLANDS +-- @extends #ATC_GROUND + +--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- --- +-- +-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG) +-- +-- --- +-- +-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed. +-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way. +-- +-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi. +-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned. +-- +-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving +-- faster than the maximum allowed speed, the pilot will be kicked. +-- +-- Different airbases have different maximum speeds, according safety regulations. +-- +-- # Airbases monitored +-- +-- The following airbases are monitored at the Mariana Island region. +-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored. +-- +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Andersen_AFB +-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.Olf_Orote +-- +-- # Installation +-- +-- ## In Single Player Missions +-- +-- ATC\_GROUND is fully functional in single player. +-- +-- ## In Multi Player Missions +-- +-- ATC\_GROUND is functional in multi player, however ... +-- +-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player. +-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed +-- by Ciribob. +-- +-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight. +-- ATC\_GROUND is communicating with this modified script to kick players! +-- +-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob. +-- +-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock) +-- +-- # Script it! +-- +-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor +-- +-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour. +-- +-- -- This creates a new ATC_GROUND_MARIANAISLANDS object. +-- +-- -- Monitor for these clients the airbases. +-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New() +-- +-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New( +-- { AIRBASE.MarianaIslands.Andersen_AFB, +-- AIRBASE.MarianaIslands.Saipan_Intl +-- } +-- ) +-- +-- +-- ## 2. Set various options +-- +-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes. +-- +-- ### 2.1 Speed limit at an airbase. +-- +-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour. +-- +-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately. +-- +-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second. +-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour. +-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour. +-- +---- @field #ATC_GROUND_MARIANAISLANDS +ATC_GROUND_MARIANAISLANDS = { + ClassName = "ATC_GROUND_MARIANAISLANDS", + Airbases = { + + [AIRBASE.MarianaIslands.Andersen_AFB] = { + ZoneBoundary = { + [1]={["y"]=16534.138036037,["x"]=11357.42159178,}, + [2]={["y"]=16193.406442738,["x"]=12080.012957533,}, + [3]={["y"]=13846.966851869,["x"]=12017.348398727,}, + [4]={["y"]=13085.815989171,["x"]=11686.317876875,}, + [5]={["y"]=13157.991797443,["x"]=11307.826209991,}, + [6]={["y"]=12055.725179065,["x"]=10795.955695916,}, + [7]={["y"]=12762.455491112,["x"]=8890.9830441032,}, + [8]={["y"]=15955.829493693,["x"]=10333.527220132,}, + [9]={["y"]=16537.500532414,["x"]=11302.009499603,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=12586.683049611,["x"]=10224.374497932,}, + [2]={["y"]=16191.720475696,["x"]=11791.299100017,}, + [3]={["y"]=16126.93956642,["x"]=11938.855615591,}, + [4]={["y"]=12520.758127164,["x"]=10385.177131701,}, + [5]={["y"]=12584.654720512,["x"]=10227.416991581,}, + }, + [2]={ + [1]={["y"]=12663.030391743,["x"]=9661.9623015306,}, + [2]={["y"]=16478.347303358,["x"]=11328.665745976,}, + [3]={["y"]=16405.4731048,["x"]=11479.11570429,}, + [4]={["y"]=12597.277684174,["x"]=9817.9733769647,}, + [5]={["y"]=12661.894752524,["x"]=9674.4462086962,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = { + ZoneBoundary = { + [1]={["y"]=2288.5182403943,["x"]=1469.0170841716,}, + [2]={["y"]=1126.2025877996,["x"]=1174.37135631,}, + [3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,}, + [4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,}, + [5]={["y"]=476.03853524366,["x"]=-1220.1783269883,}, + [6]={["y"]=2059.2220058047,["x"]=78.889693514402,}, + [7]={["y"]=1898.1396965104,["x"]=705.67531284795,}, + [8]={["y"]=2760.1768681934,["x"]=1026.0681119777,}, + [9]={["y"]=2317.2278959994,["x"]=1460.8143254273,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,}, + [2]={["y"]=1763.4754603305,["x"]=735.35988877983,}, + [3]={["y"]=1700.6941677961,["x"]=866.32615476157,}, + [4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,}, + [5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,}, + }, + [2]={ + [1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,}, + [2]={["y"]=1577.9055714735,["x"]=413.22750176368,}, + [3]={["y"]=1523.1182807849,["x"]=543.89726442232,}, + [4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,}, + [5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Rota_Intl] = { + ZoneBoundary = { + [1]={["y"]=47237.615412849,["x"]=76048.890408862,}, + [2]={["y"]=49938.030053628,["x"]=75921.721582932,}, + [3]={["y"]=49931.24873272,["x"]=75735.184004851,}, + [4]={["y"]=49295.999227075,["x"]=75754.716414519,}, + [5]={["y"]=49286.963307515,["x"]=75510.037806569,}, + [6]={["y"]=48774.280745707,["x"]=75513.331990155,}, + [7]={["y"]=48785.021396773,["x"]=75795.691662161,}, + [8]={["y"]=47232.749278491,["x"]=75839.239059146,}, + [9]={["y"]=47236.687866223,["x"]=76042.706764692,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=49741.295228062,["x"]=75901.50955922,}, + [2]={["y"]=49739.033213305,["x"]=75768.333440425,}, + [3]={["y"]=47448.460520408,["x"]=75857.400271466,}, + [4]={["y"]=47452.270177742,["x"]=75999.965448133,}, + [5]={["y"]=49738.502011054,["x"]=75905.338915708,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Saipan_Intl] = { + ZoneBoundary = { + [1]={["y"]=100489.08491445,["x"]=179799.05158855,}, + [2]={["y"]=100869.73415313,["x"]=179948.98719903,}, + [3]={["y"]=101364.78967515,["x"]=180831.98517043,}, + [4]={["y"]=101563.85713359,["x"]=180885.21496237,}, + [5]={["y"]=101733.92591034,["x"]=180457.73296886,}, + [6]={["y"]=103340.30228775,["x"]=180990.08362622,}, + [7]={["y"]=103459.55080438,["x"]=180453.77747027,}, + [8]={["y"]=100406.63048095,["x"]=179266.60983762,}, + [9]={["y"]=100225.55027532,["x"]=179423.9380961,}, + [10]={["y"]=100477.48558937,["x"]=179791.9827288,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=103170.38882002,["x"]=180654.56630524,}, + [2]={["y"]=103235.37868835,["x"]=180497.25368418,}, + [3]={["y"]=100564.72969504,["x"]=179435.41443498,}, + [4]={["y"]=100509.30718722,["x"]=179584.65394733,}, + [5]={["y"]=103163.53918905,["x"]=180651.82645285,}, + }, + [2]={ + [1]={["y"]=103048.83223261,["x"]=180819.94107128,}, + [2]={["y"]=103087.60579257,["x"]=180720.06315265,}, + [3]={["y"]=101037.52694966,["x"]=179899.50061624,}, + [4]={["y"]=100994.61708907,["x"]=180009.33151758,}, + [5]={["y"]=103043.26643227,["x"]=180820.40488798,}, + }, + }, + }, + [AIRBASE.MarianaIslands.Tinian_Intl] = { + ZoneBoundary = { + [1]={["y"]=88393.477575413,["x"]=166704.16076438,}, + [2]={["y"]=91581.732441809,["x"]=167402.54409276,}, + [3]={["y"]=91533.451647402,["x"]=166826.23670062,}, + [4]={["y"]=90827.604136952,["x"]=166699.75590414,}, + [5]={["y"]=90894.853975623,["x"]=166375.37836304,}, + [6]={["y"]=89995.027922869,["x"]=166224.92495935,}, + [7]={["y"]=88937.62899352,["x"]=166244.48573911,}, + [8]={["y"]=88408.916178231,["x"]=166480.39896864,}, + [9]={["y"]=88387.745481732,["x"]=166685.82715656,}, + }, + PointsRunways = { + [1]={ + [1]={["y"]=91329.480937912,["x"]=167204.44064529,}, + [2]={["y"]=91363.95475433,["x"]=167038.15603429,}, + [3]={["y"]=88585.849307337,["x"]=166520.3807647,}, + [4]={["y"]=88554.422227212,["x"]=166686.49505251,}, + [5]={["y"]=91318.8152578,["x"]=167203.31794212,}, + }, + }, + }, + + }, +} + +--- Creates a new ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator). +-- @return #ATC_GROUND_MARIANAISLANDS self +function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames ) + + -- Inherits from BASE + local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) ) + + self:SetKickSpeedKmph( 50 ) + self:SetMaximumKickSpeedKmph( 150 ) + +-- -- Andersen +-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Antonio_B_Won_Pat_International_Airport +-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Rota_International_Airport +-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Saipan_International_Airport +-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" ) +-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush() +-- +-- +-- -- Tinian_International_Airport +-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" ) +-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush() +-- +-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" ) +-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush() + + return self +end + + +--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object. +-- @param #ATC_GROUND_MARIANAISLANDS self +-- @param RepeatScanSeconds Time in second for defining occurency of alerts. +-- @return nothing +function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds ) + RepeatScanSeconds = RepeatScanSeconds or 0.05 + self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds ) +end From b7bc3cfbcf7d558580d70bdcadc7c2bcbc8ecdcf Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 5 Jul 2021 18:54:29 +0200 Subject: [PATCH 340/382] Spawn crates in a wider radius. Also build, when only one crate is required. --- Moose Development/Moose/Ops/CTLD.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9f9885068..1192de3a5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r1" +CTLD.version="0.1.4r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1073,11 +1073,14 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) -- loop crates needed for i=1,number do local cratealias = string.format("%s-%d", cratetemplate, math.random(1,100000)) - local cratedistance = i*4 + 6 + local cratedistance = i*4 + 8 if IsHerc then -- wider radius cratedistance = i*4 + 12 end + for i=1,50 do + math.random(90,270) + end local rheading = math.floor(math.random(90,270) * heading + 1 / 360) if not IsHerc then rheading = rheading + 180 -- mirror for Helis @@ -1574,11 +1577,11 @@ function CTLD:_BuildCrates(Group, Unit) foundbuilds = true else buildables[name].Found = buildables[name].Found + 1 - if buildables[name].Found >= buildables[name].Required then + foundbuilds = true + end + if buildables[name].Found >= buildables[name].Required then buildables[name].CanBuild = true canbuild = true - end - foundbuilds = true end --self:T{buildables = buildables}) end -- end dropped @@ -1638,7 +1641,7 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end local temptable = Build.Template or {} local zone = ZONE_GROUP:New(string.format("Unload zone-%s",unitname),Group,100) - local randomcoord = zone:GetRandomCoordinate(20,50):GetVec2() + local randomcoord = zone:GetRandomCoordinate(35):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) From 5d3ea57d4dae037dfd628aa7e1ec10d3f1ac9bca Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Jul 2021 20:24:35 +0200 Subject: [PATCH 341/382] CSAR give scripted spawned pilots a name. CTLD corrected that generic troops could be spawned only once. --- Moose Development/Moose/Ops/CSAR.lua | 4 ++-- Moose Development/Moose/Ops/CTLD.lua | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7c3b47850..8fd660487 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -663,7 +663,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ return end - local _description = _description or "none" + local _description = _description or "Unknown" local pos = {} if _randomPoint then @@ -682,7 +682,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 1192de3a5..ae11f996c 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -55,10 +55,10 @@ CTLD_CARGO = { -- @type CTLD_CARGO.Enum -- @field #string Type Type of Cargo. CTLD_CARGO.Enum = { - VEHICLE = "Vehicle", -- #string vehicles - TROOPS = "Troops", -- #string troops - FOB = "FOB", -- #string FOB - CRATE = "CRATE", -- #string crate + ["VEHICLE"] = "Vehicle", -- #string vehicles + ["TROOPS"] = "Troops", -- #string troops + ["FOB"] = "FOB", -- #string FOB + ["CRATE"] = "Crate", -- #string crate } --- Function to create new CTLD_CARGO object. @@ -513,7 +513,7 @@ CTLD.SkipFrequencies = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.3r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1010,8 +1010,11 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) loaded.Troopsloaded = loaded.Troopsloaded + troopsize - table.insert(loaded.Cargo,Cargotype) + table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) @@ -1583,7 +1586,7 @@ function CTLD:_BuildCrates(Group, Unit) buildables[name].CanBuild = true canbuild = true end - --self:T{buildables = buildables}) + self:T({buildables = buildables}) end -- end dropped end -- end crate loop -- ok let\'s list what we have @@ -2565,4 +2568,4 @@ end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua -------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------- From 65bd7909e105a9ff4011b86f6aa870dadbd5b5a9 Mon Sep 17 00:00:00 2001 From: cammel tech <47948096+cammeltech@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:26:20 +0200 Subject: [PATCH 342/382] Example for CTLD > SCORING added (#1566) * Example added Example for the connection to the SCORING Class. * kleiner Fehler eingeschlichen --- Moose Development/Moose/Ops/CTLD.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index ae11f996c..8c5f7f076 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -351,6 +351,24 @@ do -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... -- end + -- +-- ## 3.6 A simple SCORING example: +-- +-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) +-- +-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) +-- local points = 10 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end +-- +-- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) +-- local points = 5 +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- end -- -- ## 4. F10 Menu structure -- From 464fde0ed269ecf42fb7220fa681cb0f46f74a3c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:13:50 +0200 Subject: [PATCH 343/382] OPS - MSRS - Callsign --- Moose Development/Moose/Ops/FlightGroup.lua | 3 - Moose Development/Moose/Ops/OpsGroup.lua | 101 +++++++++++++++++--- 2 files changed, 87 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 7d487a335..05fc533dd 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -53,8 +53,6 @@ -- @field #number Tparking Abs. mission time stamp when the group was spawned uncontrolled and is parking. -- @field #table menu F10 radio menu. -- @field #string controlstatus Flight control status. --- @field #number callsignName Callsign name. --- @field #number callsignNumber Callsign number. -- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. -- @field #number RTBRecallCount Number that counts RTB calls. -- @@ -2949,7 +2947,6 @@ function FLIGHTGROUP:_InitGroup() end self.callsign.NumberSquad=callsign[1] self.callsign.NumberGroup=callsign[2] - self.callsign.NumberElement=callsign[3] -- First element only self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 29ee92ccd..97fd17248 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -100,6 +100,8 @@ -- -- @field #OPSGROUP.Callsign callsign Current callsign settings. -- @field #OPSGROUP.Callsign callsignDefault Default callsign settings. +-- @field #string callsignName Callsign name. +-- @field #string callsignAlias Callsign alias. -- -- @field #OPSGROUP.Spot spot Laser and IR spot. -- @@ -118,6 +120,9 @@ -- @field #number cargocounter Running number of cargo UIDs. -- @field #OPSGROUP.CarrierLoader carrierLoader Carrier loader parameters. -- @field #OPSGROUP.CarrierLoader carrierUnloader Carrier unloader parameters. +-- +-- @field #boolean useSRS Use SRS for transmissions. +-- @field Sound.SRS#MSRS msrs MOOSE SRS wrapper. -- -- @extends Core.Fsm#FSM @@ -309,9 +314,7 @@ OPSGROUP.TaskType={ -- @type OPSGROUP.Callsign -- @field #number NumberSquad Squadron number corresponding to a name like "Uzi". -- @field #number NumberGroup Group number. First number after name, e.g. "Uzi-**1**-1". --- @field #number NumberElement Element number.Second number after name, e.g. "Uzi-1-**1**" -- @field #string NameSquad Name of the squad, e.g. "Uzi". --- @field #string NameElement Name of group element, e.g. Uzi 11. --- Option data. -- @type OPSGROUP.Option @@ -1473,6 +1476,53 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) return self end +--- Use SRS Simple-Text-To-Speech for transmissions. +-- @param #OPSGROUP self +-- @param #string PathToSRS Path to SRS directory. +-- @param #string Gender Gender: "male" or "female" (default). +-- @param #string Culture Culture, e.g. "en-GB" (default). +-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`. +-- @param #number Port SRS port. Default 5002. +-- @return #OPSGROUP self +function OPSGROUP:SetSRS(PathToSRS, Gender, Culture, Voice, Port) + self.useSRS=true + self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation) + self.msrs:SetGender(Gender) + self.msrs:SetCulture(Culture) + self.msrs:SetVoice(Voice) + self.msrs:SetPort(Port) + self.msrs:SetCoalition(self:GetCoalition()) + return self +end + +--- Send a radio transmission via SRS Text-To-Speech. +-- @param #OPSGROUP self +-- @param #string Text Text of transmission. +-- @param #number Delay Delay in seconds before the transmission is started. +-- @return #OPSGROUP self +function OPSGROUP:RadioTransmission(Text, Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.RadioTransmission, self, Text, 0) + else + + if self.useSRS and self.msrs then + + local freq, modu, radioon=self:GetRadio() + + self.msrs:SetFrequencies(freq) + self.msrs:SetModulations(modu) + + self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) + + self.msrs:PlayText(Text) + end + + end + + return self +end + --- Set that this carrier is an all aspect loader. -- @param #OPSGROUP self -- @param #number Length Length of loading zone in meters. Default 50 m. @@ -3326,6 +3376,9 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) -- Set group mission status to STARTED. Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.STARTED) + + -- + -- Set mission status to STARTED. Mission:__Started(3) @@ -7941,13 +7994,14 @@ end --- Set default callsign. -- @param #OPSGROUP self -- @param #number CallsignName Callsign name. --- @param #number CallsignNumber Callsign number. +-- @param #number CallsignNumber Callsign number. Default 1. -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - self.callsignDefault={} + self.callsignDefault={} --#OPSGROUP.Callsign self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 + self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) return self end @@ -7963,6 +8017,7 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) -- Set default callsign. We switch to this when group is spawned. self:SetDefaultCallsign(CallsignName, CallsignNumber) + --self.callsign=UTILS.DeepCopy(self.callsignDefault) elseif self:IsAlive() then @@ -7978,9 +8033,21 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) -- Give command to change the callsign. self.group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + + -- Callsign of the group, e.g. Colt-1 + self.callsignName=UTILS.GetCallsignName(self.callsign.NumberSquad).."-"..self.callsign.NumberGroup + self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) + + -- Set callsign of elements. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element.status~=OPSGROUP.ElementStatus.DEAD then + element.callsign=element.unit:GetCallsign() + end + end else - --TODO: Error + self:E(self.lid.."ERROR: Group is not alive and not in utero! Cannot switch callsign") end return self @@ -8815,10 +8882,10 @@ function OPSGROUP:_AddElementByName(unitname) if unit then - -- TODO: this is wrong when grouping is used! + -- Get unit template. local unittemplate=unit:GetTemplate() - + -- Element table. local element={} --#OPSGROUP.Element -- Name and status. @@ -8828,7 +8895,10 @@ function OPSGROUP:_AddElementByName(unitname) -- Unit and group. element.unit=unit element.DCSunit=Unit.getByName(unitname) + element.gid=element.DCSunit:getNumber() + element.uid=element.DCSunit:getID() element.group=unit:GetGroup() + element.opsgroup=self -- Skill etc. element.skill=unittemplate.skill or "Unknown" @@ -8841,11 +8911,10 @@ function OPSGROUP:_AddElementByName(unitname) -- Descriptors and type/category. element.descriptors=unit:GetDesc() - --self:I({desc=element.descriptors}) - element.category=unit:GetUnitCategory() element.categoryname=unit:GetCategoryName() element.typename=unit:GetTypeName() + --self:I({desc=element.descriptors}) -- Ammo. element.ammo0=self:GetAmmoUnit(unit, false) @@ -8880,6 +8949,7 @@ function OPSGROUP:_AddElementByName(unitname) unit:SetCargoBayWeightLimit() element.weightMaxCargo=unit.__.CargoBayWeightLimit + -- Cargo bay (empty). element.cargoBay={} -- FLIGHTGROUP specific. @@ -8891,6 +8961,14 @@ function OPSGROUP:_AddElementByName(unitname) element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 element.fuelmass=element.fuelmass0 element.fuelrel=element.unit:GetFuel() + else + element.callsign="Peter-1-1" + element.modex="000" + element.payload={} + element.pylons={} + element.fuelmass0=99999 + element.fuelmass =99999 + element.fuelrel=1 end -- Debug text. @@ -8899,11 +8977,6 @@ function OPSGROUP:_AddElementByName(unitname) element.size, element.length, element.height, element.width, element.weight, element.weightMaxTotal, element.weightCargo, element.weightMaxCargo) self:T(self.lid..text) - -- Debug text. - --local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", - --element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) - --self:T(self.lid..text) - -- Add element to table. table.insert(self.elements, element) From 89308f7d06804f84492366467052e4ebe3822b81 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:49:51 +0200 Subject: [PATCH 344/382] Update Airbase.lua - Fixed Andersen --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index e63218ac5..223d459b6 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1420,7 +1420,7 @@ function AIRBASE:GetRunwayData(magvar, mark) name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or name==AIRBASE.PersianGulf.Kish_International_Airport or - name==AIRBASE.MarianaIslands.Andersen then + name==AIRBASE.MarianaIslands.Andersen_AFB then -- 1-->4, 2-->3, 3-->2, 4-->1 exception=1 From 97668e5413aaa6323938fa85ff823ee7179b1a86 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 6 Jul 2021 21:56:15 +0200 Subject: [PATCH 345/382] Update FlightGroup.lua - Fixed flight of airwing is going to tanker even if fuellowrefuel switch is false --- Moose Development/Moose/Ops/FlightGroup.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a71619474..a9e42d16e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2757,8 +2757,9 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) -- Get closest tanker from airwing that can refuel this flight. local tanker=self.airwing:GetTankerForFlight(self) - if tanker then + if tanker and self.fuellowrefuel then + -- Debug message. self:I(self.lid..string.format("Send to refuel at tanker %s", tanker.flightgroup:GetName())) -- Get a coordinate towards the tanker. From be2d1d78953c56e475cf2ec32d5cc85c98aeafce Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Jul 2021 11:33:51 +0200 Subject: [PATCH 346/382] Update OpsTransport.lua --- Moose Development/Moose/Ops/OpsTransport.lua | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index c8d69a5ca..816813ac7 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -802,10 +802,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was loaded into a carrier. --- @param Ops.OpsGroup#OPSGROUP.Element Carrier Carrier element. -function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroup, Carrier) - self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroup:GetName(), tostring(Carrier.name))) +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier OPSGROUP that was loaded into a carrier. +-- @param Ops.OpsGroup#OPSGROUP.Element CarrierElement Carrier element. +function OPSTRANSPORT:onafterLoaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier, CarrierElement) + self:I(self.lid..string.format("Loaded OPSGROUP %s into carrier %s", OpsGroupCargo:GetName(), tostring(CarrierElement.name))) end --- On after "Unloaded" event. @@ -813,9 +814,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroup OPSGROUP that was unloaded from a carrier. -function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroup) - self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroup:GetName())) +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was unloaded from a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. +function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) end --- On after "DeadCarrierGroup" event. From fc1adf3b94ee8fc5cc455228f119f576dbe4dfd7 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 8 Jul 2021 12:21:10 +0200 Subject: [PATCH 347/382] OPS Transport --- Moose Development/Moose/Core/Spawn.lua | 21 ++--- Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 71 ++++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 88 ++++++++++++++++++-- 4 files changed, 144 insertions(+), 38 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index d25186b66..272dc386d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2423,8 +2423,7 @@ end -- @param #SPAWN self -- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) @@ -2493,8 +2492,7 @@ end -- @param #SPAWN self -- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) @@ -2510,8 +2508,7 @@ end -- @param #SPAWN self -- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground. @@ -2535,8 +2532,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2() @@ -2569,8 +2565,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2() @@ -2626,8 +2621,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil Nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnStatic = STATIC:FindByName( StaticName ) @@ -2658,8 +2652,7 @@ end -- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone. -- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone. -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. --- @return Wrapper.Group#GROUP that was spawned. --- @return #nil when nothing was spawned. +-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. -- @usage -- -- local SpawnZone = ZONE:New( ZoneName ) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index d78b0a6a5..0310292ac 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1287,7 +1287,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local los=LoS(x) -- Debug message. - self:I(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 97fd17248..65060db9e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -4675,7 +4675,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:ScheduleOnce(Delay, OPSGROUP._Respawn, self, 0, Template, Reset) else - self:I(self.lid.."FF _Respawn") + -- Debug message. + self:T2(self.lid.."FF _Respawn") -- Given template or get old. Template=Template or UTILS.DeepCopy(self.template) @@ -5612,7 +5613,7 @@ end -- @param #OPSGROUP self -- @return #OPSGROUP self function OPSGROUP:_RemoveMyCarrier() - self:I(self.lid..string.format("Removing my carrier!")) + self:T(self.lid..string.format("Removing my carrier!")) self.mycarrier.group=nil self.mycarrier.element=nil self.mycarrier.reserved=nil @@ -5723,16 +5724,50 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Navy Group - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathPickup() + + if path then + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + + -- NAVYGROUP + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + + -- Give cruise command. self:__Cruise(-2) + elseif self.isArmygroup then -- Army Group - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + -- Get a (random) pre-defined transport path. + local path=self.cargoTransport:_GetPathPickup() + + if path then + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + end + end + + -- ARMYGROUP + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + self:__Cruise(-2) end @@ -5883,7 +5918,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Trigger "Loaded" event for current cargo transport. if self.cargoTransport then - self.cargoTransport:Loaded(CargoGroup, carrier) + self.cargoTransport:Loaded(CargoGroup, self, carrier) else self:E(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) end @@ -6034,15 +6069,18 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self.isArmygroup then + local cwp=self:GetWaypointCurrent() + local uid=cwp and cwp.uid or nil + local path=self.cargoTransport:_GetPathTransport() + if path then - - for _,_zone in pairs(path.zones:GetSet()) do - local zone=_zone --Core.Zone#ZONE - local coordinate=zone:GetRandomCoordinate(nil, nil, nil) --TODO: surface type land - local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.temp=true + -- Loop over coordinates. + for i,coordinate in pairs(path) do + local waypoint=ARMYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true + uid=waypoint.uid + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end - end -- ARMYGROUP @@ -6060,12 +6098,11 @@ function OPSGROUP:onafterTransport(From, Event, To) local path=self.cargoTransport:_GetPathTransport() if path then - -- Loop over zones + -- Loop over coordinates. for i,coordinate in pairs(path) do - local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) - waypoint.temp=true + local waypoint=NAVYGROUP.AddWaypoint(self, coordinate, nil, uid) ; waypoint.temp=true uid=waypoint.uid - coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) + --coordinate:MarkToAll(string.format("Path i=%d, UID=%d", i, uid)) end end @@ -6184,7 +6221,7 @@ function OPSGROUP:onafterUnloading(From, Event, To) end -- Trigger "Unloaded" event for current cargo transport - self.cargoTransport:Unloaded(cargo.opsgroup) + self.cargoTransport:Unloaded(cargo.opsgroup, self) end @@ -7236,7 +7273,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Debug message. local text=string.format("Group passing waypoint uid=%d", uid) - opsgroup:I(opsgroup.lid..text) + opsgroup:T(opsgroup.lid..text) -- Trigger PassingWaypoint event. if waypoint.temp then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 816813ac7..0ef309150 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -50,7 +50,8 @@ -- @field #number Ncargo Total number of cargo groups. -- @field #number Ncarrier Total number of assigned carriers. -- @field #number Ndelivered Total number of cargo groups delivered. --- @field #table pathsTransport Paths of `#OPSGROUP.Path`. +-- @field #table pathsTransport Transport paths of `#OPSGROUP.Path`. +-- @field #table pathsPickup Pickup paths of `#OPSGROUP.Path`. -- @extends Core.Fsm#FSM --- *Victory is the beautiful, bright-colored flower. Transport is the stem without which it could never have blossomed.* -- Winston Churchill @@ -78,6 +79,7 @@ OPSTRANSPORT = { carrierTransportStatus = {}, conditionStart = {}, pathsTransport = {}, + pathsPickup = {}, requiredCargos = {}, } @@ -203,7 +205,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) -- We got a single GROUP or OPSGROUP object. local cargo=self:_CreateCargoGroupData(GroupSet) - if cargo then --and self:CanCargo(cargo.opsgroup) + if cargo then table.insert(self.cargos, cargo) self.Ncargo=self.Ncargo+1 end @@ -530,10 +532,11 @@ end --- Add path used for transportation from the pickup to the deploy zone. If multiple paths are defined, a random one is chosen. -- @param #OPSTRANSPORT self -- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #boolean Reversed If `true`, add waypoints of group in reversed order. -- @param #number Radius Randomization radius in meters. Default 0 m. -- @param #number Altitude Altitude in feet AGL. Only for aircraft. -- @return #OPSTRANSPORT self -function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) +function OPSTRANSPORT:AddPathTransport(PathGroup, Reversed, Radius, Altitude) local path={} --#OPSTRANSPORT.Path path.coords={} @@ -543,11 +546,21 @@ function OPSTRANSPORT:AddPathTransport(PathGroup, Radius, Altitude) -- Get route points. local waypoints=PathGroup:GetTaskRoute() - for _,wp in pairs(waypoints) do - local coord=COORDINATE:New(wp.x, wp.alt, wp.y) - table.insert(path.coords, coord) + if Reversed then + for i=#waypoints,1,-1 do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + else + for i=1,#waypoints do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end end + -- Add path. table.insert(self.pathsTransport, path) @@ -580,6 +593,69 @@ function OPSTRANSPORT:_GetPathTransport() end +--- Add path used to go to the pickup zone. If multiple paths are defined, a random one is chosen. +-- @param #OPSTRANSPORT self +-- @param Wrapper.Group#GROUP PathGroup A (late activated) GROUP defining a transport path by their waypoints. +-- @param #boolean Reversed If `true`, add waypoints of group in reversed order. +-- @param #number Radius Randomization radius in meters. Default 0 m. +-- @param #number Altitude Altitude in feet AGL. Only for aircraft. +-- @return #OPSTRANSPORT self +function OPSTRANSPORT:AddPathPickup(PathGroup, Reversed, Radius, Altitude) + + local path={} --#OPSTRANSPORT.Path + path.coords={} + path.radius=Radius or 0 + path.altitude=Altitude + + -- Get route points. + local waypoints=PathGroup:GetTaskRoute() + + if Reversed then + for i=#waypoints,1,-1 do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + else + for i=1,#waypoints do + local wp=waypoints[i] + local coord=COORDINATE:New(wp.x, wp.alt, wp.y) + table.insert(path.coords, coord) + end + end + + -- Add path. + table.insert(self.pathsPickup, path) + + return self +end + +--- Get a path for pickup. +-- @param #OPSTRANSPORT self +-- @return #table The path of COORDINATEs. +function OPSTRANSPORT:_GetPathPickup() + + if self.pathsPickup and #self.pathsPickup>0 then + + -- Get a random path for transport. + local path=self.pathsPickup[math.random(#self.pathsPickup)] --#OPSTRANSPORT.Path + + + local coordinates={} + for _,coord in ipairs(path.coords) do + + -- TODO: Add randomization. + + table.insert(coordinates, coord) + end + + return coordinates + end + + return nil +end + + --- Add a carrier assigned for this transport. -- @param #OPSTRANSPORT self -- @param Ops.OpsGroup#OPSGROUP CarrierGroup Carrier OPSGROUP. From 6dfd757ea1979b2090378937153b4c8fb6dec970 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 8 Jul 2021 22:59:29 +0200 Subject: [PATCH 348/382] OPS --- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2ca5c370a..205141623 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2396,7 +2396,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set holding flag to 0=false. self.flaghold:Set(0) - local holdtime=5*60 + local holdtime=1*60 if fc or self.airboss then holdtime=nil end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 65060db9e..901bdebc0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1713,24 +1713,28 @@ end -- @param #OPSGROUP self -- @return #boolean If true, group is not spawned yet. function OPSGROUP:IsInUtero() - return self:Is("InUtero") + local is=self:Is("InUtero") + return is end --- Check if group is in state spawned. -- @param #OPSGROUP self -- @return #boolean If true, group is spawned. function OPSGROUP:IsSpawned() - return self:Is("Spawned") + local is=self:Is("Spawned") + return is end --- Check if group is dead. -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() + --env.info("FF IsDead") if self.isDead then return true else - return self:Is("Dead") + local is=self:Is("Dead") + return is end end @@ -1745,7 +1749,8 @@ end -- @param #OPSGROUP self -- @return #boolean If true, FSM state is stopped. function OPSGROUP:IsStopped() - return self:Is("Stopped") + local is=self:Is("Stopped") + return is end --- Check if this group is currently "uncontrolled" and needs to be "started" to begin its route. @@ -1781,14 +1786,16 @@ end -- @param #OPSGROUP self -- @return #boolean If true, group is retreating. function OPSGROUP:IsRetreating() - return self:is("Retreating") + local is=self:is("Retreating") + return is end --- Check if the group is engaging another unit or group. -- @param #OPSGROUP self -- @return #boolean If true, group is engaging. function OPSGROUP:IsEngaging() - return self:is("Engaging") + local is=self:is("Engaging") + return is end --- Check if the group is not a carrier yet. @@ -2358,7 +2365,7 @@ function OPSGROUP:OnEventBirth(EventData) self.destbase=self.homebase end - self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.homebase and self.homebase:GetName() or "unknown")) + self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end From 4a1df3d5cc4da70c57a4e15da0b97ed234a946d4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 16:56:03 +0200 Subject: [PATCH 349/382] -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- self.pilotmustopendoors = false -- switch to true to enable check of open doors --- Moose Development/Moose/Ops/CSAR.lua | 93 +++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 8fd660487..0a3d14c64 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -87,17 +87,21 @@ -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! -- self.verbose = 0 -- set to > 1 for stats output for debugging. --- -- (added 0.1.4) limit amount of downed pilots spawned by ejection events --- self.limitmaxdownedpilots = true, --- self.maxdownedpilots = 10, +-- -- (added 0.1.4) limit amount of downed pilots spawned by **ejection** events +-- self.limitmaxdownedpilots = true +-- self.maxdownedpilots = 10 +-- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors +-- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters +-- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters +-- self.pilotmustopendoors = false -- switch to true to enable check of open doors -- -- ## 2.1 Experimental Features -- --- "WARNING - Here\'ll be dragons! +-- WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua --- Needs SRS => 1.9.6 to work (works on the *server* side of SRS)" +-- Needs SRS => 1.9.6 to work (works on the **server** side of SRS) -- self.useSRS = false -- Set true to use FF\'s SRS integration --- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- @@ -241,7 +245,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.7r2" +CSAR.version="0.1.8r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -363,6 +367,10 @@ function CSAR:New(Coalition, Template, Alias) self.maxdownedpilots = 25 -- generate Frequencies self:_GenerateVHFrequencies() + -- added 0.1.8 + self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters + self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters + self.pilotmustopendoors = false -- switch to true to enable check on open doors -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -983,13 +991,13 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) - if _distance < 3000 and _distance > 0 then + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-5,heliname,woundedgroupname) end - elseif _distance >= 3000 and _distance < 5000 then + elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then self.heliVisibleMessage[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() @@ -1085,6 +1093,48 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) return self end + +--- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze. +-- @param #CSAR self +-- @param #string unit_name Name of unit. +-- @return #boolean outcome The outcome. +function CSAR:_IsLoadingDoorOpen( unit_name ) + self:T(self.lid .. " _IsLoadingDoorOpen") + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + self:I(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end + + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + self:I(unit_name .. " a side door is open") + ret_val = true + end + + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + self:I(unit_name .. " a side door is open ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + self:I(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + self:I(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return false +end + --- (Internal) Function to check if heli is close to group. -- @param #CSAR self -- @param #number _distance @@ -1148,15 +1198,25 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self.landedStatus[_lookupKeyHeli] = _time end if _time <= 0 or _distance < self.loadDistance then - self.landedStatus[_lookupKeyHeli] = nil - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + return true + else + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end else if (_distance < self.loadDistance) then - self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) - return false + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + return true + else + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end end end else @@ -1191,9 +1251,14 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if _time > 0 then self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + return true + else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) return false + end end _reset = false else From a69865b8c95a8f342260f67adc26990cfa51cc38 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 10 Jul 2021 16:56:31 +0200 Subject: [PATCH 350/382] -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors -- self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters -- self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters -- self.pilotmustopendoors = false -- switch to true to enable check of open doors --- Moose Development/Moose/Ops/CTLD.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8c5f7f076..4c9adf68d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -456,6 +456,8 @@ CTLD = { -- DONE: Zone Radio Beacons -- DONE: Stats Running -- DONE: Added support for Hercules +-- TODO: Possibly - either/or loading crates and troops +-- TODO: Limit of troops, crates buildable? ------------------------------ --- Radio Beacons From 268eb1d60dbcbc891d8b4e0b68099b1db53cadce Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 11 Jul 2021 18:15:25 +0200 Subject: [PATCH 351/382] OPS --- Moose Development/Moose/Core/Database.lua | 6 +- Moose Development/Moose/Core/Point.lua | 6 +- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Ops/Airboss.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 15 +- Moose Development/Moose/Ops/FlightGroup.lua | 210 +++++++++++++++---- Moose Development/Moose/Ops/NavyGroup.lua | 9 +- Moose Development/Moose/Ops/OpsGroup.lua | 188 ++++++++++------- Moose Development/Moose/Ops/OpsTransport.lua | 77 +++++-- 9 files changed, 358 insertions(+), 157 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 6b19d7ae3..df836f7dd 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1478,7 +1478,7 @@ end -- @param #DATABASE self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. function DATABASE:AddOpsGroup(opsgroup) - env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) + --env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end @@ -1494,7 +1494,7 @@ function DATABASE:GetOpsGroup(groupname) groupname=groupname:GetName() end - env.info("Getting OPSGROUP "..tostring(groupname)) + --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end @@ -1510,7 +1510,7 @@ function DATABASE:FindOpsGroup(groupname) groupname=groupname:GetName() end - env.info("Getting OPSGROUP "..tostring(groupname)) + --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b78ade334..1c28e0730 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -647,7 +647,8 @@ do -- COORDINATE local y=X*math.sin(phi)+Y*math.cos(phi) -- Coordinate assignment looks bit strange but is correct. - return COORDINATE:NewFromVec3({x=y, y=self.y, z=x}) + local coord=COORDINATE:NewFromVec3({x=y, y=self.y, z=x}) + return coord end --- Return a random Vec2 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE. @@ -690,7 +691,8 @@ do -- COORDINATE function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius ) self:F2( { OuterRadius, InnerRadius } ) - return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + local coord=COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + return coord end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7dfdfca49..c978d8f85 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6112,7 +6112,7 @@ do -- SET_OPSGROUP -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_OPSGROUP:Add(ObjectName, Object) - self:I( { ObjectName = ObjectName, Object = Object } ) + self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 71a6087fe..3133cd715 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6134,7 +6134,7 @@ function AIRBOSS:_ScanCarrierZone() local flight=_DATABASE:GetOpsGroup(groupname) if flight and flight:IsInbound() and flight.destbase:GetName()==self.carrier:GetName() then - if flight.ishelo then + if flight.isHelo then else putintomarshal=true end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f2953ede0..d8b506e64 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -38,7 +38,7 @@ -- @field Core.Set#SET_ZONE retreatZones Set of retreat zones. -- @extends Ops.OpsGroup#OPSGROUP ---- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge -- -- === -- @@ -55,17 +55,6 @@ ARMYGROUP = { engage = {}, } ---- Army group element. --- @type ARMYGROUP.Element --- @field #string name Name of the element, i.e. the unit. --- @field Wrapper.Unit#UNIT unit The UNIT object. --- @field #string status The element status. --- @field #string typename Type name. --- @field #number length Length of element in meters. --- @field #number width Width of element in meters. --- @field #number height Height of element in meters. --- @extends Ops.OpsGroup#OPSGROUP.Element - --- Target -- @type ARMYGROUP.Target -- @field Ops.Target#TARGET Target The target. @@ -73,7 +62,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.4.0" +ARMYGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 205141623..fa3aec358 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -173,17 +173,11 @@ FLIGHTGROUP.Attribute = { --- Flight group element. -- @type FLIGHTGROUP.Element --- @field #string modex Tail number. --- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. --- @field #table pylons Table of pylons. --- @field #number fuelmass Mass of fuel in kg. --- @field #string callsign Call sign, e.g. "Uzi 1-1". --- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. -- @extends Ops.OpsGroup#OPSGROUP.Element --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.6.1" +FLIGHTGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -278,6 +272,7 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "Taxiing", "Taxiing") -- The whole flight group is taxiing. self:AddTransition("*", "Takeoff", "Airborne") -- The whole flight group is airborne. self:AddTransition("*", "Airborne", "Airborne") -- The whole flight group is airborne. + self:AddTransition("*", "Cruise", "Cruising") -- The whole flight group is cruising. self:AddTransition("*", "Landing", "Landing") -- The whole flight group is landing. self:AddTransition("*", "Landed", "Landed") -- The whole flight group has landed. self:AddTransition("*", "Arrived", "Arrived") -- The whole flight group has arrived. @@ -600,11 +595,18 @@ function FLIGHTGROUP:IsTaxiing() return self:Is("Taxiing") end ---- Check if flight is airborne. +--- Check if flight is airborne or cruising. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is airborne. function FLIGHTGROUP:IsAirborne() - return self:Is("Airborne") + return self:Is("Airborne") or self:Is("Cruising") +end + +--- Check if flight is airborne or cruising. +-- @param #FLIGHTGROUP self +-- @return #boolean If true, flight is airborne. +function FLIGHTGROUP:IsCruising() + return self:Is("Cruising") end --- Check if flight is waiting after passing final waypoint. @@ -732,7 +734,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) self:Activate() _delay=1 end - self:I(self.lid.."Starting uncontrolled group") + self:T(self.lid.."Starting uncontrolled group") self.group:StartUncontrolled(_delay) self.isUncontrolled=false else @@ -804,7 +806,7 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) -- First we check if elements are still alive. Could be that they were despawned without notice, e.g. when landing on a too small airbase. for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Check that element is not already dead or not yet alive. if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then @@ -881,7 +883,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Check if flight began to taxi (if it was parking). if self:IsParking() then for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element if element.parking then -- Get distance to assigned parking spot. @@ -927,7 +929,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element local name=element.name local status=element.status @@ -974,7 +976,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local TmaxFuel=math.huge for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Get relative fuel of element. local fuel=element.unit:GetFuel() or 0 @@ -1019,7 +1021,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local fuelmin=self:GetFuelMin() - self:I(self.lid..string.format("Fuel state=%d", fuelmin)) + -- Debug info. + self:T2(self.lid..string.format("Fuel state=%d", fuelmin)) if fuelmin>=self.fuellowthresh then self.fuellow=false @@ -1365,8 +1368,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) + + -- Debug info. self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. @@ -1391,6 +1396,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) self:__ElementParking(0.11, Element) end end + end --- On after "ElementParking" event. @@ -1398,9 +1404,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) + + -- Debug info. self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A")) -- Set element status. @@ -1417,6 +1425,7 @@ function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) elseif self:IsTakeoffRunway() then self:__ElementEngineOn(0.5, Element) end + end --- On after "ElementEngineOn" event. @@ -1424,7 +1433,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) -- Debug info. @@ -1432,6 +1441,7 @@ function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ENGINEON) + end --- On after "ElementTaxiing" event. @@ -1439,7 +1449,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Get terminal ID. @@ -1453,6 +1463,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAXIING) + end --- On after "ElementTakeoff" event. @@ -1460,7 +1471,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:T(self.lid..string.format("Element takeoff %s at %s airbase.", Element.name, airbase and airbase:GetName() or "unknown")) @@ -1475,6 +1486,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) -- Trigger element airborne event. self:__ElementAirborne(0.1, Element) + end --- On after "ElementAirborne" event. @@ -1482,12 +1494,15 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element) + + -- Debug info. self:T2(self.lid..string.format("Element airborne %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE) + end --- On after "ElementLanded" event. @@ -1495,9 +1510,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) + + -- Debug info. self:T2(self.lid..string.format("Element landed %s at %s airbase", Element.name, airbase and airbase:GetName() or "unknown")) if self.despawnAfterLanding then @@ -1523,6 +1540,7 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) end end + end --- On after "ElementArrived" event. @@ -1530,7 +1548,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. -- @param Wrapper.Airbase#AIRBASE airbase The airbase, where the element arrived. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Parking The Parking spot the element has. function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) @@ -1548,7 +1566,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDestroyed(From, Event, To, Element) -- Call OPSGROUP function. @@ -1561,7 +1579,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) -- Call OPSGROUP function. @@ -1577,7 +1595,7 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) end ---- On after "Spawned" event. Sets the template, initializes the waypoints. +--- On after "Spawned" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -1701,6 +1719,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To) else -- Human flights go to TAXI OUT queue. They will go to the ready for takeoff queue when they request it. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIOUT) + -- Update menu. self:_UpdateMenu() end @@ -1736,8 +1755,30 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) -- No current airbase any more. self.currbase=nil + + -- Cruising. + self:__Cruise(-0.05) + +end + +--- On after "Cruising" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterCruise(From, Event, To) + self:T(self.lid..string.format("Flight cruising")) + + -- Not waiting anymore. + self.Twaiting=nil + self.dTwait=nil if self.isAI then + + --- + -- AI + --- + if self:IsTransporting() then if self.cargoTransport and self.cargoTransport.deployzone and self.cargoTransport.deployzone:IsInstanceOf("ZONE_AIRBASE") then local airbase=self.cargoTransport.deployzone:GetAirbase() @@ -1749,11 +1790,19 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self:LandAtAirbase(airbase) end else - self:_CheckGroupDone() + self:_CheckGroupDone(nil, 120) end + else + + --- + -- CLIENT + --- + self:_UpdateMenu() + end + end --- On after "Landing" event. @@ -1944,7 +1993,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) local allowed=true local trepeat=nil - if self:IsAlive() then -- and (self:IsAirborne() or self:IsWaiting() or self:IsInbound() or self:IsHolding()) then + if self:IsAlive() then -- Alive & Airborne ==> Update route possible. self:T3(self.lid.."Update route possible. Group is ALIVE") elseif self:IsDead() then @@ -2117,7 +2166,8 @@ end -- -- @param #FLIGHTGROUP self -- @param #number delay Delay in seconds. -function FLIGHTGROUP:_CheckGroupDone(delay) +-- @param #number waittime Time to wait if group is done. +function FLIGHTGROUP:_CheckGroupDone(delay, waittime) if self:IsAlive() and self.isAI then @@ -2127,7 +2177,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay) else -- Debug info. - self:T(self.lid.."Check group done?") + self:T(self.lid.."Check FLIGHTGROUP done?") -- First check if there is a paused mission that if self.missionpaused then @@ -2146,6 +2196,12 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:T(self.lid.."Landing at airbase! Group NOT done...") return end + + -- Group is waiting. + if self.Twaiting then + self:T(self.lid.."Waiting! Group NOT done...") + return + end -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() @@ -2161,6 +2217,10 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Final waypoint passed? if self.passedfinalwp then + + --- + -- Final Waypoint PASSED + --- -- Got current mission or task? if self.currentmission==nil and self.taskcurrent==0 and self.cargoTransport==nil then @@ -2172,13 +2232,16 @@ function FLIGHTGROUP:_CheckGroupDone(delay) local destzone=self.destzone or self.homezone -- Send flight to destination. - if destbase then + if waittime then + self:T(self.lid..string.format("Passed Final WP and No current and/or future missions/tasks/transports. Waittime given ==> Waiting for %d sec!", waittime)) + self:Wait(waittime) + elseif destbase then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") --self:RTB(destbase) self:__RTB(-0.1, destbase) elseif destzone then self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") - self:__RTZ(-3, destzone) + self:__RTZ(-0.1, destzone) else self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") self:__Wait(-1) @@ -2192,8 +2255,17 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission))) end else + + --- + -- Final Waypoint NOT PASSED + --- + + -- Debug info. self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) + + -- Update route. self:__UpdateRoute(-1) + end end @@ -2251,22 +2323,27 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) local Ntot,Nsched, Nwp=self:CountRemainingTasks() if self.taskcurrent>0 then - self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec.")) + self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec")) Tsuspend=-10 allowed=false end if Nsched>0 then - self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec.", Nsched)) + self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec", Nsched)) Tsuspend=-10 allowed=false end if Nwp>0 then - self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec.", Nwp)) + self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec", Nwp)) Tsuspend=-10 allowed=false end + + if self.Twaiting and self.dTwait then + self:I(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled", Nwp)) + allowed=false + end end @@ -2342,6 +2419,9 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set current airbase. self.currbase=airbase + + -- Passed final waypoint! + self.passedfinalwp=true -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) @@ -2500,10 +2580,11 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). -- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. -- @param #number Altitude Altitude in feet. Default 10000 ft. -- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) +function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Coord, Altitude, Speed) Coord=Coord or self.group:GetCoordinate() Altitude=Altitude or (self.isHelo and 1000 or 10000) @@ -2517,9 +2598,34 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) -- Orbit task. local TaskOrbit=self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) + + -- Orbit task. + local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) + local DCSTasks=self.group:TaskCombo({TaskOrbit, TaskFunction}) + + -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. + local TaskOrbit = self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) + local TaskStop = self.group:TaskCondition(nil, nil, nil, nil, Duration) + local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) + local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) + + local DCSTasks + if Duration then + DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver}) + else + DCSTasks=self.group:TaskCombo({TaskOrbit, TaskOver}) + end + + -- Set task. - self:SetTask(TaskOrbit) + self:SetTask(DCSTasks) + + -- Set time stamp. + self.Twaiting=timer.getAbsTime() + + -- Max waiting + self.dTwait=Duration end @@ -2885,6 +2991,20 @@ function FLIGHTGROUP._FinishedRefuelling(group, flightgroup) flightgroup:__Refueled(-1) end +--- Function called when flight finished waiting. +-- @param Wrapper.Group#GROUP group Group object. +-- @param #FLIGHTGROUP flightgroup Flight group object. +function FLIGHTGROUP._FinishedWaiting(group, flightgroup) + flightgroup:T(flightgroup.lid..string.format("Group finished waiting")) + + -- Not waiting any more. + flightgroup.Twaiting=nil + flightgroup.dTwait=nil + + -- Trigger Holding event. + flightgroup:_CheckGroupDone(1) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3034,14 +3154,14 @@ end --- Add an element to the flight group. -- @param #FLIGHTGROUP self -- @param #string unitname Name of unit. --- @return #FLIGHTGROUP.Element The element or nil. +-- @return Ops.OpsGroup#OPSGROUP.Element The element or nil. function FLIGHTGROUP:AddElementByName(unitname) local unit=UNIT:FindByName(unitname) if unit then - local element={} --#FLIGHTGROUP.Element + local element={} --Ops.OpsGroup#OPSGROUP.Element element.name=unitname element.status=OPSGROUP.ElementStatus.INUTERO @@ -3384,7 +3504,7 @@ function FLIGHTGROUP:InitWaypoints() end -- Debug info. - self:I(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) + self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) -- Update route. if #self.waypoints>0 then @@ -3501,7 +3621,7 @@ end function FLIGHTGROUP:_IsElement(unitname) for _,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element if element.name==unitname then return true @@ -3516,7 +3636,7 @@ end --- Set parking spot of element. -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element Element The element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The element. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:_SetElementParkingAt(Element, Spot) @@ -3540,7 +3660,7 @@ end --- Set parking spot of element to free -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element Element The element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The element. function FLIGHTGROUP:_SetElementParkingFree(Element) if Element.parking then @@ -3650,7 +3770,7 @@ end --- Returns the parking spot of the element. -- @param #FLIGHTGROUP self --- @param #FLIGHTGROUP.Element element Element of the flight group. +-- @param Ops.OpsGroup#OPSGROUP.Element element Element of the flight group. -- @param #number maxdist Distance threshold in meters. Default 5 m. -- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase to check for parking. Default is closest airbase to the element. -- @return Wrapper.Airbase#AIRBASE.ParkingSpot Parking spot or nil if no spot is within distance threshold. @@ -3825,7 +3945,7 @@ function FLIGHTGROUP:GetParking(airbase) -- Loop over all units - each one needs a spot. for i,_element in pairs(self.elements) do - local element=_element --#FLIGHTGROUP.Element + local element=_element --Ops.OpsGroup#OPSGROUP.Element -- Loop over all parking spots. local gotit=false diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 0310292ac..9ce1b4210 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -65,12 +65,7 @@ NAVYGROUP = { pathCorridor = 400, } ---- Navy group element. --- @type NAVYGROUP.Element --- @field #string name Name of the element, i.e. the unit. --- @field #string typename Type name. - ---- Navy group element. +--- Turn into wind parameters. -- @type NAVYGROUP.IntoWind -- @field #number Tstart Time to start. -- @field #number Tstop Time to stop. @@ -87,7 +82,7 @@ NAVYGROUP = { --- NavyGroup version. -- @field #string version -NAVYGROUP.version="0.5.0" +NAVYGROUP.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 901bdebc0..9ed08f08a 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -117,7 +117,6 @@ -- @field #string cargoStatus Cargo status of this group acting as cargo. -- @field #number cargoTransportUID Unique ID of the transport assignment this cargo group is associated with. -- @field #string carrierStatus Carrier status of this group acting as cargo carrier. --- @field #number cargocounter Running number of cargo UIDs. -- @field #OPSGROUP.CarrierLoader carrierLoader Carrier loader parameters. -- @field #OPSGROUP.CarrierLoader carrierUnloader Carrier unloader parameters. -- @@ -134,7 +133,7 @@ -- -- # The OPSGROUP Concept -- --- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP and ARMYGROUP. +-- The OPSGROUP class contains common functions used by other classes such as FLIGHTGROUP, NAVYGROUP and ARMYGROUP. -- Those classes inherit everything of this class and extend it with features specific to their unit category. -- -- This class is **NOT** meant to be used by the end user itself. @@ -180,7 +179,6 @@ OPSGROUP = { weaponData = {}, cargoqueue = {}, cargoBay = {}, - cargocounter = 1, mycarrier = {}, carrierLoader = {}, carrierUnloader = {}, @@ -225,6 +223,14 @@ OPSGROUP = { -- @field #number weightCargo Current cargo weight in kg. -- @field #number weight Current weight including cargo in kg. -- @field #table cargoBay Cargo bay. +-- +-- @field #string modex Tail number. +-- @field Wrapper.Client#CLIENT client The client if element is occupied by a human player. +-- @field #table pylons Table of pylons. +-- @field #number fuelmass Mass of fuel in kg. +-- @field #string callsign Call sign, e.g. "Uzi 1-1". +-- @field Wrapper.Airbase#AIRBASE.ParkingSpot parking The parking spot table the element is parking on. + --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -446,7 +452,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.3" +OPSGROUP.version="0.7.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -511,13 +517,13 @@ function OPSGROUP:New(group) self.spot.timer=TIMER:New(self._UpdateLaser, self) self.spot.Coordinate=COORDINATE:New(0, 0, 0) self:SetLaser(1688, true, false, 0.5) + + -- Cargo. self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - self.cargocounter=1 self:SetCarrierLoaderAllAspect() self:SetCarrierUnloaderAllAspect() - -- Init task counter. self.taskcurrent=0 self.taskcounter=0 @@ -1513,6 +1519,7 @@ function OPSGROUP:RadioTransmission(Text, Delay) self.msrs:SetFrequencies(freq) self.msrs:SetModulations(modu) + -- Debug info. self:I(self.lid..string.format("Radio transmission on %.3f MHz %s: %s", freq, UTILS.GetModulationName(modu), Text)) self.msrs:PlayText(Text) @@ -2365,7 +2372,7 @@ function OPSGROUP:OnEventBirth(EventData) self.destbase=self.homebase end - self:I(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s ==> spawned", unitname, self.currbase and self.currbase:GetName() or "unknown")) else self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", unitname)) end @@ -4572,7 +4579,9 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:I(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) + + -- Debug info. + self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -4615,7 +4624,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) -- Clear cargo bay of element. --for _,_cargo in pairs(Element.cargoBay) do for i=#Element.cargoBay,1,-1 do - local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo --_cargo --#OPSGROUP.MyCargo + local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo -- Remove from cargo bay. self:_DelCargobay(cargo.group) @@ -4628,7 +4637,7 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) if cargo.reserved then -- This group was not loaded yet ==> Not cargo any more. - cargo.group.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + cargo.group:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) else @@ -4798,6 +4807,8 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) + + -- Debug info. self:I(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Is dead now. @@ -4897,7 +4908,7 @@ function OPSGROUP:onafterStop(From, Event, To) _DATABASE.FLIGHTGROUPS[self.groupname]=nil -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database") + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from _DATABASE") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4983,7 +4994,7 @@ function OPSGROUP:_CheckCargoTransport() if self:IsNotCarrier() then -- Debug info. - self:I(self.lid.."Not carrier ==> pickup") + self:T(self.lid.."Not carrier ==> pickup") -- Initiate the cargo transport process. self:__Pickup(-1) @@ -5000,7 +5011,7 @@ function OPSGROUP:_CheckCargoTransport() self.Tloading=self.Tloading or Time -- Debug info. - self:I(self.lid.."Loading...") + self:T(self.lid.."Loading...") local boarding=false local gotcargo=false @@ -5018,15 +5029,10 @@ function OPSGROUP:_CheckCargoTransport() end end - - -- Debug. - --self:I(self.lid.."gotcargo="..tostring(gotcargo)) - --self:I(self.lid.."boarding="..tostring(boarding)) - --self:I(self.lid.."required="..tostring(self.cargoTransport:_CheckRequiredCargos())) -- Boarding finished ==> Transport cargo. if gotcargo and self.cargoTransport:_CheckRequiredCargos() and not boarding then - self:I(self.lid.."Boarding finished ==> Loaded") + self:T(self.lid.."Boarding finished ==> Loaded") self:Loaded() else -- No cargo and no one is boarding ==> check again if we can make anyone board. @@ -5046,7 +5052,7 @@ function OPSGROUP:_CheckCargoTransport() elseif self:IsUnloading() then -- Debug info. - self:I(self.lid.."Unloading ==> Checking if all cargo was delivered") + self:T(self.lid.."Unloading ==> Checking if all cargo was delivered") local delivered=true for _,_cargo in pairs(self.cargoTransport.cargos) do @@ -5064,7 +5070,7 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then - self:I(self.lid.."Unloading finished ==> Unloaded") + self:T(self.lid.."Unloading finished ==> Unloaded") self:Unloaded() else self:Unloading() @@ -5468,6 +5474,23 @@ function OPSGROUP:GetWeightCargoMax(UnitName) return weight end +--- Get OPSGROUPs in the cargo bay. +-- @param #OPSGROUP self +-- @return #table Cargo OPSGROUPs. +function OPSGROUP:GetCargoOpsGroups() + + local opsgroups={} + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + for _,_cargo in pairs(element.cargoBay) do + local cargo=_cargo --#OPSGROUP.MyCargo + table.insert(opsgroups, cargo.group) + end + end + + return opsgroups +end + --- Add weight to the internal cargo of an element of the group. -- @param #OPSGROUP self -- @param #string UnitName Name of the unit. Default is of the whole group. @@ -5635,11 +5658,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterPickup(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.PICKUP)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.PICKUP + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.PICKUP) -- Pickup zone. local Zone=self.cargoTransport.pickupzone @@ -5790,11 +5811,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterLoading(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.LOADING)) -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.LOADING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.LOADING) -- Loading time stamp. self.Tloading=timer.getAbsTime() @@ -5824,7 +5843,7 @@ function OPSGROUP:onafterLoading(From, Event, To) if carrier then -- Set cargo status. - cargo.opsgroup.cargoStatus=OPSGROUP.CargoStatus.ASSIGNED + cargo.opsgroup:_NewCargoStatus(OPSGROUP.CargoStatus.ASSIGNED) -- Order cargo group to board the carrier. cargo.opsgroup:Board(self, carrier) @@ -5860,6 +5879,36 @@ function OPSGROUP:ClearWaypoints() self.waypoints={} end +--- Set (new) cargo status. +-- @param #OPSGROUP self +-- @param #string Status New status. +function OPSGROUP:_NewCargoStatus(Status) + + -- Debug info. + if self.verbose>=2 then + self:I(self.lid..string.format("New cargo status: %s --> %s", tostring(self.cargoStatus), tostring(Status))) + end + + -- Set cargo status. + self.cargoStatus=Status + +end + +--- Set (new) carrier status. +-- @param #OPSGROUP self +-- @param #string Status New status. +function OPSGROUP:_NewCarrierStatus(Status) + + -- Debug info. + if self.verbose>=2 then + self:I(self.lid..string.format("New carrier status: %s --> %s", tostring(self.carrierStatus), tostring(Status))) + end + + -- Set cargo status. + self.carrierStatus=Status + +end + --- Transfer cargo from to another carrier. -- @param #OPSGROUP self -- @param #OPSGROUP CargoGroup The cargo group to be transferred. @@ -5886,7 +5935,7 @@ end function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) -- Debug info. - self:I(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) + self:T(self.lid..string.format("Loading group %s", tostring(CargoGroup.groupname))) -- Carrier element. local carrier=Carrier or CargoGroup:_GetMyCarrierElement() --#OPSGROUP.Element @@ -5902,12 +5951,9 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) --- -- Embark Cargo --- - - -- Debug info. - CargoGroup:I(CargoGroup.lid..string.format("New cargo status: %s --> %s", CargoGroup.cargoStatus, OPSGROUP.CargoStatus.LOADED)) - - -- Set cargo status. - CargoGroup.cargoStatus=OPSGROUP.CargoStatus.LOADED + + -- New cargo status. + CargoGroup:_NewCargoStatus(OPSGROUP.CargoStatus.LOADED) -- Clear all waypoints. CargoGroup:ClearWaypoints() @@ -5927,7 +5973,7 @@ function OPSGROUP:onafterLoad(From, Event, To, CargoGroup, Carrier) if self.cargoTransport then self.cargoTransport:Loaded(CargoGroup, self, carrier) else - self:E(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) + self:T(self.lid..string.format("WARNING: Loaded cargo but no current OPSTRANSPORT assignment!")) end else @@ -5981,11 +6027,8 @@ end -- @param #string To To state. function OPSGROUP:onafterTransport(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.TRANSPORTING)) - -- Set carrier status. - self.carrierStatus=OPSGROUP.CarrierStatus.TRANSPORTING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.TRANSPORTING) --TODO: This is all very similar to the onafterPickup() function. Could make it general. @@ -6131,11 +6174,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterUnloading(From, Event, To) - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.UNLOADING)) -- Set carrier status to UNLOADING. - self.carrierStatus=OPSGROUP.CarrierStatus.UNLOADING + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.UNLOADING) -- Deploy zone. local zone=self.cargoTransport.disembarkzone or self.cargoTransport.deployzone --Core.Zone#ZONE @@ -6270,12 +6311,9 @@ end -- @param #boolean Activated If `true`, group is active. If `false`, group is spawned in late activated state. -- @param #number Heading (Optional) Heading of group in degrees. Default is random heading for each unit. function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated, Heading) - - -- Debug info. - OpsGroup:I(OpsGroup.lid..string.format("New cargo status: %s --> %s", OpsGroup.cargoStatus, OPSGROUP.CargoStatus.NOTCARGO)) - - -- Set cargo status. - OpsGroup.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + + -- New cargo status. + OpsGroup:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) --TODO: Unload flightgroup. Find parking spot etc. @@ -6362,8 +6400,9 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterUnloaded(From, Event, To) + -- Debug info - self:I(self.lid.."Cargo unloaded..") + self:T(self.lid.."Cargo unloaded..") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -6400,9 +6439,9 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Check if this was the current transport. if self.cargoTransport and self.cargoTransport.uid==CargoTransport.uid then - - -- Debug info. - self:I(self.lid..string.format("New carrier status: %s --> %s", self.carrierStatus, OPSGROUP.CarrierStatus.NOTCARRIER)) + + -- This is not a carrier anymore. + self:_NewCarrierStatus(OPSGROUP.CarrierStatus.NOTCARRIER) -- Checks if self:IsPickingup() then @@ -6419,9 +6458,6 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) -- Nothing to do? end - -- This is not a carrier anymore. - self.carrierStatus=OPSGROUP.CarrierStatus.NOTCARRIER - -- Startup uncontrolled aircraft to allow it to go back. if self:IsFlightgroup() then if self:IsUncontrolled() then @@ -6436,7 +6472,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end -- Check group done. - self:I(self.lid.."All cargo delivered ==> check group done") + self:T(self.lid.."All cargo delivered ==> check group done") self:_CheckGroupDone(0.2) -- No current transport any more. @@ -6467,11 +6503,11 @@ function OPSGROUP:onbeforeBoard(From, Event, To, CarrierGroup, Carrier) return false elseif CarrierGroup:IsDead() then self:I(self.lid.."Carrier Group DEAD ==> Deny Board transition!") - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) return false elseif Carrier.status==OPSGROUP.ElementStatus.DEAD then self:I(self.lid.."Carrier Element DEAD ==> Deny Board transition!") - self.cargoStatus=OPSGROUP.CargoStatus.NOTCARGO + self:_NewCargoStatus(OPSGROUP.CargoStatus.NOTCARGO) return false end @@ -6486,11 +6522,9 @@ end -- @param #OPSGROUP CarrierGroup The carrier group. -- @param #OPSGROUP.Element Carrier The OPSGROUP element function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) - -- Debug info. - self:I(self.lid..string.format("New cargo status: %s --> %s", self.cargoStatus, OPSGROUP.CargoStatus.BOARDING)) -- Set cargo status. - self.cargoStatus=OPSGROUP.CargoStatus.BOARDING + self:_NewCargoStatus(OPSGROUP.CargoStatus.BOARDING) -- Army or Navy group. local CarrierIsArmyOrNavy=CarrierGroup:IsArmygroup() or CarrierGroup:IsNavygroup() @@ -6510,7 +6544,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) if board then -- Debug info. - self:I(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) + self:T(self.lid..string.format("Boarding group=%s [%s], carrier=%s", CarrierGroup:GetName(), CarrierGroup:GetState(), Carrier.name)) -- TODO: Implement embarkzone. local Coordinate=Carrier.unit:GetCoordinate() @@ -6536,7 +6570,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) --- -- Debug info. - self:I(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) + self:T(self.lid..string.format("Board with direct load to carrier %s", CarrierGroup:GetName())) local mycarriergroup=self:_GetMyCarrierGroup() @@ -6733,6 +6767,9 @@ function OPSGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, self._CheckGroupDone, self) else + -- Debug info. + self:T(self.lid.."Check OPSGROUP done?") + -- Group is engaging something. if self:IsEngaging() then self:UpdateRoute() @@ -7523,7 +7560,8 @@ function OPSGROUP:SwitchROT(rot) self.group:OptionROT(self.option.ROT) - self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + -- Debug info. + self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) end @@ -8326,7 +8364,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Spawned(-0.5) + self:Spawned() end elseif newstatus==OPSGROUP.ElementStatus.PARKING then @@ -8335,7 +8373,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Parking(-0.5) + self:Parking() end elseif newstatus==OPSGROUP.ElementStatus.ENGINEON then @@ -8351,7 +8389,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Taxiing(-0.5) + self:Taxiing() end elseif newstatus==OPSGROUP.ElementStatus.TAKEOFF then @@ -8361,7 +8399,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) if self:_AllSimilarStatus(newstatus) then -- Trigger takeoff event. Also triggers airborne event. - self:__Takeoff(-0.5, airbase) + self:Takeoff(airbase) end elseif newstatus==OPSGROUP.ElementStatus.AIRBORNE then @@ -8370,7 +8408,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Airborne(-0.1) + self:Airborne() end elseif newstatus==OPSGROUP.ElementStatus.LANDED then @@ -8463,13 +8501,19 @@ function OPSGROUP:GetElementZoneBoundingBox(UnitName) -- Create a new zone if necessary. element.zoneBoundingbox=element.zoneBoundingbox or ZONE_POLYGON_BASE:New(element.name.." Zone Bounding Box", {}) + -- Length in meters. local l=element.length + -- Width in meters. local w=element.width + -- Orientation vector. local X=self:GetOrientationX(element.name) + + -- Heading in degrees. local heading=math.deg(math.atan2(X.z, X.x)) - env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + -- Debug info. + self:T(self.lid..string.format("Element %s bouding box: l=%d w=%d heading=%d", element.name, l, w, heading)) -- Set of edges facing "North" at the origin of the map. local b={} @@ -8559,7 +8603,7 @@ function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) -- Heading in deg. local heading=math.deg(math.atan2(X.z, X.x)) - env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) + --env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) -- Bounding box at the origin of the map facing "North". local b={} diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 0ef309150..bea743fe7 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -69,6 +69,44 @@ -- * Cargo groups are **not** split and distributed into different carrier *units*. That means that the whole cargo group **must fit** into one of the carrier units. -- * Cargo groups must be inside the pickup zones to be considered for loading. Groups not inside the pickup zone will not get the command to board. -- +-- # Constructor +-- +-- A new cargo transport assignment is created with the @{#OPSTRANSPORT.New}() function +-- +-- local opstransport=OPSTRANSPORT:New(Cargo, PickupZone, DeployZone) +-- +-- Here `Cargo` is an object of the troops to be transported. This can be a GROUP, OPSGROUP, SET_GROUP or SET_OPSGROUP object. +-- +-- `PickupZone` is the zone where the troops are picked up by the transport carriers. **Note** that troops *must* be inside this zone to be considered for loading! +-- +-- `DeployZone` is the zone where the troops are transported to. +-- +-- ## Assign to Carrier(s) +-- +-- A transport can be assigned to one or multiple carrier OPSGROUPS with this @{Ops.OpsGroup#OPSGROUP.AddOpsTransport}() function +-- +-- myopsgroup:AddOpsTransport(opstransport) +-- +-- There is no restriction to the type of the carrier. It can be a ground group (e.g. an APC), a helicopter, an airplane or even a ship. +-- +-- You can also mix carrier types. For instance, you can assign the same transport to APCs and helicopters. Or to helicopters and airplanes. +-- +-- # Examples +-- +-- A carrier group is assigned to transport infantry troops from zone "Zone Kobuleti X" to zone "Zone Alpha". +-- +-- -- Carrier group. +-- local carrier=ARMYGROUP:New("TPz Fuchs Group") +-- +-- -- Set of groups to transport. +-- local infantryset=SET_GROUP:New():FilterPrefixes("Infantry Platoon Alpha"):FilterOnce() +-- +-- -- Cargo transport assignment. +-- local opstransport=OPSTRANSPORT:New(infantryset, ZONE:New("Zone Kobuleti X"), ZONE:New("Zone Alpha")) +-- +-- -- Assign transport to carrier. +-- carrier:AddOpsTransport(opstransport) +-- -- -- @field #OPSTRANSPORT OPSTRANSPORT = { @@ -112,7 +150,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.0.8" +OPSTRANSPORT.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -227,7 +265,7 @@ function OPSTRANSPORT:AddCargoGroups(GroupSet) end -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Added cargo groups:") local Weight=0 for _,_cargo in pairs(self.cargos) do @@ -432,20 +470,29 @@ end --- Get (all) cargo @{Ops.OpsGroup#OPSGROUP}s. Optionally, only delivered or undelivered groups can be returned. -- @param #OPSTRANSPORT self -- @param #boolean Delivered If `true`, only delivered groups are returned. If `false` only undelivered groups are returned. If `nil`, all groups are returned. --- @return #table Ops groups. +-- @return #table Cargo Ops groups. function OPSTRANSPORT:GetCargoOpsGroups(Delivered) local opsgroups={} for _,_cargo in pairs(self.cargos) do local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup if Delivered==nil or cargo.delivered==Delivered then - table.insert(opsgroups, cargo.opsgroup) + if cargo.opsgroup and not (cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped()) then + table.insert(opsgroups, cargo.opsgroup) + end end end return opsgroups end +--- Get carrier @{Ops.OpsGroup#OPSGROUP}s. +-- @param #OPSTRANSPORT self +-- @return #table Carrier Ops groups. +function OPSTRANSPORT:GetCarrierOpsGroups() + return self.carriers +end + --- Set transport start and stop time. -- @param #OPSTRANSPORT self @@ -579,11 +626,13 @@ function OPSTRANSPORT:_GetPathTransport() local coordinates={} - for _,coord in ipairs(path.coords) do + for _,_coord in ipairs(path.coords) do + local coord=_coord --Core.Point#COORDINATE + + -- Get random coordinate. + local c=coord:GetRandomCoordinateInRadius(path.radius) - -- TODO: Add randomization. - - table.insert(coordinates, coord) + table.insert(coordinates, c) end return coordinates @@ -642,11 +691,13 @@ function OPSTRANSPORT:_GetPathPickup() local coordinates={} - for _,coord in ipairs(path.coords) do + for _,_coord in ipairs(path.coords) do + local coord=_coord --Core.Point#COORDINATE + + -- Get random coordinate. + local c=coord:GetRandomCoordinateInRadius(path.radius) - -- TODO: Add randomization. - - table.insert(coordinates, coord) + table.insert(coordinates, c) end return coordinates @@ -753,7 +804,7 @@ function OPSTRANSPORT:IsReadyToGo() -- Nope, not yet. if not startme then text=text..("No way, at least one start condition is not true!") - self:I(text) + self:T(text) return false end From 3d6b053eb4ad68af438299bbe4d86e40ae191bbd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Jul 2021 18:31:09 +0200 Subject: [PATCH 352/382] Added function to check if a loading door on a heli is open --- Moose Development/Moose/Utilities/Utils.lua | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 29ac7f8c2..ad8bb6291 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1567,3 +1567,43 @@ function UTILS.ShuffleTable(t) end return TempTable end + +--- (Helicopter) Check if one loading door is open. +--@param #string unit_name Unit name to be checked +--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists. +function UTILS.IsLoadingDoorOpen( unit_name ) + + local ret_val = false + local unit = Unit.getByName(unit_name) + if unit ~= nil then + local type_name = unit:getTypeName() + + if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then + BASE:T(unit_name .. " Cargo doors are open or cargo door not present") + ret_val = true + end + + if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then + BASE:T(unit_name .. " a side door is open") + ret_val = true + end + + if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then + BASE:T(unit_name .. " a side door is open ") + ret_val = true + end + + if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then + BASE:T(unit_name .. " front door(s) are open") + ret_val = true + end + + if ret_val == false then + BASE:T(unit_name .. " all doors are closed") + end + return ret_val + + end -- nil + + return nil +end \ No newline at end of file From 52e2ac7174f11310ac74f193cf179bb7a48200f9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Jul 2021 18:31:17 +0200 Subject: [PATCH 353/382] Added documentation --- Moose Development/Moose/Ops/CSAR.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 0a3d14c64..d243475b0 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1377,7 +1377,7 @@ end --- (Internal) Check and return Wrappe.Unit#UNIT based on the name if alive. -- @param #CSAR self -- @param #string _unitname Name of Unit --- @return #UNIT or nil +-- @return Wrapper.Unit#UNIT The unit or nil function CSAR:_GetSARHeli(_unitName) self:T(self.lid .. " _GetSARHeli") local unit = UNIT:FindByName(_unitName) From 1b717e4683002fb12f9d9891e570ddd36c3a04c6 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 12 Jul 2021 15:27:08 +0200 Subject: [PATCH 354/382] AUFTRAG - Added push time --- Moose Development/Moose/Core/Set.lua | 11 ++- Moose Development/Moose/Ops/ArmyGroup.lua | 11 +++ Moose Development/Moose/Ops/Auftrag.lua | 25 ++++- Moose Development/Moose/Ops/FlightGroup.lua | 19 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 11 +++ Moose Development/Moose/Ops/OpsGroup.lua | 103 +++++++++++++++++--- 6 files changed, 146 insertions(+), 34 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index c978d8f85..e6ab285ba 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -233,7 +233,9 @@ do -- SET_BASE -- @param Core.Base#BASE Object The object itself. -- @return Core.Base#BASE The added BASE Object. function SET_BASE:Add( ObjectName, Object ) - self:I( { ObjectName = ObjectName, Object = Object } ) + + -- Debug info. + self:T( { ObjectName = ObjectName, Object = Object } ) -- Ensure that the existing element is removed from the Set before a new one is inserted to the Set if self.Set[ObjectName] then @@ -5908,12 +5910,13 @@ do -- SET_ZONE_GOAL -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData ) - self:I( { "New Zone Capture Coalition", EventData } ) - self:I( { "Zone Capture Coalition", EventData.ZoneGoal } ) + -- Debug info. + self:T( { "New Zone Capture Coalition", EventData } ) + self:T( { "Zone Capture Coalition", EventData.ZoneGoal } ) if EventData.ZoneGoal then if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then - self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) + self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } ) self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal ) end end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index d8b506e64..301cd48f1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -392,6 +392,17 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_UpdateEngageTarget() end + -- Check if group is waiting. + if self:IsWaiting() then + if self.Twaiting and self.dTwait then + if timer.getAbsTime()>self.Twaiting+self.dTwait then + self.Twaiting=nil + self.dTwait=nil + self:Cruise() + end + end + end + end if alive~=nil then diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 06183c53d..4c55f5119 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -39,9 +39,10 @@ -- @field #number prio Mission priority. -- @field #boolean urgent Mission is urgent. Running missions with lower prio might be cancelled. -- @field #number importance Importance. --- @field #number Tstart Mission start time in seconds. --- @field #number Tstop Mission stop time in seconds. +-- @field #number Tstart Mission start time in abs. seconds. +-- @field #number Tstop Mission stop time in abs. seconds. -- @field #number duration Mission duration in seconds. +-- @field #number Tpush Mission push/execute time in abs. seconds. -- @field Wrapper.Marker#MARKER marker F10 map marker. -- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status. -- @field #number markerCoaliton Coalition to which the marker is dispayed. @@ -442,7 +443,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.6.0" +AUFTRAG.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1436,6 +1437,24 @@ function AUFTRAG:SetTime(ClockStart, ClockStop) return self end + +--- Set mission push time. This is the time the mission is executed. If the push time is not passed, the group will wait at the mission execution waypoint. +-- @param #AUFTRAG self +-- @param #string ClockPush Time the mission is executed, e.g. "05:00" for 5 am. Can also be given as a `#number`, where it is interpreted as relative push time in seconds. +-- @return #AUFTRAG self +function AUFTRAG:SetPushTime(ClockPush) + + if ClockPush then + if type(ClockPush)=="string" then + self.Tpush=UTILS.ClockToSeconds(ClockPush) + elseif type(ClockPush)=="number" then + self.Tpush=timer.getAbsTime()+ClockPush + end + end + + return self +end + --- Set mission priority and (optional) urgency. Urgent missions can cancel other running missions. -- @param #AUFTRAG self -- @param #number Prio Priority 1=high, 100=low. Default 50. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index fa3aec358..19d6d061d 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -609,13 +609,6 @@ function FLIGHTGROUP:IsCruising() return self:Is("Cruising") end ---- Check if flight is waiting after passing final waypoint. --- @param #FLIGHTGROUP self --- @return #boolean If true, flight is waiting. -function FLIGHTGROUP:IsWaiting() - return self:Is("Waiting") -end - --- Check if flight is landing. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is landing, i.e. on final approach. @@ -2556,15 +2549,15 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) end if Nsched>0 then - self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) - Tsuspend=-10 - allowed=false + --self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) + --Tsuspend=-10 + --allowed=false end if Nwp>0 then - self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) - Tsuspend=-10 - allowed=false + --self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) + --Tsuspend=-10 + --allowed=false end if Tsuspend and not allowed then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9ce1b4210..5284cec36 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -522,6 +522,17 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group got stuck. self:_CheckStuck() + + -- Check if group is waiting. + if self:IsWaiting() then + if self.Twaiting and self.dTwait then + if timer.getAbsTime()>self.Twaiting+self.dTwait then + self.Twaiting=nil + self.dTwait=nil + self:Cruise() + end + end + end if self.verbose>=1 then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 9ed08f08a..0d686919d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -284,6 +284,7 @@ OPSGROUP.TaskType={ --- Task structure. -- @type OPSGROUP.Task -- @field #string type Type of task: either SCHEDULED or WAYPOINT. +-- @field #boolean ismission This is an AUFTRAG task. -- @field #number id Task ID. Running number to get the task. -- @field #number prio Priority. -- @field #number time Abs. mission time when to execute the task. @@ -2876,6 +2877,57 @@ function OPSGROUP:GetTaskByID(id, status) return nil end +--- On before "TaskExecute" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP.Task Task The task. +function OPSGROUP:onbeforeTaskExecute(From, Event, To, Task) + + -- Get mission of this task (if any). + local Mission=self:GetMissionByTaskID(Task.id) + + if Mission then + + if Mission.Tpush then + + local Tnow=timer.getAbsTime() + + -- Time to push + local dt=Mission.Tpush-Tnow + + -- Push time not reached. + if Tnow0 then for i,_task in pairs(tasks) do local task=_task --#OPSGROUP.Task text=text..string.format("\n[%d] %s", i, task.description) + if task.ismission then + missiontask=task + end end else text=text.." None" end self:T(self.lid..text) + + -- Check if there is mission task + if missiontask then + env.info("FF executing mission task") + self:TaskExecute(missiontask) + return 1 + end + -- TODO: maybe set waypoint enroute tasks? -- Tasks at this waypoints. local taskswp={} - -- TODO: maybe set waypoint enroute tasks? - for _,task in pairs(tasks) do local Task=task --Ops.OpsGroup#OPSGROUP.Task From c0f4eef896ecd28d95c0cb5577de0bc21955375d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 18:10:12 +0200 Subject: [PATCH 355/382] Slightly changed versions of Mantis, Sead and Shorad as the Emissions On/Off stuff ED introduced doesn't really work well. --- Moose Development/Moose/Functional/Mantis.lua | 6 +- Moose Development/Moose/Functional/Sead.lua | 278 ++++++++---------- Moose Development/Moose/Functional/Shorad.lua | 247 +++++++++------- 3 files changed, 254 insertions(+), 277 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 23faabb3f..bb6fbe45e 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -191,7 +191,7 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = true, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -208,7 +208,7 @@ do --@param #string coaltion Coalition side of your setup, e.g. "blue", "red" or "neutral" --@param #boolean dynamic Use constant (true) filtering or just filter once (false, default) (optional) --@param #string awacs Group name of your Awacs (optional) - --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN (optional, deault true) + --@param #boolean EmOnOff Make MANTIS switch Emissions on and off instead of changing the alarm state between RED and GREEN --@return #MANTIS self --@usage Start up your MANTIS with a basic setting -- @@ -267,6 +267,8 @@ do if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false + else + self.UseEmOnOff = true end end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 8eb98d4f4..ec568b627 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -1,26 +1,26 @@ --- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- === --- +-- -- ## Features: --- +-- -- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible. -- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars. --- +-- -- === --- +-- -- ## Missions: --- +-- -- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion) --- +-- -- === --- +-- -- ### Authors: **FlightControl**, **applevangelist** --- --- Last Update: April 2021 --- +-- +-- Last Update: Feb 2021 +-- -- === --- +-- -- @module Functional.Sead -- @image SEAD.JPG @@ -28,49 +28,32 @@ -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. --- +-- -- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon. --- +-- -- # Constructor: --- +-- -- Use the @{#SEAD.New}() constructor to create a new SEAD object. --- +-- -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) --- +-- -- @field #SEAD SEAD = { - ClassName = "SEAD", - TargetSkill = { - Average = { Evade = 30, DelayOn = { 40, 60 } } , - Good = { Evade = 20, DelayOn = { 30, 50 } } , - High = { Evade = 15, DelayOn = { 20, 40 } } , - Excellent = { Evade = 10, DelayOn = { 10, 30 } } - }, - SEADGroupPrefixes = {}, - SuppressedGroups = {}, - EngagementRange = 75 -- default 75% engagement range Feature Request #1355 + ClassName = "SEAD", + TargetSkill = { + Average = { Evade = 30, DelayOn = { 40, 60 } } , + Good = { Evade = 20, DelayOn = { 30, 50 } } , + High = { Evade = 15, DelayOn = { 20, 40 } } , + Excellent = { Evade = 10, DelayOn = { 10, 30 } } + }, + SEADGroupPrefixes = {}, + SuppressedGroups = {}, + EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } - -- TODO Complete list? --- Missile enumerators -- @field Harms SEAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -84,7 +67,7 @@ SEAD = { ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. @@ -97,20 +80,20 @@ SEAD = { -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) function SEAD:New( SEADGroupPrefixes ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) - - if type( SEADGroupPrefixes ) == 'table' then - for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do - self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix - end - else - self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes - end - - self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.7") - return self + local self = BASE:Inherit( self, BASE:New() ) + self:F( SEADGroupPrefixes ) + + if type( SEADGroupPrefixes ) == 'table' then + for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do + self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix + end + else + self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes + end + + self:HandleEvent( EVENTS.Shot, self.HandleEventShot ) + self:I("*** SEAD - Started Version 0.2.8") + return self end --- Update the active SEAD Set @@ -120,7 +103,7 @@ end function SEAD:UpdateSet( SEADGroupPrefixes ) self:F( SEADGroupPrefixes ) - + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -164,112 +147,83 @@ end -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData -function SEAD:OnEventShot( EventData ) - self:T( { EventData } ) +function SEAD:HandleEventShot( EventData ) + self:T( { EventData } ) - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type + local SEADUnit = EventData.IniDCSUnit + local SEADUnitName = EventData.IniDCSUnitName + local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeaponName = EventData.WeaponName -- return weapon type - self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - self:T({ SEADWeapon }) - - --[[check for SEAD missiles - if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.LD-10" --LD-10 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84E" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired - or - SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired - --]] + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T({ SEADWeapon }) + if self:_CheckHarms(SEADWeaponName) then local _targetskill = "Random" local _targetMimgroupName = "none" - local _evade = math.random (1,100) -- random number for chance of evading action - local _targetMim = EventData.Weapon:getTarget() -- Identify target - local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object - if _targetUnit and _targetUnit:IsAlive() then - local _targetMimgroup = _targetUnit:GetGroup() - local _targetMimgroupName = _targetMimgroup:GetName() -- group name - --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill - self:T( self.SEADGroupPrefixes ) - self:T( _targetMimgroupName ) - end - -- see if we are shot at - local SEADGroupFound = false - for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do - if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then - SEADGroupFound = true - self:T( '*** SEAD - Group Found' ) - break - end - end - if SEADGroupFound == true then -- yes we are being attacked - if _targetskill == "Random" then -- when skill is random, choose a skill - local Skills = { "Average", "Good", "High", "Excellent" } - _targetskill = Skills[ math.random(1,4) ] - end - self:T( _targetskill ) - if self.TargetSkill[_targetskill] then - if (_evade > self.TargetSkill[_targetskill].Evade) then + local _evade = math.random (1,100) -- random number for chance of evading action + local _targetMim = EventData.Weapon:getTarget() -- Identify target + local _targetUnit = UNIT:Find(_targetMim) -- Unit name by DCS Object + if _targetUnit and _targetUnit:IsAlive() then + local _targetMimgroup = _targetUnit:GetGroup() + local _targetMimgroupName = _targetMimgroup:GetName() -- group name + --local _targetskill = _DATABASE.Templates.Units[_targetUnit].Template.skill + self:T( self.SEADGroupPrefixes ) + self:T( _targetMimgroupName ) + end + -- see if we are shot at + local SEADGroupFound = false + for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do + if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then + SEADGroupFound = true + self:T( '*** SEAD - Group Found' ) + break + end + end + if SEADGroupFound == true then -- yes we are being attacked + if _targetskill == "Random" then -- when skill is random, choose a skill + local Skills = { "Average", "Good", "High", "Excellent" } + _targetskill = Skills[ math.random(1,4) ] + end + self:T( _targetskill ) + if self.TargetSkill[_targetskill] then + if (_evade > self.TargetSkill[_targetskill].Evade) then + + self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) + + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) + local _targetMimcont= _targetMimgroup:getController() + + routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + + --tracker ID table to switch groups off and on again + local id = { + groupName = _targetMimgroup, + ctrl = _targetMimcont + } - self:T( string.format("*** SEAD - Evading, target skill " ..string.format(_targetskill)) ) - - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) - local _targetMimcont= _targetMimgroup:getController() - - routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly - - --tracker ID table to switch groups off and on again - local id = { - groupName = _targetMimgroup, - ctrl = _targetMimcont - } - - local function SuppressionEnd(id) --switch group back on - local range = self.EngagementRange -- Feature Request #1355 - self:T(string.format("*** SEAD - Engagement Range is %d", range)) - id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) - --id.groupName:enableEmission(true) - id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 - self.SuppressedGroups[id.groupName] = nil --delete group id from table when done - end - -- randomize switch-on time - local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) - local SuppressionEndTime = timer.getTime() + delay - --create entry - if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet - self.SuppressedGroups[id.groupName] = { - SuppressionEndTime = delay - } - Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) - --_targetMimgroup:enableEmission(false) - timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function - end - end - end - end - end + local function SuppressionEnd(id) --switch group back on + local range = self.EngagementRange -- Feature Request #1355 + self:T(string.format("*** SEAD - Engagement Range is %d", range)) + id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) + --id.groupName:enableEmission(true) + id.ctrl:setOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,range) --Feature Request #1355 + self.SuppressedGroups[id.groupName] = nil --delete group id from table when done + end + -- randomize switch-on time + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + local SuppressionEndTime = timer.getTime() + delay + --create entry + if self.SuppressedGroups[id.groupName] == nil then --no timer entry for this group yet + self.SuppressedGroups[id.groupName] = { + SuppressionEndTime = delay + } + Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) + --_targetMimgroup:enableEmission(false) + timer.scheduleFunction(SuppressionEnd, id, SuppressionEndTime) --Schedule the SuppressionEnd() function + end + end + end + end + end end diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 1dbff94f4..36ca8ecdf 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -1,24 +1,24 @@ --- **Functional** -- Short Range Air Defense System --- +-- -- === --- +-- -- **SHORAD** - Short Range Air Defense System -- Controls a network of short range air/missile defense groups. --- +-- -- === --- +-- -- ## Missions: -- -- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense) --- +-- -- === --- --- ### Author : **applevangelist** --- +-- +-- ### Author : **applevangelist ** +-- -- @module Functional.Shorad -- @image Functional.Shorad.jpg -- --- Date: May 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -26,7 +26,7 @@ -- @field #string ClassName -- @field #string name Name of this Shorad -- @field #boolean debug Set the debug state --- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} +-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} -- @field #number Radius Shorad defense radius in meters -- @field Core.Set#SET_GROUP Groupset The set of Shorad groups -- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend @@ -41,10 +41,10 @@ -- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green. -- @extends Core.Base#BASE ---- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) --- +--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) +-- -- Simple Class for a more intelligent Short Range Air Defense System --- +-- -- #SHORAD -- Moose derived missile intercepting short range defense system. -- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy. @@ -52,26 +52,26 @@ -- -- ## Usage -- --- Set up a #SET_GROUP for the SAM sites to be protected: --- --- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` --- +-- Set up a #SET_GROUP for the SAM sites to be protected: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` +-- -- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%. --- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. --- +-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. +-- -- ### Start a new SHORAD system, parameters are: +-- +-- * Name: Name of this SHORAD. +-- * ShoradPrefix: Filter for the Shorad #SET_GROUP. +-- * Samset: The #SET_GROUP of SAM sites to defend. +-- * Radius: Defense radius in meters. +-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. +-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* +-- +-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` -- --- * Name: Name of this SHORAD. --- * ShoradPrefix: Filter for the Shorad #SET_GROUP. --- * Samset: The #SET_GROUP of SAM sites to defend. --- * Radius: Defense radius in meters. --- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. --- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* --- --- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` --- --- ## Customize options --- +-- ## Customize options +-- -- * SHORAD:SwitchDebug(debug) -- * SHORAD:SwitchHARMDefense(onoff) -- * SHORAD:SwitchAGMDefense(onoff) @@ -94,9 +94,9 @@ SHORAD = { lid = "", DefendHarms = true, DefendMavs = true, - DefenseLowProb = 75, + DefenseLowProb = 70, DefenseHighProb = 90, - UseEmOnOff = false, + UseEmOnOff = false, } ----------------------------------------------------------------------- @@ -108,22 +108,6 @@ do --- Missile enumerators -- @field Harms SHORAD.Harms = { - --[[ - ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired - ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired - ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired - ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired - ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired - ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired - ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired - ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired - ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired - ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired - ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired - ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired - ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired - ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired - --]] ["AGM_88"] = "AGM_88", ["AGM_45"] = "AGM_45", ["AGM_122"] = "AGM_122", @@ -137,7 +121,7 @@ do ["X_31"] = "X_31", ["Kh25"] = "Kh25", } - + --- TODO complete list? -- @field Mavs SHORAD.Mavs = { @@ -148,7 +132,7 @@ do ["Kh31"] = "Kh31", ["Kh66"] = "Kh66", } - + --- Instantiates a new SHORAD object -- @param #SHORAD self -- @param #string Name Name of this SHORAD @@ -157,10 +141,12 @@ do -- @param #number Radius Defense radius in meters, used to switch on groups -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" - function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + -- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch) + -- @retunr #SHORAD self + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) local self = BASE:Inherit( self, BASE:New() ) - self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) - + self:I({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() self.name = Name or "MyShorad" @@ -171,81 +157,101 @@ do self.ActiveTimer = ActiveTimer or 600 self.ActiveGroups = {} self.Groupset = GroupSet - self:HandleEvent( EVENTS.Shot ) self.DefendHarms = true self.DefendMavs = true self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin - self.UseEmOnOff = true -- Decide if we are using Emission on/off (default) or AlarmState red/green - self:I("*** SHORAD - Started Version 0.2.5") + self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green + self:I("*** SHORAD - Started Version 0.2.8") -- Set the string id for output to DCS.log file. self.lid=string.format("SHORAD %s | ", self.name) self:_InitState() + self:HandleEvent(EVENTS.Shot, self.HandleEventShot) return self end - + --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() + self:I(self.lid .. " _InitState") local table = {} local set = self.Groupset - self:T({set = set}) + self:I({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do if self.UseEmOnOff then --_group:SetAIOff() _group:EnableEmission(false) + _group:OptionAlarmStateRed() --Wrapper.Group#GROUP else _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP end + _group:OptionDisperseOnAttack(30) end -- gather entropy - for i=1,10 do + for i=1,100 do math.random() end + return self end - - --- Switch debug state + + --- Switch debug state on -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) - function SHORAD:SwitchDebug(debug) - self:T( { debug } ) - local onoff = debug or false - if debug then - self.debug = true - --tracing - BASE:TraceOn() - BASE:TraceClass("SHORAD") + function SHORAD:SwitchDebug(onoff) + self:I( { onoff } ) + if onoff then + self:SwitchDebugOn() else - self.debug = false - BASE:TraceOff() + self.SwitchDebugOff() end + return self end - + + --- Switch debug state on + -- @param #SHORAD self + function SHORAD:SwitchDebugOn() + self.debug = true + --tracing + BASE:TraceOn() + BASE:TraceClass("SHORAD") + return self + end + + --- Switch debug state off + -- @param #SHORAD self + function SHORAD:SwitchDebugOff() + self.debug = false + BASE:TraceOff() + return self + end + --- Switch defense for HARMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:T( { onoff } ) + self:I( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff + return self end - + --- Switch defense for AGMs -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:T( { onoff } ) + self:I( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff + return self end - + --- Set defense probability limits -- @param #SHORAD self -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:T( { low, high } ) + self:I( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -256,43 +262,51 @@ do end self.DefenseLowProb = low self.DefenseHighProb = high + return self end - + --- Set the number of seconds a SHORAD site will stay active -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active function SHORAD:SetActiveTimer(seconds) + self:I(self.lid .. " SetActiveTimer") local timer = seconds or 600 if timer < 0 then timer = 600 end self.ActiveTimer = timer + return self end --- Set the number of meters for the SHORAD defense zone -- @param #SHORAD self - -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active + -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) + self:I(self.lid .. " SetDefenseRadius") local radius = meters or 20000 if radius < 0 then radius = 20000 end self.Radius = radius + return self end - + --- Set using Emission on/off instead of changing alarm state -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) + self:I(self.lid .. " SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end - + --- Check if a HARM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:T( { WeaponName } ) + self:I(self.lid .. " _CheckHarms") + self:I( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -301,13 +315,14 @@ do end return hit end - + --- Check if an AGM was fired -- @param #SHORAD self -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:T( { WeaponName } ) + self:I(self.lid .. " _CheckMavs") + self:I( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -316,34 +331,36 @@ do end return hit end - + --- Check the coalition of the attacker -- @param #SHORAD self -- @param #string Coalition name -- @return #boolean Returns false for a match function SHORAD:_CheckCoalition(Coalition) + self:I(self.lid .. " _CheckCoalition") local owncoalition = self.Coalition local othercoalition = "" - if Coalition == 0 then + if Coalition == 0 then othercoalition = "neutral" elseif Coalition == 1 then othercoalition = "red" else othercoalition = "blue" end - self:T({owncoalition = owncoalition, othercoalition = othercoalition}) + self:I({owncoalition = owncoalition, othercoalition = othercoalition}) if owncoalition ~= othercoalition then return true else return false end end - + --- Check if the missile is aimed at a SHORAD -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtShorad(TargetGroupName) + self:I(self.lid .. " _CheckShotAtShorad") local tgtgrp = TargetGroupName local shorad = self.Groupset local shoradset = shorad:GetAliveSet() --#table @@ -352,17 +369,18 @@ do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1) then returnname = true - _groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive + --_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive end end - return returnname + return returnname end - + --- Check if the missile is aimed at a SAM site -- @param #SHORAD self -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtSams(TargetGroupName) + self:I(self.lid .. " _CheckShotAtSams") local tgtgrp = TargetGroupName local shorad = self.Samset --local shoradset = shorad:GetAliveSet() --#table @@ -376,11 +394,12 @@ do end return returnname end - + --- Calculate if the missile shot is detected -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false function SHORAD:_ShotIsDetected() + self:I(self.lid .. " _ShotIsDetected") local IsDetected = false local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local ActualDetection = math.random(1,100) -- value for this shot @@ -389,15 +408,15 @@ do end return IsDetected end - + --- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds -- @param #SHORAD self -- @param #string TargetGroup Name of the target group used to build the #ZONE -- @param #number Radius Radius of the #ZONE -- @param #number ActiveTimer Number of seconds to stay active -- @param #number TargetCat (optional) Category, i.e. Object.Category.UNIT or Object.Category.STATIC - -- @usage Use this function to integrate with other systems, example - -- + -- @usage Use this function to integrate with other systems, example + -- -- local SamSet = SET_GROUP:New():FilterPrefixes("Blue SAM"):FilterCoalitions("blue"):FilterStart() -- myshorad = SHORAD:New("BlueShorad", "Blue SHORAD", SamSet, 22000, 600, "blue") -- myshorad:SwitchDebug(true) @@ -405,7 +424,8 @@ do -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) - self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) + self:I(self.lid .. " WakeUpShorad") + self:I({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup local targetvec2 = nil @@ -432,17 +452,17 @@ do group:OptionAlarmStateGreen() end local text = string.format("Sleeping SHORAD %s", group:GetName()) - self:T(text) + self:I(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) end -- go through set and find the one(s) to activate for _,_group in pairs (shoradset) do if _group:IsAnyInZone(targetzone) then local text = string.format("Waking up SHORAD %s", _group:GetName()) - self:T(text) + self:I(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) if self.UseEmOnOff then - _group:SetAIOn() + --_group:SetAIOn() _group:EnableEmission(true) end _group:OptionAlarmStateRed() @@ -454,14 +474,15 @@ do end end end + return self end - + --- Main function - work on the EventData -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set - function SHORAD:OnEventShot( EventData ) - self:T( { EventData } ) - + function SHORAD:HandleEventShot( EventData ) + self:I( { EventData } ) + self:I(self.lid .. " HandleEventShot") --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -473,24 +494,24 @@ do local IsDetected = self:_ShotIsDetected() -- convert to text local DetectedText = "false" - if IsDetected then + if IsDetected then DetectedText = "true" end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) - self:T( text ) + self:I( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target local targetcat = targetdata:getCategory() -- Identify category - self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + self:I(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) local targetunit = nil if targetcat == Object.Category.UNIT then -- UNIT targetunit = UNIT:Find(targetdata) elseif targetcat == Object.Category.STATIC then -- STATIC targetunit = STATIC:Find(targetdata) - end + end --local targetunitname = Unit.getName(targetdata) -- Unit name if targetunit and targetunit:IsAlive() then local targetunitname = targetunit:GetName() @@ -505,23 +526,23 @@ do targetgroupname = targetunitname end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) - self:T( text ) + self:I( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) - -- check if we or a SAM site are the target + -- check if we or a SAM site are the target --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then - self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:I({shotatsams=shotatsams,shotatus=shotatus}) self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end - end + end end end - end + end -- end ----------------------------------------------------------------------- -- SHORAD end ------------------------------------------------------------------------ +----------------------------------------------------------------------- \ No newline at end of file From 86fedbfaae4235c3e0b231c8df0d17e282e71d6f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 18:16:22 +0200 Subject: [PATCH 356/382] Updated noise level --- Moose Development/Moose/Functional/Mantis.lua | 4 +- Moose Development/Moose/Functional/Sead.lua | 2 +- Moose Development/Moose/Functional/Shorad.lua | 58 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index bb6fbe45e..8ff110925 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -20,7 +20,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg --- Date: Apr 2021 +-- Date: July 2021 ------------------------------------------------------------------------- --- **MANTIS** class, extends #Core.Base#BASE @@ -310,7 +310,7 @@ do end -- @field #string version - self.version="0.4.1" + self.version="0.4.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) return self diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index ec568b627..c99e1f241 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,7 +17,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: Feb 2021 +-- Last Update: July 2021 -- -- === -- diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index 36ca8ecdf..958877ec8 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -145,7 +145,7 @@ do -- @retunr #SHORAD self function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff) local self = BASE:Inherit( self, BASE:New() ) - self:I({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() @@ -173,10 +173,10 @@ do --- Initially set all groups to alarm state GREEN -- @param #SHORAD self function SHORAD:_InitState() - self:I(self.lid .. " _InitState") + self:T(self.lid .. " _InitState") local table = {} local set = self.Groupset - self:I({set = set}) + self:T({set = set}) local aliveset = set:GetAliveSet() --#table for _,_group in pairs (aliveset) do if self.UseEmOnOff then @@ -199,7 +199,7 @@ do -- @param #SHORAD self -- @param #boolean debug Switch debug on (true) or off (false) function SHORAD:SwitchDebug(onoff) - self:I( { onoff } ) + self:T( { onoff } ) if onoff then self:SwitchDebugOn() else @@ -230,7 +230,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchHARMDefense(onoff) - self:I( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendHarms = onoff return self @@ -240,7 +240,7 @@ do -- @param #SHORAD self -- @param #boolean onoff function SHORAD:SwitchAGMDefense(onoff) - self:I( { onoff } ) + self:T( { onoff } ) local onoff = onoff or true self.DefendMavs = onoff return self @@ -251,7 +251,7 @@ do -- @param #number low Minimum detection limit, integer 1-100 -- @param #number high Maximum detection limit integer 1-100 function SHORAD:SetDefenseLimits(low,high) - self:I( { low, high } ) + self:T( { low, high } ) local low = low or 70 local high = high or 90 if (low < 0) or (low > 100) or (low > high) then @@ -269,7 +269,7 @@ do -- @param #SHORAD self -- @param #number seconds Number of seconds systems stay active function SHORAD:SetActiveTimer(seconds) - self:I(self.lid .. " SetActiveTimer") + self:T(self.lid .. " SetActiveTimer") local timer = seconds or 600 if timer < 0 then timer = 600 @@ -282,7 +282,7 @@ do -- @param #SHORAD self -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active function SHORAD:SetDefenseRadius(meters) - self:I(self.lid .. " SetDefenseRadius") + self:T(self.lid .. " SetDefenseRadius") local radius = meters or 20000 if radius < 0 then radius = 20000 @@ -295,7 +295,7 @@ do -- @param #SHORAD self -- @param #boolean switch Decide if we are changing alarm state or AI state function SHORAD:SetUsingEmOnOff(switch) - self:I(self.lid .. " SetUsingEmOnOff") + self:T(self.lid .. " SetUsingEmOnOff") self.UseEmOnOff = switch or false return self end @@ -305,8 +305,8 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckHarms(WeaponName) - self:I(self.lid .. " _CheckHarms") - self:I( { WeaponName } ) + self:T(self.lid .. " _CheckHarms") + self:T( { WeaponName } ) local hit = false if self.DefendHarms then for _,_name in pairs (SHORAD.Harms) do @@ -321,8 +321,8 @@ do -- @param #string WeaponName -- @return #boolean Returns true for a match function SHORAD:_CheckMavs(WeaponName) - self:I(self.lid .. " _CheckMavs") - self:I( { WeaponName } ) + self:T(self.lid .. " _CheckMavs") + self:T( { WeaponName } ) local hit = false if self.DefendMavs then for _,_name in pairs (SHORAD.Mavs) do @@ -337,7 +337,7 @@ do -- @param #string Coalition name -- @return #boolean Returns false for a match function SHORAD:_CheckCoalition(Coalition) - self:I(self.lid .. " _CheckCoalition") + self:T(self.lid .. " _CheckCoalition") local owncoalition = self.Coalition local othercoalition = "" if Coalition == 0 then @@ -347,7 +347,7 @@ do else othercoalition = "blue" end - self:I({owncoalition = owncoalition, othercoalition = othercoalition}) + self:T({owncoalition = owncoalition, othercoalition = othercoalition}) if owncoalition ~= othercoalition then return true else @@ -360,7 +360,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtShorad(TargetGroupName) - self:I(self.lid .. " _CheckShotAtShorad") + self:T(self.lid .. " _CheckShotAtShorad") local tgtgrp = TargetGroupName local shorad = self.Groupset local shoradset = shorad:GetAliveSet() --#table @@ -380,7 +380,7 @@ do -- @param #string TargetGroupName Name of the target group -- @return #boolean Returns true for a match, else false function SHORAD:_CheckShotAtSams(TargetGroupName) - self:I(self.lid .. " _CheckShotAtSams") + self:T(self.lid .. " _CheckShotAtSams") local tgtgrp = TargetGroupName local shorad = self.Samset --local shoradset = shorad:GetAliveSet() --#table @@ -399,7 +399,7 @@ do -- @param #SHORAD self -- @return #boolean Returns true for a detection, else false function SHORAD:_ShotIsDetected() - self:I(self.lid .. " _ShotIsDetected") + self:T(self.lid .. " _ShotIsDetected") local IsDetected = false local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value local ActualDetection = math.random(1,100) -- value for this shot @@ -424,8 +424,8 @@ do -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat) - self:I(self.lid .. " WakeUpShorad") - self:I({TargetGroup, Radius, ActiveTimer, TargetCat}) + self:T(self.lid .. " WakeUpShorad") + self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT local targetgroup = TargetGroup local targetvec2 = nil @@ -452,14 +452,14 @@ do group:OptionAlarmStateGreen() end local text = string.format("Sleeping SHORAD %s", group:GetName()) - self:I(text) + self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) end -- go through set and find the one(s) to activate for _,_group in pairs (shoradset) do if _group:IsAnyInZone(targetzone) then local text = string.format("Waking up SHORAD %s", _group:GetName()) - self:I(text) + self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) if self.UseEmOnOff then --_group:SetAIOn() @@ -481,8 +481,8 @@ do -- @param #SHORAD self -- @param Core.Event#EVENTDATA EventData The event details table data set function SHORAD:HandleEventShot( EventData ) - self:I( { EventData } ) - self:I(self.lid .. " HandleEventShot") + self:T( { EventData } ) + self:T(self.lid .. " HandleEventShot") --local ShootingUnit = EventData.IniDCSUnit --local ShootingUnitName = EventData.IniDCSUnitName local ShootingWeapon = EventData.Weapon -- Identify the weapon fired @@ -498,14 +498,14 @@ do DetectedText = "true" end local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) - self:I( text ) + self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then -- get target data local targetdata = EventData.Weapon:getTarget() -- Identify target local targetcat = targetdata:getCategory() -- Identify category - self:I(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) + self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat))) local targetunit = nil if targetcat == Object.Category.UNIT then -- UNIT targetunit = UNIT:Find(targetdata) @@ -526,7 +526,7 @@ do targetgroupname = targetunitname end local text = string.format("%s Missile Target = %s", self.lid, tostring(targetgroupname)) - self:I( text ) + self:T( text ) local m = MESSAGE:New(text,10,"Info"):ToAllIf(self.debug) -- check if we or a SAM site are the target --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP @@ -534,7 +534,7 @@ do local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean -- if being shot at, find closest SHORADs to activate if shotatsams or shotatus then - self:I({shotatsams=shotatsams,shotatus=shotatus}) + self:T({shotatsams=shotatsams,shotatus=shotatus}) self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat) end end From e33de03522d7bdb4bfb4f2dc6306477bac159f33 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Jul 2021 19:17:39 +0200 Subject: [PATCH 357/382] CSAR - honor settings NM vs KM CTLD - documentation corrections UTILS - added functions to generate beacon frequency tables FM,VHF,UHF and valid laser codes for JTACs --- Moose Development/Moose/Ops/CSAR.lua | 31 ++--- Moose Development/Moose/Ops/CTLD.lua | 12 +- Moose Development/Moose/Utilities/Utils.lua | 145 ++++++++++++++++++++ 3 files changed, 161 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index d243475b0..aa2015393 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1428,12 +1428,8 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringLLDMS() elseif self.coordtype == 2 then -- MGRS _coordinatesText = _coordinate:ToStringMGRS() - elseif self.coordtype == 3 then -- Bullseye Imperial - local Settings = _SETTINGS:SetImperial() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) - else -- Bullseye Metric --(medevac.coordtype == 4) - local Settings = _SETTINGS:SetMetric() - _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4 or 3) + _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end return _coordinatesText @@ -1467,12 +1463,11 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - -- change distance to miles if self.coordtype < 4 local distancetext = "" - if self.coordtype < 4 then - distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + if _SETTINGS:IsImperial() then + distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else - distancetext = string.format("%.3fkm", _distance/1000.0) + distancetext = string.format("%.1fkm", _distance/1000.0) end table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end @@ -1542,10 +1537,10 @@ function CSAR:_SignalFlare(_unitName) local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1554,7 +1549,7 @@ function CSAR:_SignalFlare(_unitName) _coord:FlareRed(_clockDir) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) @@ -1593,10 +1588,10 @@ function CSAR:_Reqsmoke( _unitName ) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 - if self.coordtype < 4 then - _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) else - _distance = string.format("%.3fkm",_closest.distance) + _distance = string.format("%.1fkm",_closest.distance) end local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) @@ -1605,7 +1600,7 @@ function CSAR:_Reqsmoke( _unitName ) _coord:Smoke(color) else local disttext = "4.3nm" - if self.coordtype == 4 then + if _SETTINGS:IsMetric() then disttext = "8km" end self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4c9adf68d..f910323d7 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -81,8 +81,8 @@ CTLD_CARGO = { self.Name = Name or "none" -- #string self.Templates = Templates or {} -- #table self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum - self.HasBeenMoved = HasBeenMoved or false -- #booolean - self.LoadDirectly = LoadDirectly or false -- #booolean + self.HasBeenMoved = HasBeenMoved or false -- #boolean + self.LoadDirectly = LoadDirectly or false -- #boolean self.CratesNeeded = CratesNeeded or 0 -- #number self.Positionable = Positionable or nil -- Wrapper.Positionable#POSITIONABLE self.HasBeenDropped = Dropped or false --#boolean @@ -803,16 +803,10 @@ function CTLD:_GenerateUHFrequencies() end --- (Internal) Function to generate valid FM Frequencies --- @param #CTLD sel +-- @param #CTLD self function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - local _start = 220000000 - - while _start < 399000000 do - - _start = _start + 500000 - end for _first = 3, 7 do for _second = 0, 5 do diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ad8bb6291..609ace372 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1606,4 +1606,149 @@ function UTILS.IsLoadingDoorOpen( unit_name ) end -- nil return nil +end + +--- Function to generate valid FM frequencies in mHz for radio beacons (FM). +-- @return #table Table of frequencies. +function UTILS.GenerateFMFrequencies() + local FreeFMFrequencies = {} + for _first = 3, 7 do + for _second = 0, 5 do + for _third = 0, 9 do + local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit + table.insert(FreeFMFrequencies, _frequency) + end + end + end + return FreeFMFrequencies +end + +--- Function to generate valid VHF frequencies in kHz for radio beacons (FM). +-- @return #table VHFrequencies +function UTILS.GenerateVHFrequencies() + + -- known and sorted map-wise NDBs in kHz + local _skipFrequencies = { + 214,274,291.5,295,297.5, + 300.5,304,307,309.5,311,312,312.5,316, + 320,324,328,329,330,336,337, + 342,343,348,351,352,353,358, + 363,365,368,372.5,374, + 380,381,384,389,395,396, + 414,420,430,432,435,440,450,455,462,470,485, + 507,515,520,525,528,540,550,560,570,577,580, + 602,625,641,662,670,680,682,690, + 705,720,722,730,735,740,745,750,770,795, + 822,830,862,866, + 905,907,920,935,942,950,995, + 1000,1025,1030,1050,1065,1116,1175,1182,1210 + } + + local FreeVHFFrequencies = {} + + -- first range + local _start = 200000 + while _start < 400000 do + + -- skip existing NDB frequencies# + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- second range + _start = 400000 + while _start < 850000 do + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 10000 + end + + -- third range + _start = 850000 + while _start <= 999000 do -- adjusted for Gazelle + -- skip existing NDB frequencies + local _found = false + for _, value in pairs(_skipFrequencies) do + if value * 1000 == _start then + _found = true + break + end + end + if _found == false then + table.insert(FreeVHFFrequencies, _start) + end + _start = _start + 50000 + end + + return FreeVHFFrequencies +end + +--- Function to generate valid UHF Frequencies in mHz (AM). +-- @return #table UHF Frequencies +function UTILS.GenerateUHFrequencies() + + local FreeUHFFrequencies = {} + local _start = 220000000 + + while _start < 399000000 do + table.insert(FreeUHFFrequencies, _start) + _start = _start + 500000 + end + + return FreeUHFFrequencies +end + +--- Function to generate valid laser codes for JTAC. +-- @return #table Laser Codes. +function UTILS.GenerateLaserCodes() + local jtacGeneratedLaserCodes = {} + + -- helper function + local function ContainsDigit(_number, _numberToFind) + local _thisNumber = _number + local _thisDigit = 0 + while _thisNumber ~= 0 do + _thisDigit = _thisNumber % 10 + _thisNumber = math.floor(_thisNumber / 10) + if _thisDigit == _numberToFind then + return true + end + end + return false + end + + -- generate list of laser codes + local _code = 1111 + local _count = 1 + while _code < 1777 and _count < 30 do + while true do + _code = _code + 1 + if not self:_ContainsDigit(_code, 8) + and not ContainsDigit(_code, 9) + and not ContainsDigit(_code, 0) then + table.insert(jtacGeneratedLaserCodes, _code) + break + end + end + _count = _count + 1 + end + return jtacGeneratedLaserCodes end \ No newline at end of file From 433d1bbf57c7e2a384f472f11d3e6883c7598539 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 13 Jul 2021 17:50:44 +0200 Subject: [PATCH 358/382] MANTIS - Change logic to FSM, added functions CSAR - advanced options to name injected AI downed pilots CTLD - added Herc speed check --- Moose Development/Moose/Functional/Mantis.lua | 657 ++++++++++++------ Moose Development/Moose/Ops/CSAR.lua | 24 +- Moose Development/Moose/Ops/CTLD.lua | 36 +- 3 files changed, 493 insertions(+), 224 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 8ff110925..10f0bf1a6 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -191,7 +191,17 @@ MANTIS = { ShoradLink = false, ShoradTime = 600, ShoradActDistance = 15000, - UseEmOnOff = false, + UseEmOnOff = false, + TimeStamp = 0, + state2flag = false, +} + +--- Advanced state enumerator +-- @type MANTIS.AdvancedState +MANTIS.AdvancedState = { + GREEN = 0, + AMBER = 1, + RED = 2, } ----------------------------------------------------------------------- @@ -263,7 +273,10 @@ do self.ShoradLink = false self.ShoradTime = 600 self.ShoradActDistance = 15000 - -- TODO: add emissions on/off when available .... in 2 weeks + self.TimeStamp = timer.getAbsTime() + self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins + self.state2flag = false + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -279,7 +292,7 @@ do end -- Inherit everything from BASE class. - local self = BASE:Inherit(self, BASE:New()) -- #MANTIS + local self = BASE:Inherit(self, FSM:New()) -- #MANTIS -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -310,27 +323,122 @@ do end -- @field #string version - self.version="0.4.2" + self.version="0.5.1" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) - return self - end + --- FSM Functions --- + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- MANTIS status update. + self:AddTransition("*", "Relocating", "*") -- MANTIS HQ and EWR are relocating. + self:AddTransition("*", "GreenState", "*") -- MANTIS A SAM switching to GREEN state. + self:AddTransition("*", "RedState", "*") -- MANTIS A SAM switching to RED state. + self:AddTransition("*", "AdvStateChange", "*") -- MANTIS advanced mode state change. + self:AddTransition("*", "ShoradActivated", "*") -- MANTIS woke up a connected SHORAD. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] Start + -- @param #MANTIS self + + --- Triggers the FSM event "Start" after a delay. Starts the MANTIS. Initializes parameters and starts event handlers. + -- @function [parent=#MANTIS] __Start + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the MANTIS and all its event handlers. + -- @param #MANTIS self + + --- Triggers the FSM event "Stop" after a delay. Stops the MANTIS and all its event handlers. + -- @function [parent=#MANTIS] __Stop + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#MANTIS] Status + -- @param #MANTIS self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#MANTIS] __Status + -- @param #MANTIS self + -- @param #number delay Delay in seconds. + + --- On After "Relocating" event. HQ and/or EWR moved. + -- @function [parent=#MANTIS] OnAfterRelocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + + --- On After "GreenState" event. A SAM group was switched to GREEN alert. + -- @function [parent=#MANTIS] OnAfterGreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "RedState" event. A SAM group was switched to RED alert. + -- @function [parent=#MANTIS] OnAfterRedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + + --- On After "AdvStateChange" event. Advanced state changed, influencing detection speed. + -- @function [parent=#MANTIS] OnAfterAdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + + --- On After "ShoradActivated" event. Mantis has activated a SHORAD. + -- @function [parent=#MANTIS] OnAfterShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + + return self + end ----------------------------------------------------------------------- -- MANTIS helper functions ----------------------------------------------------------------------- - --- [internal] Function to get the self.SAM_Table + --- [Internal] Function to get the self.SAM_Table -- @param #MANTIS self -- @return #table table function MANTIS:_GetSAMTable() + self:T(self.lid .. "GetSAMTable") return self.SAM_Table end - --- [internal] Function to set the self.SAM_Table + --- [Internal] Function to set the self.SAM_Table -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_SetSAMTable(table) + self:T(self.lid .. "SetSAMTable") self.SAM_Table = table return self end @@ -339,41 +447,50 @@ do -- @param #MANTIS self -- @param #number radius Radius upon which detected objects will be grouped function MANTIS:SetEWRGrouping(radius) + self:T(self.lid .. "SetEWRGrouping") local radius = radius or 5000 self.grouping = radius + return self end --- Function to set the detection radius of the EWR in meters -- @param #MANTIS self -- @param #number radius Radius of the EWR detection zone function MANTIS:SetEWRRange(radius) + self:T(self.lid .. "SetEWRRange") local radius = radius or 80000 self.acceptrange = radius + return self end --- Function to set switch-on/off zone for the SAM sites in meters -- @param #MANTIS self -- @param #number radius Radius of the firing zone function MANTIS:SetSAMRadius(radius) + self:T(self.lid .. "SetSAMRadius") local radius = radius or 25000 self.checkradius = radius + return self end --- Function to set SAM firing engage range, 0-100 percent, e.g. 75 -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetSAMRange(range) + self:T(self.lid .. "SetSAMRange") local range = range or 75 if range < 0 or range > 100 then range = 75 end self.engagerange = range + return self end --- Function to set a new SAM firing engage range, use this method to adjust range while running MANTIS, e.g. for different setups day and night -- @param #MANTIS self -- @param #number range Percent of the max fire range function MANTIS:SetNewSAMRangeWhileRunning(range) + self:T(self.lid .. "SetNewSAMRangeWhileRunning") local range = range or 75 if range < 0 or range > 100 then range = 75 @@ -381,20 +498,32 @@ do self.engagerange = range self:_RefreshSAMTable() self.mysead.EngagementRange = range + return self end --- Function to set switch-on/off the debug state -- @param #MANTIS self -- @param #boolean onoff Set true to switch on function MANTIS:Debug(onoff) + self:T(self.lid .. "SetDebug") local onoff = onoff or false self.debug = onoff + if onoff then + -- Debug trace. + BASE:TraceOn() + BASE:TraceClass("MANTIS") + BASE:TraceLevel(1) + else + BASE:TraceOff() + end + return self end --- Function to get the HQ object for further use -- @param #MANTIS self -- @return Wrapper.GROUP#GROUP The HQ #GROUP object or *nil* if it doesn't exist function MANTIS:GetCommandCenter() + self:T(self.lid .. "GetCommandCenter") if self.HQ_CC then return self.HQ_CC else @@ -406,26 +535,31 @@ do -- @param #MANTIS self -- @param #string prefix Name of the AWACS group in the mission editor function MANTIS:SetAwacs(prefix) + self:T(self.lid .. "SetAwacs") if prefix ~= nil then if type(prefix) == "string" then self.AWACS_Prefix = prefix self.advAwacs = true end end + return self end --- Function to set AWACS detection range. Defaults to 250.000m (250km) - use **before** starting your Mantis! -- @param #MANTIS self -- @param #number range Detection range of the AWACS group function MANTIS:SetAwacsRange(range) - local range = range or 250000 - self.awacsrange = range + self:T(self.lid .. "SetAwacsRange") + local range = range or 250000 + self.awacsrange = range + return self end --- Function to set the HQ object for further use -- @param #MANTIS self -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ function MANTIS:SetCommandCenter(group) + self:T(self.lid .. "SetCommandCenter") local group = group or nil if group ~= nil then if type(group) == "string" then @@ -436,14 +570,17 @@ do self.HQ_Template_CC = group:GetName() end end + return self end --- Function to set the detection interval -- @param #MANTIS self -- @param #number interval The interval in seconds function MANTIS:SetDetectInterval(interval) + self:T(self.lid .. "SetDetectInterval") local interval = interval or 30 self.detectinterval = interval + return self end --- Function to set Advanded Mode @@ -453,7 +590,8 @@ do -- @usage Advanced mode will *decrease* reactivity of MANTIS, if HQ and/or EWR network dies. Set SAMs to RED state if both are dead. Requires usage of an **HQ** object and the **dynamic** option. -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) - self:F({onoff, ratio}) + self:T(self.lid .. "SetAdvancedMode") + self:T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -461,53 +599,58 @@ do self.advanced = true self.adv_state = 0 self.Adv_EWR_Group = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterStart() - env.info(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) + self:I(string.format("***** Starting Advanced Mode MANTIS Version %s *****", self.version)) else local text = self.lid.." Advanced Mode requires a HQ and dynamic to be set. Revisit your MANTIS:New() statement to add both." local m= MESSAGE:New(text,10,"MANTIS",true):ToAll() - BASE:E(text) + self:E(text) end + return self end --- Set using Emissions on/off instead of changing alarm state -- @param #MANTIS self -- @param #boolean switch Decide if we are changing alarm state or Emission state function MANTIS:SetUsingEmOnOff(switch) + self:T(self.lid .. "SetUsingEmOnOff") self.UseEmOnOff = switch or false + return self end --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false function MANTIS:_CheckHQState() + self:T(self.lid .. "CheckHQState") local text = self.lid.." Checking HQ State" - self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local hq = self.HQ_Template_CC local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - env.info(self.lid.." HQ is alive!") + self:T(self.lid.." HQ is alive!") return true else - env.info(self.lid.." HQ is dead!") + self:T(self.lid.." HQ is dead!") return false end end - end + end + return self end --- [Internal] Function to check if EWR is (at least partially) alive -- @param #MANTIS self -- @return #boolean True if EWR is alive, else false function MANTIS:_CheckEWRState() + self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:F(text) + self:T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check if self.advanced then local EWR_Group = self.Adv_EWR_Group @@ -521,24 +664,26 @@ do end end end - env.info(self.lid..string.format(" No of EWR alive is %d", nalive)) + self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else return false end - end + end + return self end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self -- @return #number Newly calculated interval -- @return #number Previous state for tracking 0, 1, or 2 - function MANTIS:_CheckAdvState() - local text = self.lid.." Checking Advanced State" - self:F(text) + function MANTIS:_CalcAdvState() + self:T(self.lid .. "CalcAdvState") + local text = self.lid.." Calculating Advanced State" + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -557,9 +702,9 @@ do ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:F(text) + self:T(text) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end return newinterval, currstate end @@ -568,7 +713,8 @@ do -- @param #boolean hq If true, will relocate HQ object -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) - self:F({hq, ewr}) + self:T(self.lid .. "SetAutoRelocate") + self:T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then @@ -576,23 +722,25 @@ do self.autorelocateunits = { HQ = hqrel, EWR = ewrel } self:T({self.autorelocate, self.autorelocateunits}) end + return self end --- [Internal] Function to execute the relocation -- @param #MANTIS self function MANTIS:_RelocateGroups() - self:T(self.lid.." Relocating Groups") + self:T(self.lid .. "RelocateGroups") local text = self.lid.." Relocating Groups" local m= MESSAGE:New(text,10,"MANTIS",true):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end if self.autorelocate then -- relocate HQ - if self.autorelocateunits.HQ and self.HQ_CC then --only relocate if HQ exists + local HQGroup = self.HQ_CC + if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC self:T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" - local m= MESSAGE:New(text,10,"MANTIS"):ToAll() - _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) + --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() + _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) end --relocate EWR -- TODO: maybe dependent on AlarmState? Observed: SA11 SR only relocates if no objects in reach @@ -601,26 +749,27 @@ do local EWR_GRP = SET_GROUP:New():FilterPrefixes(self.EWR_Templates_Prefix):FilterCoalitions(self.Coalition):FilterOnce() local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do - if _grp:IsGround() then + if _grp:IsAlive() and _grp:IsGround() then self:T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(text) end + if self.verbose then self:I(text) end _grp:RelocateGroundRandomInRadius(20,500,true,true) end end end end + return self end - --- (Internal) Function to check if any object is in the given SAM zone + --- [Internal] Function to check if any object is in the given SAM zone -- @param #MANTIS self -- @param #table dectset Table of coordinates of detected items - -- @param samcoordinate Core.Point#COORDINATE Coordinate object. + -- @param Core.Point#COORDINATE samcoordinate Coordinate object. -- @return #boolean True if in any zone, else false -- @return #number Distance Target distance in meters or zero when no object is in zone function MANTIS:CheckObjectInZone(dectset, samcoordinate) - self:F(self.lid.."CheckObjectInZone Called") + self:T(self.lid.."CheckObjectInZone") -- check if non of the coordinate is in the given defense zone local radius = self.checkradius local set = dectset @@ -632,7 +781,7 @@ do local targetdistance = samcoordinate:DistanceFromPointVec2(coord) local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end + if self.verbose then self:I(self.lid..text) end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -641,11 +790,11 @@ do return false, 0 end - --- (Internal) Function to start the detection via EWR groups + --- [Internal] Function to start the detection via EWR groups -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartDetection() - self:F(self.lid.."Starting Detection") + self:T(self.lid.."Starting Detection") -- start detection local groupset = self.EWR_Group @@ -653,14 +802,14 @@ do local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISdetection:SetAcceptRange(acceptrange) - _MANTISdetection:SetRefreshTimeInterval(interval) - _MANTISdetection:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISdetection = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISdetection:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISdetection:SetAcceptRange(acceptrange) + MANTISdetection:SetRefreshTimeInterval(interval) + MANTISdetection:Start() - function _MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISdetection:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -669,14 +818,14 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISdetection + return MANTISdetection end - --- (Internal) Function to start the detection via AWACS if defined as separate + --- [Internal] Function to start the detection via AWACS if defined as separate -- @param #MANTIS self -- @return Functional.Detection #DETECTION_AREAS The running detection set function MANTIS:StartAwacsDetection() - self:F(self.lid.."Starting Awacs Detection") + self:T(self.lid.."Starting Awacs Detection") -- start detection local group = self.AWACS_Prefix @@ -685,14 +834,14 @@ do --local acceptrange = self.acceptrange or 80000 local interval = self.detectinterval or 60 - --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [internal] The MANTIS detection object - _MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[internal] Grouping detected objects to 5000m zones - _MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) - _MANTISAwacs:SetAcceptRange(self.awacsrange) --250km - _MANTISAwacs:SetRefreshTimeInterval(interval) - _MANTISAwacs:Start() + --@param Functional.Detection #DETECTION_AREAS _MANTISdetection [Internal] The MANTIS detection object + local MANTISAwacs = DETECTION_AREAS:New( groupset, grouping ) --[Internal] Grouping detected objects to 5000m zones + MANTISAwacs:FilterCategories({ Unit.Category.AIRPLANE, Unit.Category.HELICOPTER }) + MANTISAwacs:SetAcceptRange(self.awacsrange) --250km + MANTISAwacs:SetRefreshTimeInterval(interval) + MANTISAwacs:Start() - function _MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) + function MANTISAwacs:OnAfterDetectedItem(From,Event,To,DetectedItem) --BASE:I( { From, Event, To, DetectedItem }) local debug = false if DetectedItem.IsDetected and debug then @@ -701,15 +850,15 @@ do local m = MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) end end - return _MANTISAwacs + return MANTISAwacs end - --- (Internal) Function to set the SAM start state + --- [Internal] Function to set the SAM start state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:SetSAMStartState() -- DONE: if using dynamic filtering, update SAM_Table and the (active) SEAD groups, pull req #1405/#1406 - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."Setting SAM Start States") -- get SAM Group local SAM_SET = self.SAM_Group local SAM_Grps = SAM_SET.Set --table of objects @@ -742,11 +891,11 @@ do return self end - --- (Internal) Function to update SAM table and SEAD state + --- [Internal] Function to update SAM table and SEAD state -- @param #MANTIS self -- @return #MANTIS self function MANTIS:_RefreshSAMTable() - self:F(self.lid.."Setting SAM Start States") + self:T(self.lid.."RefreshSAMTable") -- Requires SEAD 0.2.2 or better -- get SAM Group local SAM_SET = self.SAM_Group @@ -779,6 +928,7 @@ do -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up function MANTIS:AddShorad(Shorad,Shoradtime) + self:T(self.lid.."AddShorad") local Shorad = Shorad or nil local ShoradTime = Shoradtime or 600 local ShoradLink = true @@ -787,184 +937,279 @@ do self.Shorad = Shorad --#SHORAD self.ShoradTime = Shoradtime -- #number end + return self end --- Function to unlink #MANTIS from a #SHORAD installation -- @param #MANTIS self function MANTIS:RemoveShorad() + self:T(self.lid.."RemoveShorad") self.ShoradLink = false + return self end ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- - --- Function to set the SAM start state + --- [Internal] Check detection function + -- @param #MANTIS self + -- @param Functional.Detection#DETECTION_AREAS detection Detection object + -- @return #MANTIS self + function MANTIS:_Check(detection) + self:T(self.lid .. "Check") + --get detected set + local detset = detection:GetDetectedItemCoordinates() + self:T("Check:", {detset}) + -- randomly update SAM Table + local rand = math.random(1,100) + if rand > 65 then -- 1/3 of cases + self:_RefreshSAMTable() + end + -- switch SAMs on/off if (n)one of the detected groups is inside their reach + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local samcoordinate = _data[2] + local name = _data[1] + local samgroup = GROUP:FindByName(name) + local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) + if IsInZone then --check any target in zone + if samgroup:IsAlive() then + -- switch on SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + --samgroup:SetAIOn() + samgroup:EnableEmission(true) + end + samgroup:OptionAlarmStateRed() + self:__RedState(1,samgroup) + -- link in to SHORAD if available + -- DONE: Test integration fully + if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(name, radius, ontime) + self:__ShoradActivated(1,name, radius, ontime) + end + -- debug output + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + else + if samgroup:IsAlive() then + -- switch off SAM + if self.UseEmOnOff then + -- TODO: add emissions on/off + samgroup:EnableEmission(false) + self:__GreenState(1,samgroup) + --samgroup:SetAIOff() + else + samgroup:OptionAlarmStateGreen() + self:__GreenState(1,samgroup) + end + --samgroup:OptionROEWeaponFree() + --samgroup:SetAIOn() + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end --end alive + end --end check + end --for for loop + return self + end + + --- [Internal] Relocation relay function -- @param #MANTIS self -- @return #MANTIS self - function MANTIS:Start() - self:F(self.lid.."Starting MANTIS") - self:SetSAMStartState() - self.Detection = self:StartDetection() - if self.advAwacs then - self.AWACS_Detection = self:StartAwacsDetection() - end - -- detection function - local function check(detection) - --get detected set - local detset = detection:GetDetectedItemCoordinates() - self:F("Check:", {detset}) - -- randomly update SAM Table - local rand = math.random(1,100) - if rand > 65 then -- 1/3 of cases - self:_RefreshSAMTable() - end - -- switch SAMs on/off if (n)one of the detected groups is inside their reach - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local samcoordinate = _data[2] - local name = _data[1] - local samgroup = GROUP:FindByName(name) - local IsInZone, Distance = self:CheckObjectInZone(detset, samcoordinate) - if IsInZone then --check any target in zone + function MANTIS:_Relocate() + self:T(self.lid .. "Relocate") + self:_RelocateGroups() + return self + end + + --- [Internal] Check advanced state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckAdvState() + self:T(self.lid .. "CheckAdvSate") + local interval, oldstate = self:_CalcAdvState() + local newstate = self.adv_state + if newstate ~= oldstate then + -- deal with new state + self:__AdvStateChange(1,oldstate,newstate,interval) + if newstate == 2 then + -- switch alarm state RED + self.state2flag = true + local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates + for _,_data in pairs (samset) do + local name = _data[1] + local samgroup = GROUP:FindByName(name) if samgroup:IsAlive() then - -- switch on SAM if self.UseEmOnOff then -- TODO: add emissions on/off --samgroup:SetAIOn() samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - -- link in to SHORAD if available - -- DONE: Test integration fully - if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early - local Shorad = self.Shorad - local radius = self.checkradius - local ontime = self.ShoradTime - Shorad:WakeUpShorad(name, radius, ontime) - end - -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - else - if samgroup:IsAlive() then - -- switch off SAM - if self.UseEmOnOff then - -- TODO: add emissions on/off - samgroup:EnableEmission(false) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then env.info(self.lid..text) end - end --end alive - end --end check - end --for for loop - end --end function - -- relocation relay function - local function relocate() - self:_RelocateGroups() - end - -- check advanced state - local function checkadvstate() - local interval, oldstate = self:_CheckAdvState() - local newstate = self.adv_state - if newstate ~= oldstate then - -- deal with new state - if newstate == 2 then - -- switch alarm state RED - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end -- stop Awacs timer - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end -- stop timer - local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates - for _,_data in pairs (samset) do - local name = _data[1] - local samgroup = GROUP:FindByName(name) - if samgroup:IsAlive() then - if self.UseEmOnOff then - -- TODO: add emissions on/off - --samgroup:SetAIOn() - samgroup:EnableEmission(true) - end - samgroup:OptionAlarmStateRed() - end -- end alive - end -- end for loop - elseif newstate <= 1 then - -- change MantisTimer to slow down or speed up - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() - self.MantisTimer.isrunning = false - end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() - self.MantisATimer.isrunning = false - end - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - end - end -- end newstate vs oldstate - end - -- timers to run the system - local interval = self.detectinterval - self.MantisTimer = TIMER:New(check,self.Detection) - self.MantisTimer:Start(5,interval,nil) - self.MantisTimer.isrunning = true - -- Awacs timer + end -- end alive + end -- end for loop + elseif newstate <= 1 then + -- change MantisTimer to slow down or speed up + self.detectinterval = interval + self.state2flag = false + end + end -- end newstate vs oldstate + return self + end + + --- [Internal] Function to set start state + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:T(self.lid.."Starting MANTIS") + self:SetSAMStartState() + self.Detection = self:StartDetection() if self.advAwacs then - self.MantisATimer = TIMER:New(check,self.AWACS_Detection) - self.MantisATimer:Start(15,interval,nil) - self.MantisATimer.isrunning = true - end - -- timer to relocate HQ and EWR - if self.autorelocate then - local relointerval = math.random(1800,3600) -- random between 30 and 60 mins - self.MantisReloTimer = TIMER:New(relocate) - self.MantisReloTimer:Start(relointerval,relointerval,nil) - end - -- timer for advanced state check - if self.advanced then - self.MantisAdvTimer = TIMER:New(checkadvstate) - self.MantisAdvTimer:Start(30,interval*5,nil) + self.AWACS_Detection = self:StartAwacsDetection() end + self:__Status(self.detectinterval) return self end - --- Function to stop MANTIS + --- [Internal] Before status function for MANTIS -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State -- @return #MANTIS self - function MANTIS:Stop() - if self.MantisTimer.isrunning then - self.MantisTimer:Stop() + function MANTIS:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- check detection + if not self.state2flag then + self:_Check(self.Detection) end - if self.MantisATimer.isrunning then - self.MantisATimer:Stop() + + -- check Awacs + if self.advAwacs and not self.state2flag then + self:_Check(self.AWACS_Detection) end + + -- relocate HQ and EWR if self.autorelocate then - self.MantisReloTimer:Stop() + local relointerval = self.relointerval + local thistime = timer.getAbsTime() + local timepassed = thistime - self.TimeStamp + + local halfintv = math.floor(timepassed / relointerval) + + --self:T({timepassed=timepassed, halfintv=halfintv}) + + if halfintv >= 1 then + self.TimeStamp = timer.getAbsTime() + self:_Relocate() + self:__Relocating(1) + end end + + -- timer for advanced state check if self.advanced then - self.MantisAdvTimer:Stop() + self:_CheckAdvState() end - return self + + return self + end + + --- [Internal] Status function for MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStatus(From,Event,To) + self:T({From, Event, To}) + local interval = self.detectinterval * -1 + self:__Status(interval) + return self end + --- [Internal] Function to stop MANTIS + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterStop(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event Relocating + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @return #MANTIS self + function MANTIS:onafterRelocating(From, Event, To) + self:T({From, Event, To}) + return self + end + + --- [Internal] Function triggered by Event GreenState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterGreenState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event RedState + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param Wrapper.Group#GROUP Group The GROUP object whose state was changed + -- @return #MANTIS self + function MANTIS:onafterRedState(From, Event, To, Group) + self:T({From, Event, To, Group}) + return self + end + + --- [Internal] Function triggered by Event AdvStateChange + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #number Oldstate Old state - 0 = green, 1 = amber, 2 = red + -- @param #number Newstate New state - 0 = green, 1 = amber, 2 = red + -- @param #number Interval Calculated detection interval based on state and advanced feature setting + -- @return #MANTIS self + function MANTIS:onafterAdvStateChange(From, Event, To, Oldstate, Newstate, Interval) + self:T({From, Event, To, Oldstate, Newstate, Interval}) + return self + end + + --- [Internal] Function triggered by Event ShoradActivated + -- @param #MANTIS self + -- @param #string From The From State + -- @param #string Event The Event + -- @param #string To The To State + -- @param #string Name Name of the GROUP which SHORAD shall protect + -- @param #number Radius Radius around the named group to find SHORAD groups + -- @param #number Ontime Seconds the SHORAD will stay active + function MANTIS:onafterShoradActivated(From, Event, To, Name, Radius, Ontime) + self:T({From, Event, To, Name, Radius, Ontime}) + return self + end end ----------------------------------------------------------------------- -- MANTIS end diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index aa2015393..c3dda1dff 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -662,7 +662,9 @@ end -- @param #string _description (optional) Description. -- @param #boolean _randomPoint (optional) Random yes or no. -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -671,7 +673,9 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ return end - local _description = _description or "Unknown" + local _description = _description or "PoW" + local unitname = unitname or "Old Rusty" + local typename = typename or "Phantom II" local pos = {} if _randomPoint then @@ -690,7 +694,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, "PoW", _description, nil, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description) return self end @@ -702,12 +706,14 @@ end -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. +-- @param #string unitname (optional) Name of the lost unit. +-- @param #string typename (optional) Type of plane. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition --- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage) +-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) return self end @@ -1199,7 +1205,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time <= 0 or _distance < self.loadDistance then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, bugger!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.landedStatus[_lookupKeyHeli] = nil @@ -1211,7 +1217,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG else if (_distance < self.loadDistance) then if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, honk!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1252,7 +1258,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) else if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then - self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in, noob!", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true) return true else self.hoverStatus[_lookupKeyHeli] = nil diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f910323d7..8db791730 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -410,6 +410,7 @@ do -- my_ctld.enableHercules = true -- my_ctld.HercMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft -- my_ctld.HercMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.HercMaxSpeed = 77 -- 77mps or 270 kph or 150 kn -- -- Also, the following options need to be set to `true`: -- @@ -478,7 +479,7 @@ CTLD = { -- @field #table vhfbeacon Beacon info as #CTLD.ZoneBeacon --- Zone Type Info. --- @type CTLD. +-- @type CTLD.CargoZoneType CTLD.CargoZoneType = { LOAD = "load", DROP = "drop", @@ -651,6 +652,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.enableHercules = false self.HercMinAngels = 165 -- for troop/cargo drop via chute self.HercMaxAngels = 2000 -- for troop/cargo drop via chute + self.HercMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -2283,10 +2285,12 @@ end local aheight = uheight - gheight -- height above ground local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m - local mspeed = 2 -- 2 m/s - -- TODO:Add speed test for Herc, should not be above 280kph/150kn - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) - if (aheight <= maxh) and (aheight >= minh) then + local maxspeed = self.HercMaxSpeed -- 77 mps + -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + local kmspeed = uspeed * 3.6 + local knspeed = kmspeed / 1.86 + self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) + if (aheight <= maxh) and (aheight >= minh) and (uspeed <= maxspeed) then -- yep within parameters outcome = true end @@ -2302,7 +2306,14 @@ end local inhover = self:IsCorrectHover(Unit) local htxt = "true" if not inhover then htxt = "false" end - local text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + local text = "" + if _SETTINGS:IsMetric() then + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 2mps \n - In parameter: %s", self.minimumHoverHeight, self.maximumHoverHeight, htxt) + else + local minheight = UTILS.MetersToFeet(self.minimumHoverHeight) + local maxheight = UTILS.MetersToFeet(self.maximumHoverHeight) + text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) + end self:_SendMessage(text, 10, false, Group) --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self @@ -2316,9 +2327,16 @@ end local inhover = self:IsCorrectFlightParameters(Unit) local htxt = "true" if not inhover then htxt = "false" end - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) - local text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + local text = "" + if _SETTINGS:IsImperial() then + local minheight = UTILS.MetersToFeet(self.HercMinAngels) + local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + text = string.format("Flight parameters (airdrop):\n - Min height %dft \n - Max height %dft \n - In parameter: %s", minheight, maxheight, htxt) + else + local minheight = self.HercMinAngels + local maxheight = self.HercMaxAngels + text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) + end self:_SendMessage(text, 10, false, Group) --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self From 1ac40684defbc1f6a65b21a575017bc74c64a10b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Jul 2021 08:39:54 +0200 Subject: [PATCH 359/382] Added option to force description on injected pilots for scripting --- Moose Development/Moose/Ops/CSAR.lua | 39 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index c3dda1dff..49a357e34 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -609,7 +609,8 @@ end -- @param #number _freq Frequency -- @param #boolean noMessage -- @param #string _description Description -function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) +-- @param #boolean forcedesc Use the description only for the pilot track entry +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description, forcedesc ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) @@ -622,11 +623,10 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq) - local _typeName = _typeName or "PoW" + local _typeName = _typeName or "Pilot" if not noMessage then self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, 10) - --local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) end if _freq then @@ -635,15 +635,14 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:_AddSpecialOptions(_spawnedGroup) - local _text = " " - if _playerName ~= nil then - _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName - elseif _typeName ~= nil then - _text = "AI Pilot of " .. _unitName .. " - " .. _typeName - else - _text = _description - end - + local _text = _description + if not forcedesc then + if _playerName ~= nil then + _text = "Pilot " .. _playerName + elseif _unitName ~= nil then + _text = "AI Pilot of " .. _unitName + end + end self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias @@ -664,7 +663,8 @@ end -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. -- @param #string unitname (optional) Name of the lost unit. -- @param #string typename (optional) Type of plane. -function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename) +-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc) self:T(self.lid .. " _SpawnCsarAtZone") local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position @@ -694,7 +694,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ _country = country.id.UN_PEACEKEEPERS end - self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description) + self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, freq, _nomessage, _description, forcedesc) return self end @@ -706,14 +706,15 @@ end -- @param #string Description (optional) Description. -- @param #boolean RandomPoint (optional) Random yes or no. -- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR. --- @param #string unitname (optional) Name of the lost unit. --- @param #string typename (optional) Type of plane. --- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys works, they can do this like so: +-- @param #string Unitname (optional) Name of the lost unit. +-- @param #string Typename (optional) Type of plane. +-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. +-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: -- -- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition -- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Wagner", true, false, "Charly-1-1", "F5E" ) -function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) - self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename) +function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) + self:_SpawnCsarAtZone(Zone, Coalition, Description, RandomPoint, Nomessage, Unitname, Typename, Forcedesc) return self end From 93a8086ff6b3bccefb48efd7ed68ef177dd6f81a Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 14 Jul 2021 15:37:57 +0200 Subject: [PATCH 360/382] Update Mantis.lua (#1569) Added state tracker so that Red/Green Events only get triggered when a state actually changes. --- Moose Development/Moose/Functional/Mantis.lua | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 10f0bf1a6..1186378e1 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -194,6 +194,7 @@ MANTIS = { UseEmOnOff = false, TimeStamp = 0, state2flag = false, + SamStateTracker = {}, } --- Advanced state enumerator @@ -276,6 +277,7 @@ do self.TimeStamp = timer.getAbsTime() self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false + self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode if EmOnOff then if EmOnOff == false then @@ -323,7 +325,7 @@ do end -- @field #string version - self.version="0.5.1" + self.version="0.5.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -867,7 +869,7 @@ do local engagerange = self.engagerange -- firing range in % of max --cycle through groups and set alarm state etc for _i,_group in pairs (SAM_Grps) do - local group = _group + local group = _group -- Wrapper.Group#GROUP -- TODO: add emissions on/off if self.UseEmOnOff then group:EnableEmission(false) @@ -876,11 +878,12 @@ do group:OptionAlarmStateGreen() -- AI off end group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --default engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) table.insert( SEAD_Grps, grpname ) + self.SamStateTracker[grpname] = "GREEN" end end self.SAM_Table = SAM_Tbl @@ -907,7 +910,7 @@ do for _i,_group in pairs (SAM_Grps) do local group = _group group:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION,engagerange) --engagement will be 75% of firing range - if group:IsGround() then + if group:IsGround() and group:IsAlive() then local grpname = group:GetName() local grpcoord = group:GetCoordinate() table.insert( SAM_Tbl, {grpname, grpcoord}) -- make the table lighter, as I don't really use the zone here @@ -982,7 +985,10 @@ do samgroup:EnableEmission(true) end samgroup:OptionAlarmStateRed() - self:__RedState(1,samgroup) + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,samgroup) + self.SamStateTracker[name] = "RED" + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and Distance < self.ShoradActDistance then -- don't give SHORAD position away too early @@ -1001,16 +1007,13 @@ do if samgroup:IsAlive() then -- switch off SAM if self.UseEmOnOff then - -- TODO: add emissions on/off samgroup:EnableEmission(false) - self:__GreenState(1,samgroup) - --samgroup:SetAIOff() - else - samgroup:OptionAlarmStateGreen() - self:__GreenState(1,samgroup) end - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() + samgroup:OptionAlarmStateGreen() + if self.SamStateTracker[name] ~= "GREEN" then + self:__GreenState(1,samgroup) + self.SamStateTracker[name] = "GREEN" + end local text = string.format("SAM %s switched to alarm state GREEN!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(self.lid..text) end From 835041e5f6e225eef4ffc66d19bf86f3f842016e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 14 Jul 2021 21:00:34 +0200 Subject: [PATCH 361/382] OPS --- Moose Development/Moose/Ops/ArmyGroup.lua | 25 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 79 ++++------ Moose Development/Moose/Ops/NavyGroup.lua | 21 +-- Moose Development/Moose/Ops/OpsGroup.lua | 149 +++++++++++++------ Moose Development/Moose/Ops/OpsTransport.lua | 37 +++-- Moose Development/Moose/Wrapper/Group.lua | 10 +- 6 files changed, 190 insertions(+), 131 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 301cd48f1..9506a270b 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -345,10 +345,10 @@ end function ARMYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -567,16 +567,19 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if not self.option.Formation then self.option.Formation=self.optionDefault.Formation end + + -- Update route. + if #self.waypoints>1 then + self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) + else + self:FullStop() + end + + -- Update status. + self:__Status(-0.1) end - -- Update route. - if #self.waypoints>1 then - self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) - else - self:FullStop() - end - end --- On before "UpdateRoute" event. @@ -1117,7 +1120,7 @@ function ARMYGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -1177,7 +1180,7 @@ function ARMYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 19d6d061d..5b15572ee 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2191,7 +2191,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) end -- Group is waiting. - if self.Twaiting then + if self:IsWaiting() then self:T(self.lid.."Waiting! Group NOT done...") return end @@ -2531,37 +2531,31 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. --- @param #number Altitude Altitude in feet. Default 10000 ft. --- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). +-- @param #number Altitude Altitude in feet. Default 10,000 ft for airplanes and 1,000 feet for helos. +-- @param #number Speed Speed in knots. Default 250 kts for airplanes and 20 kts for helos. +function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) local allowed=true local Tsuspend=nil - -- Check if there are remaining tasks. - local Ntot,Nsched, Nwp=self:CountRemainingTasks() - + -- Check for a current task. if self.taskcurrent>0 then - self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 10 sec.")) - Tsuspend=-10 + self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 allowed=false end - - if Nsched>0 then - --self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) - --Tsuspend=-10 - --allowed=false - end - - if Nwp>0 then - --self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> WAIT event is suspended for 10 sec.", Nwp)) - --Tsuspend=-10 - --allowed=false + + -- Check for a current transport assignment. + if self.cargoTransport then + self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false end + -- Call wait again. if Tsuspend and not allowed then - self:__Wait(Tsuspend, Coord, Altitude, Speed) + self:__Wait(Tsuspend, Duration, Altitude, Speed) end return allowed @@ -2574,14 +2568,18 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). --- @param Core.Point#COORDINATE Coord Coordinate where to orbit. Default current position. --- @param #number Altitude Altitude in feet. Default 10000 ft. --- @param #number Speed Speed in knots. Default 250 kts. -function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Coord, Altitude, Speed) +-- @param #number Altitude Altitude in feet. Default 10,000 ft for airplanes and 1,000 feet for helos. +-- @param #number Speed Speed in knots. Default 250 kts for airplanes and 20 kts for helos. +function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) - Coord=Coord or self.group:GetCoordinate() + -- Group will orbit at its current position. + local Coord=self.group:GetCoordinate() + + -- Set altitude: 1000 ft for helos and 10,000 ft for panes. Altitude=Altitude or (self.isHelo and 1000 or 10000) - Speed=Speed or (self.isHelo and 80 or 250) + + -- Set speed. + Speed=Speed or (self.isHelo and 20 or 250) -- Debug message. local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) @@ -3009,7 +3007,7 @@ function FLIGHTGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -3106,7 +3104,7 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) @@ -3606,27 +3604,6 @@ function FLIGHTGROUP:AddWaypointLanding(Airbase, Speed, AfterWaypointWithID, Alt end - ---- Check if a unit is an element of the flightgroup. --- @param #FLIGHTGROUP self --- @param #string unitname Name of unit. --- @return #boolean If true, unit is element of the flight group or false if otherwise. -function FLIGHTGROUP:_IsElement(unitname) - - for _,_element in pairs(self.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP.Element - - if element.name==unitname then - return true - end - - end - - return false -end - - - --- Set parking spot of element. -- @param #FLIGHTGROUP self -- @param Ops.OpsGroup#OPSGROUP.Element Element The element. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 5284cec36..204792dcf 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -693,16 +693,19 @@ function NAVYGROUP:onafterSpawned(From, Event, To) else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end + + -- Update route. + if #self.waypoints>1 then + self:Cruise() + else + self:FullStop() + end + + -- Update status. + self:__Status(-0.1) end - -- Update route. - if #self.waypoints>1 then - self:Cruise() - else - self:FullStop() - end - end --- On before "UpdateRoute" event. @@ -1132,7 +1135,7 @@ function NAVYGROUP:_InitGroup() -- First check if group was already initialized. if self.groupinitialized then - self:E(self.lid.."WARNING: Group was already initialized!") + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end @@ -1197,7 +1200,7 @@ function NAVYGROUP:_InitGroup() self.actype=units[1]:GetTypeName() -- Debug info. - if self.verbose>=0 then + if self.verbose>=1 then local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 0d686919d..69b5c1d01 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -373,6 +373,7 @@ OPSGROUP.TaskType={ -- @field #number MissilesAS Amount of anti-ship missiles. -- @field #number MissilesCR Amount of cruise missiles. -- @field #number MissilesBM Amount of ballistic missiles. +-- @field #number MissilesSA Amount of surfe-to-air missiles. --- Waypoint data. -- @type OPSGROUP.Waypoint @@ -1695,6 +1696,12 @@ end -- @return #boolean If true, the group exists or false if the group does not exist. If nil, the DCS group could not be found. function OPSGROUP:IsActive() + if self.group then + local active=self.group:IsActive() + return active + end + + return nil end --- Check if group is alive. @@ -1737,7 +1744,6 @@ end -- @param #OPSGROUP self -- @return #boolean If true, all units/elements of the group are dead. function OPSGROUP:IsDead() - --env.info("FF IsDead") if self.isDead then return true else @@ -1806,6 +1812,16 @@ function OPSGROUP:IsEngaging() return is end +--- Check if group is currently waiting. +-- @param #OPSGROUP self +-- @return #boolean If true, group is currently waiting. +function OPSGROUP:IsWaiting() + if self.Twaiting then + return true + end + return false +end + --- Check if the group is not a carrier yet. -- @param #OPSGROUP self -- @return #boolean If true, group is not a carrier. @@ -1902,14 +1918,6 @@ function OPSGROUP:IsLoaded(CarrierGroupName) return self.cargoStatus==OPSGROUP.CargoStatus.LOADED end ---- Check if the group is cargo and waiting for a carrier to pick it up. --- @param #OPSGROUP self --- @return #boolean If true, group is waiting for a carrier. -function OPSGROUP:IsWaitingAsCargo() - return self.cargoStatus==OPSGROUP.CargoStatus.WAITING -end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3872,14 +3880,37 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if group is currently waiting. --- @param #OPSGROUP self --- @param #boolean If true, group is currently waiting. -function OPSGROUP:IsWaiting() - if self.Twaiting then - return true +--- On before "Wait" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Duration Duration how long the group will be waiting in seconds. Default `nil` (=forever). +function OPSGROUP:onbeforeWait(From, Event, To, Duration) + + local allowed=true + local Tsuspend=nil + + -- Check for a current task. + if self.taskcurrent>0 then + self:I(self.lid..string.format("WARNING: Got current task ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false end - return false + + -- Check for a current transport assignment. + if self.cargoTransport then + self:I(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) + Tsuspend=-30 + allowed=false + end + + -- Call wait again. + if Tsuspend and not allowed then + self:__Wait(Tsuspend, Duration) + end + + return allowed end --- On after "Wait" event. @@ -3991,7 +4022,6 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) -- Check if there is mission task if missiontask then - env.info("FF executing mission task") self:TaskExecute(missiontask) return 1 end @@ -4038,7 +4068,7 @@ function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID) if n then - -- TODO: switch to re-enable waypoint tasks. + -- TODO: Switch to re-enable waypoint tasks? if false then local tasks=self:GetTasksWaypoint(n) @@ -4213,7 +4243,7 @@ function OPSGROUP:onbeforeLaserOn(From, Event, To, Target) self:LaserGotLOS() else -- Try to switch laser on again in 10 sec. - self:I(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") + self:T(self.lid.."LASER got no LOS currently. Trying to switch the laser on again in 10 sec") self:__LaserOn(-10, Target) return false end @@ -4697,7 +4727,6 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) -- Clear cargo bay of element. - --for _,_cargo in pairs(Element.cargoBay) do for i=#Element.cargoBay,1,-1 do local cargo=Element.cargoBay[i] --#OPSGROUP.MyCargo @@ -4809,6 +4838,13 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Group is DESPAWNED --- + -- Ensure elements in utero. + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + self:ElementInUtero(element) + end + + --[[ -- Loop over template units. @@ -4845,7 +4881,10 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Not dead or destroyed any more. self.isDead=false self.isDestroyed=false - self.Ndestroyed=0 + self.Ndestroyed=0 + + self:InitWaypoints() + self:_InitGroup() -- Reset events. --self:ResetEvents() @@ -4919,10 +4958,17 @@ function OPSGROUP:onafterDead(From, Event, To) for i,_transport in pairs(self.cargoqueue) do local transport=_transport --Ops.OpsTransport#OPSTRANSPORT transport:__DeadCarrierGroup(1, self) - end + end + + -- Cargo queue empty + self.cargoqueue={} + + -- No current cargo transport. + self.cargoTransport=nil + -- Stop in a sec. - self:__Stop(-5) + --self:__Stop(-5) end --- On before "Stop" event. @@ -5343,6 +5389,7 @@ function OPSGROUP:_CheckDelivered(CargoTransport) elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then -- This one is dead. else + --env.info(string.format()) done=false --Someone is not done! end @@ -5378,14 +5425,14 @@ end -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport Cargo transport do be deleted. -- @return #OPSGROUP self -function OPSGROUP:DelCargoTransport(CargoTransport) - - for i,_transport in pairs(self.cargoqueue) do - local transport=_transport --Ops.OpsTransport#OPSTRANSPORT +function OPSGROUP:DelOpsTransport(CargoTransport) + + for i=#self.cargoqueue,1,-1 do + local transport=self.cargoqueue[i] --Ops.OpsTransport#OPSTRANSPORT if transport.uid==CargoTransport.uid then table.remove(self.cargoqueue, i) return self - end + end end return self @@ -5574,7 +5621,7 @@ function OPSGROUP:AddWeightCargo(UnitName, Weight) local element=self:GetElementByName(UnitName) - if element and element.unit and element.unit:IsAlive() then + if element then --we do not check if the element is actually alive because we need to remove cargo from dead units -- Add weight. element.weightCargo=element.weightCargo+Weight @@ -5767,7 +5814,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- We are already in the pickup zone ==> wait and initiate loading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:Wait() + self:FullStop() end -- Start loading. @@ -5992,7 +6039,7 @@ end function OPSGROUP:_TransferCargo(CargoGroup, CarrierGroup, CarrierElement) -- Debug info. - self:I(self.lid..string.format("Transferring cargo %s to new carrier group %s", CargoGroup:GetName(), CarrierGroup:GetName())) + self:T(self.lid..string.format("Transferring cargo %s to new carrier group %s", CargoGroup:GetName(), CarrierGroup:GetName())) -- Unload from this and directly load into the other carrier. self:Unload(CargoGroup) @@ -6136,7 +6183,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- We are already in the pickup zone ==> wait and initiate unloading. if (self:IsArmygroup() or self:IsNavygroup()) and not self:IsHolding() then - self:Wait() + self:FullStop() end -- Start loading. @@ -6440,10 +6487,12 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Add current waypoint. These have been cleard on loading. if OpsGroup:IsNavygroup() then + OpsGroup:ClearWaypoints() OpsGroup.currentwp=1 OpsGroup.passedfinalwp=true NAVYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) elseif OpsGroup:IsArmygroup() then + OpsGroup:ClearWaypoints() OpsGroup.currentwp=1 OpsGroup.passedfinalwp=true ARMYGROUP.AddWaypoint(OpsGroup, Coordinate, nil, nil, nil, false) @@ -6555,7 +6604,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end -- Remove cargo transport from cargo queue. - self:DelCargoTransport(CargoTransport) + self:DelOpsTransport(CargoTransport) end @@ -6860,8 +6909,6 @@ function OPSGROUP:_CheckGroupDone(delay) -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) - --env.info("FF CheckGroupDone") - if waypoint then -- Number of tasks remaining for this waypoint. @@ -7437,7 +7484,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Wait and load cargo. - opsgroup:Wait() + opsgroup:FullStop() opsgroup:__Loading(-5) end @@ -7450,7 +7497,7 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) else -- Stop and unload. - opsgroup:Wait() + opsgroup:FullStop() opsgroup:Unloading() end @@ -8521,7 +8568,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:__Dead(-1) + self:Dead() end end @@ -8678,8 +8725,6 @@ function OPSGROUP:_GetElementZoneLoader(Element, Zone, Loader) -- Heading in deg. local heading=math.deg(math.atan2(X.z, X.x)) - --env.info(string.format("FF l=%d w=%d h=%d", l, w, heading)) - -- Bounding box at the origin of the map facing "North". local b={} @@ -9035,6 +9080,24 @@ function OPSGROUP:_CoordinateFromObject(Object) return nil end +--- Check if a unit is an element of the flightgroup. +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +-- @return #boolean If true, unit is element of the flight group or false if otherwise. +function OPSGROUP:_IsElement(unitname) + + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + + if element.name==unitname then + return true + end + + end + + return false +end + --- Add a unit/element to the OPS group. -- @param #OPSGROUP self -- @param #string unitname Name of unit. @@ -9095,7 +9158,7 @@ function OPSGROUP:_AddElementByName(unitname) element.weight=element.weightEmpty+element.weightCargo -- Looks like only aircraft have a massMax value in the descriptors. - element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+2*95 --If max mass is not given, we assume 10 soldiers. + element.weightMaxTotal=element.descriptors.massMax or element.weightEmpty+8*95 --If max mass is not given, we assume 8 soldiers. if self.isArmygroup then @@ -9141,7 +9204,9 @@ function OPSGROUP:_AddElementByName(unitname) self:T(self.lid..text) -- Add element to table. - table.insert(self.elements, element) + if not self:_IsElement(unitname) then + table.insert(self.elements, element) + end -- Trigger spawned event if alive. if unit:IsAlive() then diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index bea743fe7..61bd09885 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -208,8 +208,8 @@ function OPSTRANSPORT:New(GroupSet, Pickupzone, Deployzone) -- PLANNED --> SCHEDULED --> EXECUTING --> DELIVERED self:AddTransition("*", "Planned", OPSTRANSPORT.Status.PLANNED) -- Cargo transport was planned. self:AddTransition(OPSTRANSPORT.Status.PLANNED, "Scheduled", OPSTRANSPORT.Status.SCHEDULED) -- Cargo is queued at at least one carrier. - self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. - self:AddTransition(OPSTRANSPORT.Status.EXECUTING, "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. + self:AddTransition(OPSTRANSPORT.Status.SCHEDULED, "Executing", OPSTRANSPORT.Status.EXECUTING) -- Cargo is being transported. + self:AddTransition("*", "Delivered", OPSTRANSPORT.Status.DELIVERED) -- Cargo was delivered. self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "*") @@ -885,7 +885,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterPlanned(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.PLANNED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Scheduled" event. @@ -894,7 +894,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterScheduled(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.SCHEDULED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) end --- On after "Executing" event. @@ -903,7 +903,22 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterExecuting(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.EXECUTING)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) +end + +--- On before "Delivered" event. +-- @param #OPSTRANSPORT self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSTRANSPORT:onbeforeDelivered(From, Event, To) + + -- Check that we do not call delivered again. + if From==OPSTRANSPORT.Status.DELIVERED then + return false + end + + return true end --- On after "Delivered" event. @@ -912,7 +927,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSTRANSPORT:onafterDelivered(From, Event, To) - self:I(self.lid..string.format("New status %s", OPSTRANSPORT.Status.DELIVERED)) + self:T(self.lid..string.format("New status: %s-->%s", From, To)) -- Inform all assigned carriers that cargo was delivered. They can have this in the queue or are currently processing this transport. for _,_carrier in pairs(self.carriers) do @@ -941,7 +956,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo OPSGROUP that was unloaded from a carrier. +-- @param Ops.OpsGroup#OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. -- @param Ops.OpsGroup#OPSGROUP OpsGroupCarrier Carrier OPSGROUP that unloaded the cargo. function OPSTRANSPORT:onafterUnloaded(From, Event, To, OpsGroupCargo, OpsGroupCarrier) self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) @@ -966,6 +981,7 @@ end -- @param #string To To state. function OPSTRANSPORT:onafterDeadCarrierAll(From, Event, To) self:I(self.lid..string.format("ALL Carrier OPSGROUPs are dead! Setting stage to PLANNED if not all cargo was delivered.")) + self:_CheckDelivered() if not self:IsDelivered() then self:Planned() end @@ -997,7 +1013,6 @@ function OPSTRANSPORT:_CheckDelivered() -- This one was destroyed. elseif cargo.opsgroup:IsDead() then -- This one is dead. - dead=false elseif cargo.opsgroup:IsStopped() then -- This one is stopped. dead=false @@ -1089,7 +1104,7 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) local carrierGroup=_carrier --Ops.OpsGroup#OPSGROUP -- First check if carrier is alive and loading cargo. - if carrierGroup and carrierGroup:IsAlive() and carrierGroup:IsLoading() then + if carrierGroup and carrierGroup:IsAlive() and (carrierGroup:IsLoading() or self.deployzone:IsInstanceOf("ZONE_AIRBASE")) then -- Find an element of the group that has enough free space. carrier=carrierGroup:FindCarrierForCargo(CargoGroup) @@ -1098,10 +1113,10 @@ function OPSTRANSPORT:FindTransferCarrierForCargo(CargoGroup, Zone) if Zone==nil or Zone:IsCoordinateInZone(carrier.unit:GetCoordinate()) then return carrier, carrierGroup else - self:T3(self.lid.."Got transfer carrier but carrier not in zone (yet)!") + self:T2(self.lid.."Got transfer carrier but carrier not in zone (yet)!") end else - self:T3(self.lid.."No transfer carrier available!") + self:T2(self.lid.."No transfer carrier available!") end end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a9c678ab5..f844a8ad2 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -310,8 +310,7 @@ end --- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Position The 3D position vectors of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive. function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) @@ -340,8 +339,7 @@ end -- -- @param #GROUP self -- @return #boolean true if the group is alive and active. --- @return #boolean false if the group is alive but inactive. --- @return #nil if the group does not exist anymore. +-- @return #boolean false if the group is alive but inactive or #nil if the group does not exist anymore. function GROUP:IsAlive() self:F2( self.GroupName ) @@ -363,8 +361,7 @@ end --- Returns if the group is activated. -- @param #GROUP self --- @return #boolean true if group is activated. --- @return #nil The group is not existing or alive. +-- @return #boolean true if group is activated or #nil The group is not existing or alive. function GROUP:IsActive() self:F2( self.GroupName ) @@ -412,7 +409,6 @@ function GROUP:Destroy( GenerateEvent, delay ) self:F2( self.GroupName ) if delay and delay>0 then - --SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else From efe41a5e21a1047aa8f47868d12b431f45e834ed Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Jul 2021 17:16:25 +0200 Subject: [PATCH 362/382] CSAR and CTLD - use Frequency generation moved to UTILS CTLD - added option to drop crates anywhere MANTIS - added state tracker to call Green/Red state change events only once UTILS - added Marianas NDBs to Frequency generation --- Moose Development/Moose/Functional/Mantis.lua | 7 ++ Moose Development/Moose/Ops/CSAR.lua | 34 ++++++--- Moose Development/Moose/Ops/CTLD.lua | 76 ++++++++++++------- Moose Development/Moose/Utilities/Utils.lua | 4 +- 4 files changed, 80 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 1186378e1..3b185043c 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1136,6 +1136,13 @@ do -- @return #MANTIS self function MANTIS:onafterStatus(From,Event,To) self:T({From, Event, To}) + -- Display some states + if self.debug then + self:I(self.lid .. "Status Report") + for _name,_state in pairs(self.SamStateTracker) do + self:I(string.format("Site %s\tStatus %s",_name,_state)) + end + end local interval = self.detectinterval * -1 self:__Status(interval) return self diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 49a357e34..ecd048504 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -214,6 +214,8 @@ CSAR = { -- @field #number timestamp Timestamp for approach process --- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. + +--[[ Moved to Utils -- @field #CSAR.SkipFrequencies CSAR.SkipFrequencies = { 214,274,291.5,295,297.5, @@ -229,7 +231,8 @@ CSAR.SkipFrequencies = { 905,907,920,935,942,950,995, 1000,1025,1030,1050,1065,1116,1175,1182,1210 } - +--]] + --- All slot / Limit settings -- @type CSAR.AircraftType -- @field #string typename Unit type name. @@ -245,7 +248,7 @@ CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r1" +CSAR.version="0.1.8r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -872,7 +875,11 @@ function CSAR:_EventHandler(EventData) end if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then - self:_RescuePilots(_unit) + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_event.IniUnitName) then + self:_DisplayMessageToSAR(_unit, "Open the door to let me out!", self.messageTime, true) + else + self:_RescuePilots(_unit) + end else self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) end @@ -1113,27 +1120,27 @@ function CSAR:_IsLoadingDoorOpen( unit_name ) local type_name = unit:getTypeName() if type_name == "Mi-8MT" and unit:getDrawArgumentValue(86) == 1 or unit:getDrawArgumentValue(250) == 1 then - self:I(unit_name .. " Cargo doors are open or cargo door not present") + self:T(unit_name .. " Cargo doors are open or cargo door not present") ret_val = true end if type_name == "Mi-24P" and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(86) == 1 then - self:I(unit_name .. " a side door is open") + self:T(unit_name .. " a side door is open") ret_val = true end if type_name == "UH-1H" and unit:getDrawArgumentValue(43) == 1 or unit:getDrawArgumentValue(44) == 1 then - self:I(unit_name .. " a side door is open ") + self:T(unit_name .. " a side door is open ") ret_val = true end if string.find(type_name, "SA342" ) and unit:getDrawArgumentValue(34) == 1 or unit:getDrawArgumentValue(38) == 1 then - self:I(unit_name .. " front door(s) are open") + self:T(unit_name .. " front door(s) are open") ret_val = true end if ret_val == false then - self:I(unit_name .. " all doors are closed") + self:T(unit_name .. " all doors are closed") end return ret_val @@ -1346,8 +1353,12 @@ function CSAR:_ScheduledSARFlight(heliname,groupname) end if _dist < 200 and _heliUnit:InAir() == false then + if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(heliname) then + self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true) + else self:_RescuePilots(_heliUnit) return + end end --queue up @@ -1755,11 +1766,13 @@ end -- @param #CSAR self function CSAR:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - local _skipFrequencies = self.SkipFrequencies + --local _skipFrequencies = self.SkipFrequencies local FreeVHFFrequencies = {} - local UsedVHFFrequencies = {} + --local UsedVHFFrequencies = {} + FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + --[[ moved to UTILS -- first range local _start = 200000 while _start < 400000 do @@ -1819,6 +1832,7 @@ function CSAR:_GenerateVHFrequencies() _start = _start + 50000 end + --]] self.FreeVHFFrequencies = FreeVHFFrequencies return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 8db791730..f4363342f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -262,6 +262,7 @@ do -- -- my_ctld.useprefix = true -- (DO NOT SWITCH THIS OFF UNLESS YOU KNOW WHAT YOU ARE DOING!) Adjust **before** starting CTLD. If set to false, *all* choppers of the coalition side will be enabled for CTLD. -- my_ctld.CrateDistance = 30 -- List and Load crates in this radius only. +-- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. -- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. -- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. -- my_ctld.forcehoverload = true -- Crates (not: troops) can only be loaded while hovering. @@ -516,6 +517,8 @@ CTLD.UnitTypes = { } --- Updated and sorted known NDB beacons (in kHz!) from the available maps + +--[[ -- Now in UTILS -- @field #CTLD.SkipFrequencies CTLD.SkipFrequencies = { 214,274,291.5,295,297.5, @@ -531,10 +534,11 @@ CTLD.SkipFrequencies = { 905,907,920,935,942,950,995, 1000,1025,1030,1050,1065,1116,1175,1182,1210 } - +--]] + --- CTLD class version. -- @field #string version -CTLD.version="0.1.3r2" +CTLD.version="0.1.4r1" --- Instantiate a new CTLD. -- @param #CTLD self @@ -643,6 +647,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.minimumHoverHeight = 4 self.forcehoverload = true self.hoverautoloading = true + self.dropcratesanywhere = false -- #1570 self.smokedistance = 2000 self.movetroopstowpzone = true @@ -794,13 +799,15 @@ end function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} + self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() + --[[ local _start = 220000000 while _start < 399000000 do table.insert(self.FreeUHFFrequencies, _start) _start = _start + 500000 end - + --]] return self end @@ -809,7 +816,8 @@ end function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} - + self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() + --[[ for _first = 3, 7 do for _second = 0, 5 do for _third = 0, 9 do @@ -818,7 +826,7 @@ function CTLD:_GenerateFMFrequencies() end end end - + --]] return self end @@ -826,6 +834,13 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") + + self.FreeVHFFrequencies = {} + self.UsedVHFFrequencies = {} + + self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() + + --[[ local _skipFrequencies = self.SkipFrequencies self.FreeVHFFrequencies = {} @@ -882,10 +897,11 @@ function CTLD:_GenerateVHFrequencies() end _start = _start + 50000 end - + --]] return self end +--[[ -- unused --- (Internal) Function to generate valid laser codes. -- @param #CTLD self function CTLD:_GenerateLaserCodes() @@ -926,6 +942,8 @@ function CTLD:_ContainsDigit(_number, _numberToFind) return false end +--]] + --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData @@ -1056,24 +1074,26 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) if not drop then inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) else - inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if self.dropcratesanywhere then -- #1570 + inzone = true + else + inzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + end end if not inzone then self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a logistics zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end - + -- avoid crate spam local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[Unit:GetTypeName()] -- #CTLD.UnitCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 30 local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - --local m = MESSAGE:New("There are enough crates nearby already! Take care of those first!",15,"CTLD"):ToGroup(Group) + return self end -- spawn crates in front of helicopter @@ -1082,7 +1102,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number local cratename = cargotype:GetName() - --self:Tself.lid .. string.format("Crate %s requested", cratename)) local cratetemplate = "Container"-- #string -- get position and heading of heli local position = Unit:GetCoordinate() @@ -1130,7 +1149,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:__CratesDropped(1, Group, Unit, droppedcargo) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,15,"CTLD",true):ToGroup(Group) return self end @@ -1149,7 +1167,6 @@ function CTLD:_ListCratesNearby( _group, _unit) for _,_entry in pairs (crates) do local entry = _entry -- #CTLD_CARGO local name = entry:GetName() --#string - -- TODO Meaningful sorting/aggregation local dropped = entry:WasDropped() if dropped then text:Add(string.format("Dropped crate for %s",name)) @@ -1158,14 +1175,12 @@ function CTLD:_ListCratesNearby( _group, _unit) end end if text:GetCount() == 1 then - text:Add("--------- N O N E ------------") + text:Add(" N O N E") end text:Add("------------------------------------------------------------") self:_SendMessage(text:Text(), 30, true, _group) - --local m = MESSAGE:New(text:Text(),15,"CTLD",true):ToGroup(_group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) - --local m = MESSAGE:New(string.format("No (loadable) crates within %d meters!",finddist),15,"CTLD",true):ToGroup(_group) end return self end @@ -1347,7 +1362,7 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(string.format("Troops: %d(%d), Crates: %d(%d)",no_troops,trooplimit,no_crates,cratelimit)) report:Add("------------------------------------------------------------") - report:Add("-- TROOPS --") + report:Add(" -- TROOPS --") for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum @@ -1356,10 +1371,10 @@ function CTLD:_ListCargo(Group, Unit) end end if report:GetCount() == 4 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") - report:Add("-- CRATES --") + report:Add(" -- CRATES --") local cratecount = 0 for _,_cargo in pairs(cargotable) do local cargo = _cargo -- #CTLD_CARGO @@ -1370,7 +1385,7 @@ function CTLD:_ListCargo(Group, Unit) end end if cratecount == 0 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") local text = report:Text() @@ -1498,13 +1513,16 @@ end -- @param Wrappe.Unit#UNIT Unit function CTLD:_UnloadCrates(Group, Unit) self:T(self.lid .. " _UnloadCrates") - -- check if we are in DROP zone - local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) - if not inzone then - self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) - if not self.debug then - return self + + if not self.dropcratesanywhere then -- #1570 + -- check if we are in DROP zone + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) + if not inzone then + self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) + --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) + if not self.debug then + return self + end end end -- check for hover unload @@ -2058,7 +2076,7 @@ function CTLD:_ListRadioBeacons(Group, Unit) end end if report:GetCount() == 1 then - report:Add("--------- N O N E ------------") + report:Add(" N O N E") end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 609ace372..87198a2d7 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1631,10 +1631,10 @@ function UTILS.GenerateVHFrequencies() local _skipFrequencies = { 214,274,291.5,295,297.5, 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, + 320,324,328,329,330,332,336,337, 342,343,348,351,352,353,358, 363,365,368,372.5,374, - 380,381,384,389,395,396, + 380,381,384,385,389,395,396, 414,420,430,432,435,440,450,455,462,470,485, 507,515,520,525,528,540,550,560,570,577,580, 602,625,641,662,670,680,682,690, From 78b3e3c60b2adebb847cf37225dd48bccd81e9e4 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 15 Jul 2021 22:25:02 +0200 Subject: [PATCH 363/382] OPS Respawn --- Moose Development/Moose/Core/Fsm.lua | 18 +- Moose Development/Moose/Core/Point.lua | 79 ++++++-- Moose Development/Moose/Ops/ArmyGroup.lua | 9 +- Moose Development/Moose/Ops/FlightGroup.lua | 35 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 13 +- Moose Development/Moose/Ops/OpsGroup.lua | 196 +++++++++++++++----- 6 files changed, 252 insertions(+), 98 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 0ada59e71..68807904b 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -410,7 +410,7 @@ do -- FSM Transition.To = To -- Debug message. - self:T2( Transition ) + self:T3( Transition ) self._Transitions[Transition] = Transition self:_eventmap( self.Events, Transition ) @@ -432,7 +432,7 @@ do -- FSM -- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM. -- @return Core.Fsm#FSM_PROCESS The SubFSM. function FSM:AddProcess( From, Event, Process, ReturnEvents ) - self:T( { From, Event } ) + self:T3( { From, Event } ) local Sub = {} Sub.From = From @@ -533,7 +533,7 @@ do -- FSM Process._Scores[State].ScoreText = ScoreText Process._Scores[State].Score = Score - self:T( Process._Scores ) + self:T3( Process._Scores ) return Process end @@ -576,7 +576,7 @@ do -- FSM self[__Event] = self[__Event] or self:_delayed_transition(Event) -- Debug message. - self:T2( "Added methods: " .. Event .. ", " .. __Event ) + self:T3( "Added methods: " .. Event .. ", " .. __Event ) Events[Event] = self.Events[Event] or { map = {} } self:_add_to_map( Events[Event].map, EventStructure ) @@ -825,7 +825,7 @@ do -- FSM end -- Debug. - self:T2( { CallID = CallID } ) + self:T3( { CallID = CallID } ) end end @@ -846,7 +846,7 @@ do -- FSM function FSM:_gosub( ParentFrom, ParentEvent ) local fsmtable = {} if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then - self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) + self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } ) return self.subs[ParentFrom][ParentEvent] else return {} @@ -1150,7 +1150,7 @@ do -- FSM_PROCESS -- @param #FSM_PROCESS self -- @return #FSM_PROCESS function FSM_PROCESS:Copy( Controllable, Task ) - self:T( { self:GetClassNameAndID() } ) + self:T3( { self:GetClassNameAndID() } ) local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS @@ -1176,13 +1176,13 @@ do -- FSM_PROCESS -- Copy End States for EndStateID, EndState in pairs( self:GetEndStates() ) do - self:T( EndState ) + self:T3( EndState ) NewFsm:AddEndState( EndState ) end -- Copy the score tables for ScoreID, Score in pairs( self:GetScores() ) do - self:T( Score ) + self:T3( Score ) NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score ) end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 1c28e0730..b450eebbf 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -26,19 +26,28 @@ -- -- ### Authors: -- --- * FlightControl : Design & Programming +-- * FlightControl (Design & Programming) -- -- ### Contributions: +-- +-- * funkyfranky +-- * Applevangelist +-- +-- === -- -- @module Core.Point -- @image Core_Coordinate.JPG - - do -- COORDINATE --- @type COORDINATE + -- @field #string ClassName Name of the class + -- @field #number x Component of the 3D vector. + -- @field #number y Component of the 3D vector. + -- @field #number z Component of the 3D vector. + -- @field #number Heading Heading in degrees. Needs to be set first. + -- @field #number Velocity Velocity in meters per second. Needs to be set first. -- @extends Core.Base#BASE @@ -201,13 +210,24 @@ do -- COORDINATE ClassName = "COORDINATE", } - --- @field COORDINATE.WaypointAltType + --- Waypoint altitude types. + -- @type COORDINATE.WaypointAltType + -- @field #string BARO Barometric altitude. + -- @field #string RADIO Radio altitude. COORDINATE.WaypointAltType = { BARO = "BARO", RADIO = "RADIO", } - --- @field COORDINATE.WaypointAction + --- Waypoint actions. + -- @type COORDINATE.WaypointAction + -- @field #string TurningPoint Turning point. + -- @field #string FlyoverPoint Fly over point. + -- @field #string FromParkingArea From parking area. + -- @field #string FromParkingAreaHot From parking area hot. + -- @field #string FromRunway From runway. + -- @field #string Landing Landing. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointAction = { TurningPoint = "Turning Point", FlyoverPoint = "Fly Over Point", @@ -218,7 +238,14 @@ do -- COORDINATE LandingReFuAr = "LandingReFuAr", } - --- @field COORDINATE.WaypointType + --- Waypoint types. + -- @type COORDINATE.WaypointType + -- @field #string TakeOffParking Take of parking. + -- @field #string TakeOffParkingHot Take of parking hot. + -- @field #string TakeOff Take off parking hot. + -- @field #string TurningPoint Turning point. + -- @field #string Land Landing point. + -- @field #string LandingReFuAr Landing and refuel and rearm. COORDINATE.WaypointType = { TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", @@ -232,13 +259,13 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self -- @param DCS#Distance x The x coordinate of the Vec3 point, pointing to the North. - -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to the Right. - -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the Right. - -- @return #COORDINATE + -- @param DCS#Distance y The y coordinate of the Vec3 point, pointing to up. + -- @param DCS#Distance z The z coordinate of the Vec3 point, pointing to the right. + -- @return #COORDINATE self function COORDINATE:New( x, y, z ) - --env.info("FF COORDINATE New") - local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE + local self=BASE:Inherit(self, BASE:New()) -- #COORDINATE + self.x = x self.y = y self.z = z @@ -249,7 +276,7 @@ do -- COORDINATE --- COORDINATE constructor. -- @param #COORDINATE self -- @param #COORDINATE Coordinate. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromCoordinate( Coordinate ) local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE @@ -263,8 +290,8 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec2 coordinates. -- @param #COORDINATE self -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. - -- @return #COORDINATE + -- @param DCS#Distance LandHeightAdd (Optional) The default height if required to be evaluated will be the land height of the x, y coordinate. You can specify an extra height to be added to the land height. + -- @return #COORDINATE self function COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) local LandHeight = land.getHeight( Vec2 ) @@ -274,8 +301,6 @@ do -- COORDINATE local self = self:New( Vec2.x, LandHeight, Vec2.y ) -- #COORDINATE - self:F2( self ) - return self end @@ -283,7 +308,7 @@ do -- COORDINATE --- Create a new COORDINATE object from Vec3 coordinates. -- @param #COORDINATE self -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return #COORDINATE + -- @return #COORDINATE self function COORDINATE:NewFromVec3( Vec3 ) local self = self:New( Vec3.x, Vec3.y, Vec3.z ) -- #COORDINATE @@ -292,6 +317,22 @@ do -- COORDINATE return self end + + --- Create a new COORDINATE object from a waypoint. This uses the components + -- + -- * `waypoint.x` + -- * `waypoint.alt` + -- * `waypoint.y` + -- + -- @param #COORDINATE self + -- @param DCS#Waypoint Waypoint The waypoint. + -- @return #COORDINATE self + function COORDINATE:NewFromWaypoint(Waypoint) + + local self=self:New(Waypoint.x, Waypoint.alt, Waypoint.y) -- #COORDINATE + + return self + end --- Return the coordinates itself. Sounds stupid but can be useful for compatibility. -- @param #COORDINATE self @@ -1720,7 +1761,7 @@ do -- COORDINATE return self:GetSurfaceType()==land.SurfaceType.LAND end - --- Checks if the surface type is road. + --- Checks if the surface type is land. -- @param #COORDINATE self -- @return #boolean If true, the surface type at the coordinate is land. function COORDINATE:IsSurfaceTypeLand() @@ -2082,7 +2123,7 @@ do -- COORDINATE --- Circle to all. -- Creates a circle on the map with a given radius, color, fill color, and outline. -- @param #COORDINATE self - -- @param #numberr Radius Radius in meters. Default 1000 m. + -- @param #number Radius Radius in meters. Default 1000 m. -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red (default). -- @param #number Alpha Transparency [0,1]. Default 1. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 9506a270b..b0c0b2c91 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -141,7 +141,7 @@ function ARMYGROUP:New(group) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize the group. self:_InitGroup() @@ -1115,8 +1115,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #ARMYGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #ARMYGROUP self -function ARMYGROUP:_InitGroup() +function ARMYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -1125,7 +1126,7 @@ function ARMYGROUP:_InitGroup() end -- Get template of group. - self.template=self.group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=false @@ -1136,7 +1137,7 @@ function ARMYGROUP:_InitGroup() self.isAI=true -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Ground groups cannot be uncontrolled. self.isUncontrolled=false diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5b15572ee..a10db29bf 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -306,7 +306,7 @@ function FLIGHTGROUP:New(group) self:HandleEvent(EVENTS.Kill, self.OnEventKill) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize group. self:_InitGroup() @@ -1478,7 +1478,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) -- Trigger element airborne event. - self:__ElementAirborne(0.1, Element) + self:__ElementAirborne(0.01, Element) end @@ -1750,7 +1750,7 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) self.currbase=nil -- Cruising. - self:__Cruise(-0.05) + self:__Cruise(-0.01) end @@ -1882,7 +1882,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) SpawnPoint.airdromeId = nil -- Airbase. - local airbase=self.isLandingAtAirbase + local airbase=self.isLandingAtAirbase --Wrapper.Airbase#AIRBASE -- Get airbase ID and category. local AirbaseID = airbase:GetID() @@ -1909,7 +1909,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) local unit=units[i] local element=self:GetElementByName(unit.name) if element and element.status~=OPSGROUP.ElementStatus.DEAD then - unit.parking=element.parking.TerminalID + unit.parking=element.parking and element.parking.TerminalID or nil unit.parking_id=nil local vec3=element.unit:GetVec3() local heading=element.unit:GetHeading() @@ -2415,6 +2415,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Passed final waypoint! self.passedfinalwp=true + + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) @@ -2469,7 +2473,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set holding flag to 0=false. self.flaghold:Set(0) - local holdtime=1*60 + local holdtime=2*60 if fc or self.airboss then holdtime=nil end @@ -3002,8 +3006,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #FLIGHTGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:_InitGroup() +function FLIGHTGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -3015,7 +3020,7 @@ function FLIGHTGROUP:_InitGroup() local group=self.group --Wrapper.Group#GROUP -- Get template of group. - self.template=group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=true @@ -3026,10 +3031,10 @@ function FLIGHTGROUP:_InitGroup() self.isHelo=group:IsHelicopter() -- Is (template) group uncontrolled. - self.isUncontrolled=self.template.uncontrolled + self.isUncontrolled=template.uncontrolled -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Max speed in km/h. self.speedMax=group:GetSpeedMax() @@ -3044,12 +3049,12 @@ function FLIGHTGROUP:_InitGroup() self.ammo=self:GetAmmoTot() -- Radio parameters from template. Default is set on spawn if not modified by user. - self.radio.Freq=tonumber(self.template.frequency) - self.radio.Modu=tonumber(self.template.modulation) - self.radio.On=self.template.communication + self.radio.Freq=tonumber(template.frequency) + self.radio.Modu=tonumber(template.modulation) + self.radio.On=template.communication -- Set callsign. Default is set on spawn if not modified by user. - local callsign=self.template.units[1].callsign + local callsign=template.units[1].callsign if type(callsign)=="number" then -- Sometimes callsign is just "101". local cs=tostring(callsign) callsign={} @@ -3453,7 +3458,7 @@ function FLIGHTGROUP:IsLandingAirbase(wp) if wp then - if wp.action and wp.action==COORDINATE.WaypointAction.LANDING then + if wp.action and wp.action==COORDINATE.WaypointAction.Landing then return true else return false diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 204792dcf..a1209e0db 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -165,7 +165,7 @@ function NAVYGROUP:New(group) -- Init waypoints. - self:InitWaypoints() + self:_InitWaypoints() -- Initialize the group. self:_InitGroup() @@ -1130,8 +1130,9 @@ end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. -- @param #NAVYGROUP self +-- @param #table Template Template used to init the group. Default is `self.template`. -- @return #NAVYGROUP self -function NAVYGROUP:_InitGroup() +function NAVYGROUP:_InitGroup(Template) -- First check if group was already initialized. if self.groupinitialized then @@ -1140,7 +1141,7 @@ function NAVYGROUP:_InitGroup() end -- Get template of group. - self.template=self.group:GetTemplate() + local template=Template or self:_GetTemplate() -- Define category. self.isAircraft=false @@ -1154,7 +1155,7 @@ function NAVYGROUP:_InitGroup() self.isAI=true -- Is (template) group late activated. - self.isLateActivated=self.template.lateActivation + self.isLateActivated=template.lateActivation -- Naval groups cannot be uncontrolled. self.isUncontrolled=false @@ -1170,8 +1171,8 @@ function NAVYGROUP:_InitGroup() -- Radio parameters from template. Default is set on spawn if not modified by the user. self.radio.On=true -- Radio is always on for ships. - self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 - self.radio.Modu=tonumber(self.template.units[1].modulation) + self.radio.Freq=tonumber(template.units[1].frequency)/1000000 + self.radio.Modu=tonumber(template.units[1].modulation) -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 69b5c1d01..6983f7dba 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -375,6 +375,12 @@ OPSGROUP.TaskType={ -- @field #number MissilesBM Amount of ballistic missiles. -- @field #number MissilesSA Amount of surfe-to-air missiles. +--- Spawn point data. +-- @type OPSGROUP.Spawnpoint +-- @field Core.Point#COORDINATE Coordinate Coordinate where to spawn +-- @field Wrapper.Airbase#AIRBASE Airport Airport where to spawn. +-- @field #table TerminalIDs Terminal IDs, where to spawn the group. It is a table of `#number`s because a group can consist of multiple units. + --- Waypoint data. -- @type OPSGROUP.Waypoint -- @field #number uid Waypoint's unit id, which is a running number. @@ -503,6 +509,9 @@ function OPSGROUP:New(group) return nil end end + + -- Set the template. + self:_SetTemplate() -- Init set of detected units. self.detectedunits=SET_UNIT:New() @@ -3881,7 +3890,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On before "Wait" event. --- @param #FLIGHTGROUP self +-- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. @@ -4787,7 +4796,7 @@ end -- @param #OPSGROUP self -- @param #number Delay Delay in seconds before respawn happens. Default 0. -- @param DCS#Template Template (optional) The template of the Group retrieved with GROUP:GetTemplate(). If the template is not provided, the template will be retrieved of the group itself. --- @param #boolean Reset Reset positions if TRUE. +-- @param #boolean Reset Reset waypoints and reinit group if `true`. -- @return #OPSGROUP self function OPSGROUP:_Respawn(Delay, Template, Reset) @@ -4799,7 +4808,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self:T2(self.lid.."FF _Respawn") -- Given template or get old. - Template=Template or UTILS.DeepCopy(self.template) + Template=Template or self:_GetTemplate(true) if self:IsAlive() then @@ -4807,6 +4816,8 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Group is ALIVE --- + --[[ + -- Get units. local units=self.group:GetUnits() @@ -4828,6 +4839,29 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) end end + + ]] + + local units=Template.units + + for i=#units,1,-1 do + local unit=units[i] + local element=self:GetElementByName(unit.name) + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + unit.parking=element.parking and element.parking.TerminalID or unit.parking + unit.parking_id=nil + local vec3=element.unit:GetVec3() + local heading=element.unit:GetHeading() + unit.x=vec3.x + unit.y=vec3.z + unit.alt=vec3.y + unit.heading=math.rad(heading) + unit.psi=-unit.heading + else + table.remove(units, i) + end + end + -- Despawn old group. Dont trigger any remove unit event since this is a respawn. self:Despawn(0, true) @@ -4843,28 +4877,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) local element=_element --#OPSGROUP.Element self:ElementInUtero(element) end - - - --[[ - - -- Loop over template units. - for UnitID, Unit in pairs(Template.units) do - - local element=self:GetElementByName(Unit.name) - - if element then - local vec3=element.vec3 - local heading=element.heading - Unit.x=vec3.x - Unit.y=vec3.z - Unit.alt=vec3.y - Unit.heading=math.rad(heading) - Unit.psi=-Unit.heading - end - - end - - ]] end @@ -4881,10 +4893,18 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Not dead or destroyed any more. self.isDead=false self.isDestroyed=false - self.Ndestroyed=0 - self:InitWaypoints() - self:_InitGroup() + + self.groupinitialized=false + self.Ndestroyed=0 + self.wpcounter=1 + self.currentwp=1 + + -- Init waypoints. + self:_InitWaypoints() + + -- Init Group. + self:_InitGroup() -- Reset events. --self:ResetEvents() @@ -5827,6 +5847,10 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Add waypoint. if self.isFlightgroup then + + --- + -- Flight Group + --- if airbasePickup then @@ -5872,7 +5896,9 @@ function OPSGROUP:onafterPickup(From, Event, To) elseif self.isNavygroup then + --- -- Navy Group + --- local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil @@ -5898,7 +5924,9 @@ function OPSGROUP:onafterPickup(From, Event, To) elseif self.isArmygroup then + --- -- Army Group + --- local cwp=self:GetWaypointCurrent() local uid=cwp and cwp.uid or nil @@ -5972,12 +6000,12 @@ function OPSGROUP:onafterLoading(From, Event, To) else -- Debug info. - self:T(self.lid.."Cannot board carrier!") + self:T(self.lid..string.format("Cannot board carrier! Group %s is NOT (yet) in zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) end else -- Debug info. - self:T(self.lid.."Cargo NOT in embark zone "..self.cargoTransport.embarkzone:GetName()) + self:T(self.lid..string.format("Cargo %s NOT in embark zone %s", cargo.opsgroup:GetName(), self.cargoTransport.embarkzone:GetName())) end end @@ -7251,6 +7279,7 @@ function OPSGROUP:_CreateWaypoint(waypoint) waypoint.patrol=false waypoint.detour=false waypoint.astar=false + waypoint.temp=false -- Increase UID counter. self.wpcounter=self.wpcounter+1 @@ -7271,33 +7300,40 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) table.insert(self.waypoints, wpnumber, waypoint) -- Debug info. - self:T(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) + self:T(self.lid..string.format("Adding waypoint at index=%d with UID=%d", wpnumber, waypoint.uid)) -- Now we obviously did not pass the final waypoint. - self.passedfinalwp=false - - -- Switch to cruise mode. - if self:IsHolding() then - -- Disable this for now. Cruise has to be commanded manually now. If group is ordered to hold, it will hold until told to move again. - --self:Cruise() + if self.currentwp and wpnumber>self.currentwp then + self.passedfinalwp=false end + end --- Initialize Mission Editor waypoints. -- @param #OPSGROUP self +-- @param #number WpIndexMin +-- @param #number WpIndexMax -- @return #OPSGROUP self -function OPSGROUP:InitWaypoints() +function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax) -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() - -- Waypoints + -- Waypoints empty! self.waypoints={} + + WpIndexMin=WpIndexMin or 1 + WpIndexMax=WpIndexMax or #self.waypoints0 + WpIndexMax=math.min(WpIndexMax, #self.waypoints0) --Ensure max is not out of bounce. - for index,wp in pairs(self.waypoints0) do + --for index,wp in pairs(self.waypoints0) do + + for i=WpIndexMin,WpIndexMax do + + local wp=self.waypoints0[i] --DCS#Waypoint -- Coordinate of the waypoint. - local coordinate=COORDINATE:New(wp.x, wp.alt, wp.y) + local coordinate=COORDINATE:NewFromWaypoint(wp) -- Strange! wp.speed=wp.speed or 0 @@ -7305,17 +7341,49 @@ function OPSGROUP:InitWaypoints() -- Speed at the waypoint. local speedknots=UTILS.MpsToKnots(wp.speed) - if index==1 then + if i==1 then self.speedWp=wp.speed end + + local waypoint=self:_CreateWaypoint(wp) + + self:_AddWaypoint(waypoint) -- Add waypoint. - self:AddWaypoint(coordinate, speedknots, index-1, nil, false) - + --[[ + if self:IsFlightgroup() then + FLIGHTGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Altitude, false) + elseif self:IsArmygroup() then + ARMYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Formation, false) + elseif self:IsNavygroup() then + NAVYGROUP.AddWaypoint(self, coordinate, speedknots, index-1, Depth, false) + else + -- Should not happen! + self:AddWaypoint(coordinate, speedknots, index-1, nil, false) + end + ]] + end -- Debug info. self:T(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) + + -- Flight group specific. + if self:IsFlightgroup() then + + -- Get home and destination airbases from waypoints. + self.homebase=self.homebase or self:GetHomebaseFromWaypoints() + self.destbase=self.destbase or self:GetDestinationFromWaypoints() + self.currbase=self:GetHomebaseFromWaypoints() + + -- Remove the landing waypoint. We use RTB for that. It makes adding new waypoints easier as we do not have to check if the last waypoint is the landing waypoint. + if self.destbase and #self.waypoints>1 then + table.remove(self.waypoints, #self.waypoints) + else + self.destbase=self.homebase + end + + end -- Update route. if #self.waypoints>0 then @@ -7324,7 +7392,9 @@ function OPSGROUP:InitWaypoints() if #self.waypoints==1 then self.passedfinalwp=true end - + + else + self:E(self.lid.."WARNING: No waypoints initialized. Number of waypoints is 0!") end return self @@ -9219,6 +9289,42 @@ function OPSGROUP:_AddElementByName(unitname) return nil end +--- Set the template of the group. +-- @param #OPSGROUP self +-- @param #table Template Template to set. Default is from the GROUP. +-- @return #OPSGROUP self +function OPSGROUP:_SetTemplate(Template) + + self.template=Template or self.group:GetTemplate() + + self:I(self.lid.."Setting group template") + + return self +end + +--- Get the template of the group. +-- @param #OPSGROUP self +-- @param #boolean Copy Get a deep copy of the template. +-- @return #table Template table. +function OPSGROUP:_GetTemplate(Copy) + + if self.template then + + if Copy then + local template=UTILS.DeepCopy(self.template) + return template + else + return self.template + end + + else + self:E(self.lid..string.format("ERROR: No template was set yet!")) + end + + return nil +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 25e118f3dc7a1925b64b408a00332295d2550af7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 17 Jul 2021 15:48:18 +0200 Subject: [PATCH 364/382] Added correct Player Mi-8MT unitname CSAR - logic change to detect dead pilots, if they are not set to immortal. Added FSM event "KIA" --- Moose Development/Moose/Ops/CSAR.lua | 182 +++++++++------------------ Moose Development/Moose/Ops/CTLD.lua | 22 +--- 2 files changed, 58 insertions(+), 146 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index ecd048504..df419a5b9 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1,3 +1,4 @@ + --- **Ops** -- Combat Search and Rescue. -- -- === @@ -212,7 +213,8 @@ CSAR = { -- @field #string player Player name if applicable. -- @field Wrapper.Group#GROUP group Spawned group object. -- @field #number timestamp Timestamp for approach process - +-- @field #boolean alive Group is alive or dead/rescued +-- --- Updated and sorted list of known NDB beacons (in kHz!) from the available maps. --[[ Moved to Utils @@ -242,13 +244,14 @@ CSAR.AircraftType["SA342Minigun"] = 2 CSAR.AircraftType["SA342L"] = 4 CSAR.AircraftType["SA342M"] = 4 CSAR.AircraftType["UH-1H"] = 8 -CSAR.AircraftType["Mi-8MTV2"] = 12 +CSAR.AircraftType["Mi-8MTV2"] = 12 +CSAR.AircraftType["Mi-8MT"] = 12 CSAR.AircraftType["Mi-24P"] = 8 CSAR.AircraftType["Mi-24V"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.8r2" +CSAR.version="0.1.8r3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -320,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Boarded", "*") -- Pilot boarded. self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "KIA", "*") -- Pilot killed in action. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables, mainly for tracking actions @@ -461,6 +465,14 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string HeliName Name of the helicopter group. -- @param #number PilotsSaved Number of the saved pilots on board when landing. + --- On After "KIA" event. Pilot is dead. + -- @function [parent=#CSAR] OnAfterKIA + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Pilotname Name of the pilot KIA. + return self end @@ -494,6 +506,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.typename = Typename or "" DownedPilot.group = Group DownedPilot.timestamp = 0 + DownedPilot.alive = true -- Add Pilot local PilotTable = self.downedPilots @@ -930,7 +943,7 @@ function CSAR:_CheckNameInDownedPilots(name) local found = false local table = nil for _,_pilot in pairs(PilotTable) do - if _pilot.name == name then + if _pilot.name == name and _pilot.alive == true then found = true table = _pilot break @@ -947,25 +960,10 @@ end function CSAR:_RemoveNameFromDownedPilots(name,force) local PilotTable = self.downedPilots --#CSAR.DownedPilot local found = false - for _,_pilot in pairs(PilotTable) do + for _index,_pilot in pairs(PilotTable) do if _pilot.name == name then - local group = _pilot.group -- Wrapper.Group#GROUP - if group then - if (not group:IsAlive()) or ( force == true) then -- don\'t delete groups which still exist - found = true - _pilot.desc = nil - _pilot.frequency = nil - _pilot.index = nil - _pilot.name = nil - _pilot.originalUnit = nil - _pilot.player = nil - _pilot.side = nil - _pilot.typename = nil - _pilot.group = nil - _pilot.timestamp = nil - end + self.downedPilots[_index].alive = false end - end end return found end @@ -988,7 +986,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end local _woundedGroup = _downedpilot.group - if _woundedGroup ~= nil then + if _woundedGroup ~= nil and _woundedGroup:IsAlive() then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking @@ -1001,7 +999,6 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) @@ -1019,7 +1016,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end else self:T("...Downed Pilot KIA?!") - self:_RemoveNameFromDownedPilots(_downedpilot.name) + if not _downedpilot.alive then + --self:__KIA(1,_downedpilot.name) + self:_RemoveNameFromDownedPilots(_downedpilot.name, true) + end end return self end @@ -1295,36 +1295,6 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end end ---- (Internal) Check if group not KIA. --- @param #CSAR self --- @param Wrapper.Group#GROUP _woundedGroup --- @param #string _woundedGroupName --- @param Wrapper.Unit#UNIT _heliUnit --- @param #string _heliName --- @return #boolean Outcome -function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) - self:T(self.lid .. " _CheckGroupNotKIA") - -- check if unit has died or been picked up - local inTransit = false - if _woundedGroup and _heliUnit then - for _currentHeli, _groups in pairs(self.inTransitGroups) do - if _groups[_woundedGroupName] then - inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) - break - end -- end name check - end -- end loop - if not inTransit then - -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) - end - --stops the message being displayed again - self:_RemoveNameFromDownedPilots(_woundedGroupName) - end - --continue - return inTransit -end - --- (Internal) Monitor in-flight returning groups. -- @param #CSAR self -- @param #string heliname Heli name @@ -1475,7 +1445,7 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) --local _woundedGroup = GROUP:FindByName(_groupName) local _woundedGroup = _value.group - if _woundedGroup then + if _woundedGroup and _value.alive then local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() @@ -1769,70 +1739,7 @@ function CSAR:_GenerateVHFrequencies() --local _skipFrequencies = self.SkipFrequencies local FreeVHFFrequencies = {} - --local UsedVHFFrequencies = {} FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - - --[[ moved to UTILS - -- first range - local _start = 200000 - while _start < 400000 do - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 10000 - end - - -- second range - _start = 400000 - while _start < 850000 do - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 10000 - end - - -- third range - _start = 850000 - while _start <= 999000 do -- updated for Gazelle - - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - - if _found == false then - table.insert(FreeVHFFrequencies, _start) - end - - _start = _start + 50000 - end - --]] self.FreeVHFFrequencies = FreeVHFFrequencies return self end @@ -1929,8 +1836,8 @@ function CSAR:_CountActiveDownedPilots() self:T(self.lid .. " _CountActiveDownedPilots") local PilotsInFieldN = 0 for _, _unitName in pairs(self.downedPilots) do - self:T({_unitName}) - if _unitName.name ~= nil then + self:T({_unitName.desc}) + if _unitName.alive == true then PilotsInFieldN = PilotsInFieldN + 1 end end @@ -1981,6 +1888,26 @@ function CSAR:onafterStart(From, Event, To) return self end +--- (Internal) Function called before Status() event. +-- @param #CSAR self +function CSAR:_CheckDownedPilotTable() + local pilots = self.downedPilots + for _,_entry in pairs (pilots) do + self:T("Checking for " .. _entry.name) + self:T({entry=_entry}) + local group = _entry.group + if not group:IsAlive() then + self:T("Group is dead") + if _entry.alive == true then + self:T("Switching .alive to false") + self:__KIA(1,_entry.desc) + self:_RemoveNameFromDownedPilots(_entry.name,true) + end + end + end + return self +end + --- (Internal) Function called before Status() event. -- @param #CSAR self. -- @param #string From From state. @@ -1991,15 +1918,18 @@ function CSAR:onbeforeStatus(From, Event, To) -- housekeeping self:_AddMedevacMenuItem() self:_RefreshRadioBeacons() + self:_CheckDownedPilotTable() for _,_sar in pairs (self.csarUnits) do local PilotTable = self.downedPilots for _,_entry in pairs (PilotTable) do - local entry = _entry -- #CSAR.DownedPilot - local name = entry.name - local timestamp = entry.timestamp or 0 - local now = timer.getAbsTime() - if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. - self:_CheckWoundedGroupStatus(_sar,name) + if _entry.alive then + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we\'re not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end end end end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index f4363342f..d65a0deba 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -510,32 +510,13 @@ CTLD.UnitTypes = { ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, + ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8}, ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers } ---- Updated and sorted known NDB beacons (in kHz!) from the available maps - ---[[ -- Now in UTILS --- @field #CTLD.SkipFrequencies -CTLD.SkipFrequencies = { - 214,274,291.5,295,297.5, - 300.5,304,307,309.5,311,312,312.5,316, - 320,324,328,329,330,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 380,381,384,389,395,396, - 414,420,430,432,435,440,450,455,462,470,485, - 507,515,520,525,528,540,550,560,570,577,580,602,625,641,662,670,680,682,690, - 705,720,722,730,735,740,745,750,770,795, - 822,830,862,866, - 905,907,920,935,942,950,995, - 1000,1025,1030,1050,1065,1116,1175,1182,1210 - } ---]] - --- CTLD class version. -- @field #string version CTLD.version="0.1.4r1" @@ -2449,6 +2430,7 @@ end -- @return #CTLD self function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) + self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes --self:T{prefix=prefix}) From 09785ef451fecb771bd6415d22be4385db042ca7 Mon Sep 17 00:00:00 2001 From: bbirchnz Date: Sun, 18 Jul 2021 21:01:45 +1000 Subject: [PATCH 365/382] - ctld: add troop extract (#1574) --- Moose Development/Moose/Ops/CTLD.lua | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d65a0deba..6fa3ff5f9 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -577,6 +577,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. @@ -693,6 +694,17 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self + --- FSM Function OnAfterCratesPickedUp. -- @function [parent=#CTLD] OnAfterCratesPickedUp -- @param #CTLD self @@ -1038,6 +1050,96 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return self end + --- (Internal) Function to extract (load from the field) troops into a heli. + -- @param #CTLD self + -- @param Wrapper.Group#GROUP Group + -- @param Wrapper.Unit#UNIT Unit + -- @param #CTLD_CARGO Cargotype + function CTLD:_ExtractTroops(Group, Unit) + self:T(self.lid .. " _ExtractTroops") + -- landed or hovering over load zone? + local grounded = not self:IsUnitInAir(Unit) + local hoverload = self:CanHoverLoad(Unit) + + if not grounded and not hoverload then + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + if not self.debug then return self end + end + -- load troops into heli + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load troops + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) + local cantroops = capabilities.troops -- #boolean + local trooplimit = capabilities.trooplimit -- #number + local unitcoord = unit:GetCoordinate() + + -- find nearest group of deployed troops + local nearestGroup = nil + local nearestGroupIndex = -1 + local nearestDistance = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + if nearestGroup == nil or nearestDistance > self.CrateDistance then + self:_SendMessage("No units close enough to extract!", 10, false, Group) + return self + end + -- find matching cargo type + local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") + local Cargotype = nil + for k,v in pairs(self.Cargo_Troops) do + if v.Name == groupType then + Cargotype = v + break + end + end + + if Cargotype == nil then + self:_SendMessage("Can't find a matching cargo type for " .. groupType, 10, false, Group) + return self + end + + local troopsize = Cargotype:GetCratesNeeded() -- #number + -- have we loaded stuff already? + local numberonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Troopsloaded or 0 + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + if troopsize + numberonboard > trooplimit then + self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) + return + else + self.CargoCounter = self.CargoCounter + 1 + local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, CTLD_CARGO.Enum.TROOPS, true, true, Cargotype.CratesNeeded) + self:T({cargotype=loadcargotype}) + loaded.Troopsloaded = loaded.Troopsloaded + troopsize + table.insert(loaded.Cargo,loadcargotype) + self.Loaded_Cargo[unitname] = loaded + self:_SendMessage("Troops boarded!", 10, false, Group) + self:__TroopsExtracted(1,Group, Unit, nearestGroup) + + -- clean up: + table.remove(self.DroppedTroops, nearestGroupIndex) + nearestGroup:Destroy() + end + return self + end + --- (Internal) Function to spawn crates in front of the heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1824,6 +1926,7 @@ function CTLD:_RefreshF10Menus() menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) end local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() + local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() end local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) if unittype == "Hercules" then @@ -2542,6 +2645,21 @@ end return self end + --- (Internal) FSM Function onbeforeTroopsExtracted. + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @return #CTLD self + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + self:T({From, Event, To}) + return self + end + + --- (Internal) FSM Function onbeforeTroopsDeployed. -- @param #CTLD self -- @param #string From State. From 96d1d3cb660b786c6ea8e73ada70c933a600d6a5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 18 Jul 2021 14:53:00 +0200 Subject: [PATCH 366/382] Light code cleanup, added docu for troop extract --- Moose Development/Moose/Ops/CTLD.lua | 238 ++++----------------------- 1 file changed, 30 insertions(+), 208 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 6fa3ff5f9..3488ee961 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -290,7 +290,7 @@ do -- ["SA342M"] = {type="SA342M", crates=false, troops=true, cratelimit = 0, trooplimit = 4}, -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2}, -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, --- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, +-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12}, -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0}, -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 1, trooplimit = 8}, @@ -336,8 +336,16 @@ do -- function my_ctld:OnAfterTroopsDeployed(From, Event, To, Group, Unit, Troops) -- ... your code here ... -- end +-- +-- ## 3.4 OnAfterTroopsExtracted -- --- ## 3.4 OnAfterCratesDropped +-- This function is called when a player has re-boarded already deployed troops from the field: +-- +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops) +-- ... your code here ... +-- end +-- +-- ## 3.5 OnAfterCratesDropped -- -- This function is called when a player has deployed crates to a DROP zone: -- @@ -345,7 +353,7 @@ do -- ... your code here ... -- end -- --- ## 3.5 OnAfterCratesBuild +-- ## 3.6 OnAfterCratesBuild -- -- This function is called when a player has build a vehicle or FOB: -- @@ -353,7 +361,7 @@ do -- ... your code here ... -- end -- --- ## 3.6 A simple SCORING example: +-- ## 3.7 A simple SCORING example: -- -- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport) -- @@ -381,7 +389,8 @@ do -- -- ## 4.2 Manage Troops -- --- Use this entry to load and drop troops. +-- Use this entry to load, drop and extract troops. NOTE - with extract you can only load troops from the field that were deployed prior. +-- Currently limited CTLD_CARGO troops, which are build from **one** template. Also, this will heal/complete your units as they are respawned. -- -- ## 4.3 List boarded cargo -- @@ -519,7 +528,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r1" +CTLD.version="0.1.4r2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -577,7 +586,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- CTLD status update. self:AddTransition("*", "TroopsPickedUp", "*") -- CTLD pickup event. - self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. + self:AddTransition("*", "TroopsExtracted", "*") -- CTLD extract event. self:AddTransition("*", "CratesPickedUp", "*") -- CTLD pickup event. self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. @@ -596,7 +605,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self.UsedVHFFrequencies = {} self.UsedUHFFrequencies = {} self.UsedFMFrequencies = {} - --self.jtacGeneratedLaserCodes = {} -- radio beacons self.RadioSound = "beacon.ogg" @@ -651,7 +659,6 @@ function CTLD:New(Coalition, Prefixes, Alias) self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() - --self:_GenerateLaserCodes() -- curr unused ------------------------ --- Pseudo Functions --- @@ -694,16 +701,16 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD_CARGO Cargo Cargo troops. -- @return #CTLD self - --- FSM Function OnAfterTroopsExtracted. - -- @function [parent=#CTLD] OnAfterTroopsExtracted - -- @param #CTLD self - -- @param #string From State. - -- @param #string Event Trigger. - -- @param #string To State. - -- @param Wrapper.Group#GROUP Group Group Object. - -- @param Wrapper.Unit#UNIT Unit Unit Object. - -- @param #CTLD_CARGO Cargo Cargo troops. - -- @return #CTLD self + --- FSM Function OnAfterTroopsExtracted. + -- @function [parent=#CTLD] OnAfterTroopsExtracted + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #CTLD_CARGO Cargo Cargo troops. + -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. -- @function [parent=#CTLD] OnAfterCratesPickedUp @@ -793,14 +800,6 @@ function CTLD:_GenerateUHFrequencies() self:T(self.lid .. " _GenerateUHFrequencies") self.FreeUHFFrequencies = {} self.FreeUHFFrequencies = UTILS.GenerateUHFrequencies() - --[[ - local _start = 220000000 - - while _start < 399000000 do - table.insert(self.FreeUHFFrequencies, _start) - _start = _start + 500000 - end - --]] return self end @@ -810,16 +809,6 @@ function CTLD:_GenerateFMFrequencies() self:T(self.lid .. " _GenerateFMrequencies") self.FreeFMFrequencies = {} self.FreeFMFrequencies = UTILS.GenerateFMFrequencies() - --[[ - for _first = 3, 7 do - for _second = 0, 5 do - for _third = 0, 9 do - local _frequency = ((100 * _first) + (10 * _second) + _third) * 100000 --extra 0 because we didnt bother with 4th digit - table.insert(self.FreeFMFrequencies, _frequency) - end - end - end - --]] return self end @@ -827,121 +816,16 @@ end -- @param #CTLD self function CTLD:_GenerateVHFrequencies() self:T(self.lid .. " _GenerateVHFrequencies") - self.FreeVHFFrequencies = {} self.UsedVHFFrequencies = {} - self.FreeVHFFrequencies = UTILS.GenerateVHFrequencies() - - --[[ - local _skipFrequencies = self.SkipFrequencies - - self.FreeVHFFrequencies = {} - self.UsedVHFFrequencies = {} - - -- first range - local _start = 200000 - while _start < 400000 do - - -- skip existing NDB frequencies# - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- second range - _start = 400000 - while _start < 850000 do - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 10000 - end - - -- third range - _start = 850000 - while _start <= 999000 do -- adjusted for Gazelle - -- skip existing NDB frequencies - local _found = false - for _, value in pairs(_skipFrequencies) do - if value * 1000 == _start then - _found = true - break - end - end - if _found == false then - table.insert(self.FreeVHFFrequencies, _start) - end - _start = _start + 50000 - end - --]] return self end ---[[ -- unused ---- (Internal) Function to generate valid laser codes. --- @param #CTLD self -function CTLD:_GenerateLaserCodes() - self:T(self.lid .. " _GenerateLaserCodes") - self.jtacGeneratedLaserCodes = {} - -- generate list of laser codes - local _code = 1111 - local _count = 1 - while _code < 1777 and _count < 30 do - while true do - _code = _code + 1 - if not self:_ContainsDigit(_code, 8) - and not self:_ContainsDigit(_code, 9) - and not self:_ContainsDigit(_code, 0) then - table.insert(self.jtacGeneratedLaserCodes, _code) - break - end - end - _count = _count + 1 - end -end - ---- (Internal) Helper function to generate laser codes. --- @param #CTLD self --- @param #number _number --- @param #number _numberToFind -function CTLD:_ContainsDigit(_number, _numberToFind) - self:T(self.lid .. " _ContainsDigit") - local _thisNumber = _number - local _thisDigit = 0 - while _thisNumber ~= 0 do - _thisDigit = _thisNumber % 10 - _thisNumber = math.floor(_thisNumber / 10) - if _thisDigit == _numberToFind then - return true - end - end - return false -end - ---]] - --- (Internal) Event handler function -- @param #CTLD self -- @param Core.Event#EVENTDATA EventData function CTLD:_EventHandler(EventData) - -- TODO: events dead and playerleaveunit - nil table entries self:T(string.format("%s Event = %d",self.lid, EventData.id)) local event = EventData -- Core.Event#EVENTDATA if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then @@ -960,6 +844,7 @@ function CTLD:_EventHandler(EventData) -- Herc support --self:T_unit:GetTypeName()) if _unit:GetTypeName() == "Hercules" and self.enableHercules then + self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end return @@ -1003,7 +888,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) if not self.debug then return self end elseif not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) - --local m = MESSAGE:New("You need to land or hover in position to load!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end end -- load troops into heli @@ -1012,10 +896,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local unitname = unit:GetName() local cargotype = Cargotype -- #CTLD_CARGO local cratename = cargotype:GetName() -- #string - --self:Tself.lid .. string.format("Troops %s requested", cratename)) -- see if this heli can load troops local unittype = unit:GetTypeName() - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local capabilities = self:_GetUnitCapabilities(Unit) local cantroops = capabilities.troops -- #boolean local trooplimit = capabilities.trooplimit -- #number @@ -1034,7 +916,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) end if troopsize + numberonboard > trooplimit then self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) - --local m = MESSAGE:New("Sorry, we\'re crammed already!",10,"CTLD",true):ToGroup(group) return else self.CargoCounter = self.CargoCounter + 1 @@ -1044,7 +925,6 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded self:_SendMessage("Troops boarded!", 10, false, Group) - --local m = MESSAGE:New("Troops boarded!",10,"CTLD",true):ToGroup(group) self:__TroopsPickedUp(1,Group, Unit, Cargotype) end return self @@ -1055,7 +935,7 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #CTLD_CARGO Cargotype - function CTLD:_ExtractTroops(Group, Unit) + function CTLD:_ExtractTroops(Group, Unit) -- #1574 thanks to @bbirchnz! self:T(self.lid .. " _ExtractTroops") -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) @@ -1150,7 +1030,6 @@ end function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self:T(self.lid .. " _GetCrates") local cgoname = Cargo:GetName() - --self:T{cgoname, number, drop}) -- check if we are in LOAD zone local inzone = false local drop = drop or false @@ -1176,7 +1055,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist) if numbernearby >= canloadcratesno and not drop then self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group) - return self end -- spawn crates in front of helicopter @@ -1311,9 +1189,6 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist) end end end - --self:Tstring.format("Found crates = %d",index)) - -- table.sort(found) - --self:T({found}) return found, index end @@ -1344,13 +1219,10 @@ function CTLD:_LoadCratesNearby(Group, Unit) ----------------------------------------- if not cancrates then self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) - --local m = MESSAGE:New("Sorry this chopper cannot carry crates!",10,"CTLD"):ToGroup(Group) elseif self.forcehoverload and not canhoverload then self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) elseif not grounded and not canhoverload then self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) - --local m = MESSAGE:New("Land or hover over the crates to pick them up!",10,"CTLD"):ToGroup(Group) else -- have we loaded stuff already? local numberonboard = 0 @@ -1369,7 +1241,6 @@ function CTLD:_LoadCratesNearby(Group, Unit) local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist) -- #table if number == 0 or numberonboard == cratelimit then self:_SendMessage("Sorry no loadable crates nearby or fully loaded!", 10, false, Group) - --local m = MESSAGE:New("Sorry no loadable crates nearby or fully loaded!",10,"CTLD"):ToGroup(Group) return -- exit else -- go through crates and load @@ -1377,13 +1248,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) local crateidsloaded = {} local loops = 0 while loaded.Cratesloaded < cratelimit and loops < number do - --for _ind,_crate in pairs (nearcrates) do loops = loops + 1 local crateind = 0 -- get crate with largest index for _ind,_crate in pairs (nearcrates) do if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - --crate = _crate crateind = _crate:GetID() end end @@ -1398,10 +1267,8 @@ function CTLD:_LoadCratesNearby(Group, Unit) crate:GetPositionable():Destroy() crate.Positionable = nil self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - --local m = MESSAGE:New(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()),10,"CTLD"):ToGroup(Group) self:__CratesPickedUp(1, Group, Unit, crate) end - --if loaded.Cratesloaded == cratelimit then break end end self.Loaded_Cargo[unitname] = loaded -- clean up real world crates @@ -1433,7 +1300,6 @@ function CTLD:_ListCargo(Group, Unit) local unitname = Unit:GetName() local unittype = Unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local trooplimit = capabilities.trooplimit -- #boolean local cratelimit = capabilities.cratelimit -- #number local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo @@ -1473,10 +1339,8 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) else self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit), 10, false, Group) - --local m = MESSAGE:New(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d",trooplimit,cratelimit),10,"CTLD"):ToGroup(Group) end return self end @@ -1550,13 +1414,11 @@ function CTLD:_UnloadTroops(Group, Unit) end -- template loop cargo:SetWasDropped(true) self:_SendMessage(string.format("Dropped Troops %s into action!",name), 10, false, Group) - --local m = MESSAGE:New(string.format("Dropped Troops %s into action!",name),10,"CTLD"):ToGroup(Group) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter]) end -- if type end end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) - --local m = MESSAGE:New("Troops have returned to base!",15,"CTLD"):ToGroup(Group) self:__TroopsRTB(1, Group, Unit) end -- cleanup load list @@ -1570,7 +1432,6 @@ function CTLD:_UnloadTroops(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum local dropped = cargo:WasDropped() - --local moved = cargo:HasMoved() if type ~= CTLD_CARGO.Enum.TROOP and not dropped then table.insert(loaded.Cargo,_cargo) loaded.Cratesloaded = loaded.Cratesloaded + 1 @@ -1581,10 +1442,8 @@ function CTLD:_UnloadTroops(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1602,7 +1461,6 @@ function CTLD:_UnloadCrates(Group, Unit) local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.DROP) if not inzone then self:_SendMessage("You are not close enough to a drop zone!", 10, false, Group) - --local m = MESSAGE:New("You are not close enough to a drop zone!",15,"CTLD"):ToGroup(Group) if not self.debug then return self end @@ -1631,8 +1489,6 @@ function CTLD:_UnloadCrates(Group, Unit) self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) cargo:SetHasMoved(true) - --local name cargo:GetName() - --local m = MESSAGE:New(string.format("Dropped Crate for %s!",name),10,"CTLD"):ToGroup(Group) end end -- cleanup load list @@ -1654,10 +1510,8 @@ function CTLD:_UnloadCrates(Group, Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not within airdrop parameters!",10,"CTLD"):ToGroup(Group) else self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) - --local m = MESSAGE:New("Nothing loaded or not hovering within parameters!",10,"CTLD"):ToGroup(Group) end end return self @@ -1718,7 +1572,6 @@ function CTLD:_BuildCrates(Group, Unit) if build.CanBuild then txtok = "YES" end - --self:T{name,needed,found,txtok}) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables @@ -1726,7 +1579,6 @@ function CTLD:_BuildCrates(Group, Unit) report:Add("------------------------------------------------------------") local text = report:Text() self:_SendMessage(text, 30, true, Group) - --local m = MESSAGE:New(text,30,"CTLD",true):ToGroup(Group) -- let\'s get going if canbuild then -- loop again @@ -1740,7 +1592,6 @@ function CTLD:_BuildCrates(Group, Unit) end else self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) - --local m = MESSAGE:New(string.format("No crates within %d meters!",finddist),15,"CTLD",true):ToGroup(Group) end -- number > 0 return self end @@ -1784,17 +1635,14 @@ function CTLD:_MoveGroupToZone(Group) self:T(self.lid .. " _MoveGroupToZone") local groupname = Group:GetName() or "none" local groupcoord = Group:GetCoordinate() - --self:Tself.lid .. " _MoveGroupToZone for " .. groupname) -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) --self:Tstring.format("Closest WP zone %s is %d meters",name,distance)) if (distance <= self.movetroopsdistance) and zone then -- yes, we can ;) local groupname = Group:GetName() - --self:Tstring.format("Moving troops %s to zone %s, distance %d!",groupname,name,distance)) local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local coordinate = zonecoord:GetVec2() - --self:T{coordinate=coordinate}) Group:SetAIOn() Group:OptionAlarmStateAuto() Group:OptionDisperseOnAttack(30) @@ -1813,7 +1661,6 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) self:T(self.lid .. " _CleanUpCrates") -- clean up real world crates local build = Build -- #CTLD.Buildable - --self:T{Build = Build}) local existingcrates = self.Spawned_Cargo -- #table of exising crates local newexcrates = {} -- get right number of crates to destroy @@ -1827,18 +1674,15 @@ function CTLD:_CleanUpCrates(Crates,Build,Number) for _,_crate in pairs(Crates) do local nowcrate = _crate -- #CTLD_CARGO local name = nowcrate:GetName() - --self:Tstring.format("Looking for Crate for %s", name)) local thisID = nowcrate:GetID() if name == nametype then -- matching crate type table.insert(destIDs,thisID) found = found + 1 nowcrate:GetPositionable():Destroy() nowcrate.Positionable = nil - --self:Tstring.format("%s Found %d Need %d", name, found, numberdest)) end if found == numberdest then break end -- got enough end - --self:T{destIDs}) -- loop and remove from real world representation for _,_crate in pairs(existingcrates) do local excrate = _crate -- #CTLD_CARGO @@ -1863,7 +1707,6 @@ function CTLD:_RefreshF10Menus() self:T(self.lid .. " _RefreshF10Menus") local PlayerSet = self.PilotGroups -- Core.Set#SET_GROUP local PlayerTable = PlayerSet:GetSetObjects() -- #table of #GROUP objects - --self:T({PlayerTable=PlayerTable}) -- rebuild units table local _UnitList = {} for _key, _group in pairs (PlayerTable) do @@ -1880,7 +1723,6 @@ function CTLD:_RefreshF10Menus() self.CtldUnits = _UnitList -- build unit menus - local menucount = 0 local menus = {} for _, _unitName in pairs(self.CtldUnits) do @@ -1892,7 +1734,6 @@ function CTLD:_RefreshF10Menus() -- get chopper capabilities local unittype = _unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cantroops = capabilities.troops local cancrates = capabilities.crates -- top menu @@ -1982,13 +1823,10 @@ function CTLD:AddZone(Zone) local zone = Zone -- #CTLD.CargoZone if zone.type == CTLD.CargoZoneType.LOAD then table.insert(self.pickupZones,zone) - --self:T"Registered LOAD zone " .. zone.name) elseif zone.type == CTLD.CargoZoneType.DROP then table.insert(self.dropOffZones,zone) - --self:T"Registered DROP zone " .. zone.name) else table.insert(self.wpZones,zone) - --self:T"Registered MOVE zone " .. zone.name) end return self end @@ -2053,7 +1891,6 @@ function CTLD:_GetFMBeacon(Name) beacon.name = Name beacon.frequency = FM / 1000000 beacon.modulation = radio.modulation.FM - return beacon end @@ -2164,7 +2001,6 @@ function CTLD:_ListRadioBeacons(Group, Unit) end report:Add("------------------------------------------------------------") self:_SendMessage(report:Text(), 30, true, Group) - --local m = MESSAGE:New(report:Text(),30,"CTLD",true):ToGroup(Group) return self end @@ -2227,7 +2063,6 @@ end function CTLD:IsUnitInZone(Unit,Zonetype) self:T(self.lid .. " IsUnitInZone") local unitname = Unit:GetName() - --self:Tstring.format("%s | Zone search for %s | Type %s",self.lid,unitname,Zonetype)) local zonetable = {} local outcome = false if Zonetype == CTLD.CargoZoneType.LOAD then @@ -2253,7 +2088,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) local color = czone.color local zoneradius = zone:GetRadius() local distance = self:_GetDistance(zonecoord,unitcoord) - --self:Tstring.format("Check distance: %d",distance)) if distance <= zoneradius and active then outcome = true end @@ -2264,7 +2098,6 @@ function CTLD:IsUnitInZone(Unit,Zonetype) colorret = color end end - --self:T{outcome, zonenameret, zoneret, maxdist}) return outcome, zonenameret, zoneret, maxdist end @@ -2300,7 +2133,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local txt = "smoking" if Flare then txt = "flaring" end self:_SendMessage(string.format("Roger, %s zone %s!",txt, zonename), 10, false, Group) - --local m = MESSAGE:New(string.format("Roger, %s zone %s!",txt, zonename),10,"CTLD"):ToGroup(Group) smoked = true end end @@ -2308,7 +2140,6 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) if not smoked then local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) - --local m = MESSAGE:New(string.format("Negative, need to be closer than %dnm to a zone!",distance),10,"CTLD"):ToGroup(Group) end return self end @@ -2361,7 +2192,6 @@ end local maxh = self.maximumHoverHeight -- 15 local minh = self.minimumHoverHeight -- 5 local mspeed = 2 -- 2 m/s - --self:Tstring.format("%s Unit parameters: at %dm AGL with %dmps",self.lid,aheight,uspeed)) if (uspeed <= mspeed) and (aheight <= maxh) and (aheight >= minh) then -- yep within parameters outcome = true @@ -2388,7 +2218,7 @@ end local maxh = self.HercMinAngels-- 1500m local minh = self.HercMaxAngels -- 5000m local maxspeed = self.HercMaxSpeed -- 77 mps - -- TODO: TEST - Speed test for Herc, should not be above 280kph/150kn + -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 self:T(string.format("%s Unit parameters: at %dm AGL with %dmps | %dkph | %dkn",self.lid,aheight,uspeed,kmspeed,knspeed)) @@ -2417,7 +2247,6 @@ end text = string.format("Hover parameters (autoload/drop):\n - Min height %dm \n - Max height %dm \n - Max speed 6fts \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,10,"CTLD",false):ToGroup(Group) return self end @@ -2440,7 +2269,6 @@ end text = string.format("Flight parameters (airdrop):\n - Min height %dm \n - Max height %dm \n - In parameter: %s", minheight, maxheight, htxt) end self:_SendMessage(text, 10, false, Group) - --local m = MESSAGE:New(text,15,"CTLD",false):ToGroup(Group) return self end @@ -2488,7 +2316,6 @@ end local unitname = Unit:GetName() local Group = Unit:GetGroup() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities - --local capabilities = self.UnitTypes[unittype] -- #CTLD.UnitCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number if cancrates then @@ -2536,16 +2363,12 @@ end self:I(self.lid .. "Started.") if self.useprefix or self.enableHercules then local prefix = self.prefixes - --self:T{prefix=prefix}) if self.enableHercules then - --self:T("CTLD with prefixes and Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else - --self:T("CTLD with prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() end else - --self:T("CTLD NO prefixes NO Hercules") self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategories("helicopter"):FilterStart() end -- Events @@ -2581,8 +2404,7 @@ end -- gather some stats -- pilots local pilots = 0 - for _,_pilot in pairs (self.CtldUnits) do - + for _,_pilot in pairs (self.CtldUnits) do pilots = pilots + 1 end From 6690f70b05b28cb8df047df66462dd5b03a844ba Mon Sep 17 00:00:00 2001 From: bbirchnz Date: Mon, 19 Jul 2021 15:16:21 +1000 Subject: [PATCH 367/382] fix CTLD:ActivateZone not processing default correctly and using wrong zone table (#1575) --- Moose Development/Moose/Ops/CTLD.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3488ee961..33c33a9d5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1840,15 +1840,15 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) self:T(self.lid .. " AddZone") local newstate = true -- set optional in case we\'re deactivating - if not NewState or NewState == false then - newstate = false - end + if NewState ~= nil then + newstate = NewState + end + -- get correct table - local zone = ZoneType -- #CTLD.CargoZone local table = {} - if zone.type == CTLD.CargoZoneType.LOAD then + if ZoneType == CTLD.CargoZoneType.LOAD then table = self.pickupZones - elseif zone.type == CTLD.CargoZoneType.DROP then + elseif ZoneType == CTLD.CargoZoneType.DROP then table = self.dropOffZones else table = self.wpZones @@ -1864,6 +1864,7 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) return self end + --- User function - Deactivate Name #CTLD.CargoZoneType ZoneType for this CTLD instance. -- @param #CTLD self -- @param #string Name Name of the zone to change in the ME. From 8a539982517777f57e2fc95392350f6c8558ac0f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 20 Jul 2021 18:30:06 +0200 Subject: [PATCH 368/382] MANTIS - make start random async, so detection for multiple MANTIS objects works better INTEL - added function to alter detection types, typos corrected etc --- Moose Development/Moose/Functional/Mantis.lua | 2 +- Moose Development/Moose/Ops/Intelligence.lua | 89 +++++++++++++------ 2 files changed, 65 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index 3b185043c..c381fc5f8 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -1081,7 +1081,7 @@ do if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end - self:__Status(self.detectinterval) + self:__Status(-math.random(1,10)) return self end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 7ee0a79fd..40ae21786 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -56,30 +56,30 @@ -- -- ## set up a detection SET_GROUP -- --- `Red_DetectionSetGroup = SET_GROUP:New()` --- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )` --- `Red_DetectionSetGroup:FilterOnce()` +-- `Red_DetectionSetGroup = SET_GROUP:New()` +-- `Red_DetectionSetGroup:FilterPrefixes( { "Red EWR" } )` +-- `Red_DetectionSetGroup:FilterOnce()` -- -- ## New Intel type detection for the red side, logname "KGB" -- --- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")` --- `RedIntel:SetClusterAnalysis(true,true)` --- `RedIntel:SetVerbosity(2)` --- `RedIntel:Start()` +-- `RedIntel = INTEL:New(Red_DetectionSetGroup,"red","KGB")` +-- `RedIntel:SetClusterAnalysis(true,true)` +-- `RedIntel:SetVerbosity(2)` +-- `RedIntel:__Start(2)` -- -- ## Hook into new contacts found -- --- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)` --- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")` --- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` --- `end` +-- `function RedIntel:OnAfterNewContact(From, Event, To, Contact)` +-- `local text = string.format("NEW contact %s detected by %s", Contact.groupname, Contact.recce or "unknown")` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` -- -- ## And/or new clusters found -- --- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)` --- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)` --- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` --- `end` +-- `function RedIntel:OnAfterNewCluster(From, Event, To, Contact, Cluster)` +-- `local text = string.format("NEW cluster %d size %d with contact %s", Cluster.index, Cluster.size, Contact.groupname)` +-- `local m = MESSAGE:New(text,15,"KGB"):ToAll()` +-- `end` -- -- -- @field #INTEL @@ -116,7 +116,7 @@ INTEL = { -- @field #number speed Last known speed in m/s. -- @field #boolean isship -- @field #boolean ishelo --- @field #boolean isgrund +-- @field #boolean isground -- @field Ops.Auftrag#AUFTRAG mission The current Auftrag attached to this contact -- @field #string recce The name of the recce unit that detected this contact @@ -135,13 +135,13 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.2" +INTEL.version="0.2.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Filter detection methods. +-- DONE: Filter detection methods. -- TODO: process detected set asynchroniously for better performance. -- DONE: Accept zones. -- DONE: Reject zones. @@ -203,7 +203,14 @@ function INTEL:New(DetectionSet, Coalition, Alias) self.alias="CIA" end end - end + end + + self.DetectVisual = true + self.DetectOptical = true + self.DetectRadar = true + self.DetectIRST = true + self.DetectRWR = true + self.DetectDLINK = true -- Set some string id for output to DCS.log file. self.lid=string.format("INTEL %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -475,6 +482,39 @@ function INTEL:SetClusterRadius(radius) return self end +--- Set detection types for this #INTEL - all default to true. +-- @param #INTEL self +-- @param #boolean DetectVisual Visual detection +-- @param #boolean DetectOptical Optical detection +-- @param #boolean DetectRadar Radar detection +-- @param #boolean DetectIRST IRST detection +-- @param #boolean DetectRWR RWR detection +-- @param #boolean DetectDLINK Data link detection +-- @return self +function INTEL:SetDetectionTypes(DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK) + self.DetectVisual = DetectVisual and true + self.DetectOptical = DetectOptical and true + self.DetectRadar = DetectRadar and true + self.DetectIRST = DetectIRST and true + self.DetectRWR = DetectRWR and true + self.DetectDLINK = DetectDLINK and true + return self +end + +--- Get table of #INTEL.Contact objects +-- @param #INTEL self +-- @return #table Contacts +function INTEL:GetContactTable() + return self.Contacts +end + +--- Get table of #INTEL.Cluster objects +-- @param #INTEL self +-- @return #table Clusters +function INTEL:GetClusterTable() + return self.Clusters +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -530,7 +570,7 @@ function INTEL:onafterStatus(From, Event, To) text=text..string.format("\n- %s (%s): %s, units=%d, T=%d sec", contact.categoryname, contact.attribute, contact.groupname, contact.group:CountAliveUnits(), dT) if contact.mission then local mission=contact.mission --Ops.Auftrag#AUFTRAG - text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown") end end self:I(self.lid..text) @@ -558,14 +598,13 @@ function INTEL:UpdateIntel() local recce=_recce --Wrapper.Unit#UNIT -- Get detected units. - self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting) + self:GetDetectedUnits(recce, DetectedUnits, RecceDetecting, self.DetectVisual, self.DetectOptical, self.DetectRadar, self.DetectIRST, self.DetectRWR, self.DetectDLINK) end end end - -- TODO: Filter detection methods? local remove={} for unitname,_unit in pairs(DetectedUnits) do local unit=_unit --Wrapper.Unit#UNIT @@ -700,7 +739,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) item.velocity=group:GetVelocityVec3() item.speed=group:GetVelocityMPS() item.recce=RecceDetecting[groupname] - self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknonw", item.recce or "unknown")) + self:T(string.format("%s group detect by %s/%s", groupname, RecceDetecting[groupname] or "unknown", item.recce or "unknown")) -- Add contact to table. self:AddContact(item) @@ -728,7 +767,7 @@ function INTEL:CreateDetectedItems(DetectedGroups, RecceDetecting) end ---- Return the detected target groups of the controllable as a @{SET_GROUP}. +--- (Internal) Return the detected target groups of the controllable as a @{SET_GROUP}. -- The optional parametes specify the detection methods that can be applied. -- If no detection method is given, the detection will use all the available methods by default. -- @param #INTEL self @@ -815,7 +854,7 @@ function INTEL:onafterLostCluster(From, Event, To, Cluster, Mission) local text = self.lid..string.format("LOST cluster %d", Cluster.index) if Mission then local mission=Mission --Ops.Auftrag#AUFTRAG - text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unkown") + text=text..string.format(" mission name=%s type=%s target=%s", mission.name, mission.type, mission:GetTargetName() or "unknown") end self:T(text) end From 4dfaca610f04882493c6c1883f6d5c47390bdf82 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Jul 2021 18:22:31 +0200 Subject: [PATCH 369/382] INTEL - bug fixes, added new datalink class --- Moose Development/Moose/Ops/Intelligence.lua | 266 ++++++++++++++++++- 1 file changed, 260 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 40ae21786..98c62cd68 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -135,7 +135,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.5" +INTEL.version="0.2.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -229,7 +229,8 @@ function INTEL:New(DetectionSet, Coalition, Alias) self:AddTransition("*", "LostContact", "*") -- Contact could not be detected any more. self:AddTransition("*", "NewCluster", "*") -- New cluster has been detected. - self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. + self:AddTransition("*", "LostCluster", "*") -- Cluster could not be detected any more. + self:AddTransition("*", "Stop", "Stopped") -- Defaults self:SetForgetTime() @@ -503,16 +504,24 @@ end --- Get table of #INTEL.Contact objects -- @param #INTEL self --- @return #table Contacts +-- @return #table Contacts or nil if not running function INTEL:GetContactTable() - return self.Contacts + if self:Is("Running") then + return self.Contacts + else + return nil + end end --- Get table of #INTEL.Cluster objects -- @param #INTEL self --- @return #table Clusters +-- @return #table Clusters or nil if not running function INTEL:GetClusterTable() - return self.Clusters + if self:Is("Running") and self.clusteranalysis then + return self.Clusters + else + return nil + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1430,3 +1439,248 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +---------------------------------------------------------------------------------------------- +-- Start INTEL_DLINK +---------------------------------------------------------------------------------------------- + +--- **Ops_DLink** - Support for Office of Military Intelligence. +-- +-- **Main Features:** +-- +-- * Overcome limitations of (non-available) datalinks between ground radars +-- * Detect and track contacts consistently across INTEL instances +-- * Use FSM events to link functionality into your scripts +-- * Easy setup +-- +--- === +-- +-- ### Author: **applevangelist** +-- @module Ops.Intel_DLink +-- @image OPS_Intel.png + +--- INTEL_DLINK class. +-- @type INTEL_DLINK +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number verbose Make the logging verbose. +-- @field #string alias Alias name for logging. +-- @field #number cachetime Number of seconds to keep an object. +-- @field #number interval Number of seconds between collection runs. +-- @field #table contacts Table of Ops.Intelligence#INTEL.Contact contacts. +-- @field #table clusters Table of Ops.Intelligence#INTEL.Cluster clusters. +-- @field #table contactcoords Table of contacts' Core.Point#COORDINATE objects. +-- @extends Core.Fsm#FSM + +--- INTEL_DLINK data aggregator +-- @field #INTEL_DLINK +INTEL_DLINK = { + ClassName = "INTEL_DLINK", + verbose = 0, + lid = nil, + alias = nil, + cachetime = 300, + interval = 20, + contacts = {}, + clusters = {}, + contactcoords = {}, +} + +--- Version string +-- @field #string version +INTEL_DLINK.version = "0.0.1" + +--- Function to instantiate a new object +-- @param #INTEL_DLINK self +-- @param #table Intels Table of Ops.Intelligence#INTEL objects. +-- @param #string Alias (optional) Name of this instance. Default "SPECTRE" +-- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds). +-- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds). +function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #INTEL + + self.intels = Intels or {} + self.contacts = {} + self.clusters = {} + self.contactcoords = {} + + -- Set alias. + if Alias then + self.alias=tostring(Alias) + else + self.alias="SPECTRE" + end + + -- Cache time + self.cachetime = Cachetime or 300 + + -- Interval + self.interval = Interval or 20 + + -- Set some string id for output to DCS.log file. + self.lid=string.format("INTEL_DLINK %s | ", self.alias) + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Collect", "*") -- Collect data. + self:AddTransition("*", "Collected", "*") -- Collection of data done. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + return self +end +---------------------------------------------------------------------------------------------- +-- Helper & User Functions +---------------------------------------------------------------------------------------------- + +--- Function to add an #INTEL object to the aggregator +-- @param #INTEL_DLINK self +-- @param Ops.Intelligence#INTEL Intel the #INTEL object to add +-- @return #INTEL_DLINK self +function INTEL_DLINK:AddIntel(Intel) + self:T(self.lid .. "AddIntel") + if Intel then + table.insert(self.intels,Intel) + end + return self +end + +---------------------------------------------------------------------------------------------- +-- FSM Functions +---------------------------------------------------------------------------------------------- + +--- Function to start the work. +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onafterStart(From, Event, To) + self:T({From, Event, To}) + local text = string.format("Version %s started.", self.version) + self:I(self.lid .. text) + self:__Collect(-math.random(1,10)) + return self +end + +--- Function to collect data from the various #INTEL +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onbeforeCollect(From, Event, To) + self:T({From, Event, To}) + -- run through our #INTEL objects and gather the contacts tables + self:T("Contacts Data Gathering") + local newcontacts = {} + local intels = self.intels -- #table + for _,_intel in pairs (intels) do + _intel = _intel -- #INTEL + if _intel:Is("Running") then + local ctable = _intel:GetContactTable() or {} -- #INTEL.Contact + for _,_contact in pairs (ctable) do + local _ID = string.format("%s-%d",_contact.groupname, _contact.Tdetected) + self:T(string.format("Adding %s",_ID)) + newcontacts[_ID] = _contact + end + end + end + -- clean up for stale contacts and dupes + self:T("Cleanup") + local contacttable = {} + local coordtable = {} + local TNow = timer.getAbsTime() + local Tcache = self.cachetime + for _ind, _contact in pairs(newcontacts) do -- #string, #INTEL.Contact + if TNow - _contact.Tdetected < Tcache then + if (not contacttable[_contact.groupname]) or (contacttable[_contact.groupname] and contacttable[_contact.groupname].Tdetected < _contact.Tdetected) then + self:T(string.format("Adding %s",_contact.groupname)) + contacttable[_contact.groupname] = _contact + table.insert(coordtable,_contact.position) + end + end + end + -- run through our #INTEL objects and gather the clusters tables + self:T("Clusters Data Gathering") + local newclusters = {} + local intels = self.intels -- #table + for _,_intel in pairs (intels) do + _intel = _intel -- #INTEL + if _intel:Is("Running") then + local ctable = _intel:GetClusterTable() or {} -- #INTEL.Cluster + for _,_cluster in pairs (ctable) do + local _ID = string.format("%s-%d", _intel.alias, _cluster.index) + self:T(string.format("Adding %s",_ID)) + table.insert(newclusters,_cluster) + end + end + end + -- update self tables + self.contacts = contacttable + self.contactcoords = coordtable + self.clusters = newclusters + self:__Collected(1, contacttable, newclusters) -- make table available via FSM Event + -- schedule next round + local interv = self.interval * -1 + self:__Collect(interv) + return self +end + +--- Function called after collection is done +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @param #table Contacts The table of collected #INTEL.Contact contacts +-- @param #table Clusters The table of collected #INTEL.Cluster clusters +-- @return #INTEL_DLINK self +function INTEL_DLINK:onbeforeCollected(From, Event, To, Contacts, Clusters) + self:T({From, Event, To}) + return self +end + +--- Function to stop +-- @param #INTEL_DLINK self +-- @param #string From The From state +-- @param #string Event The Event triggering this call +-- @param #string To The To state +-- @return #INTEL_DLINK self +function INTEL_DLINK:onafterStop(From, Event, To) + self:T({From, Event, To}) + local text = string.format("Version %s stopped.", self.version) + self:I(self.lid .. text) + return self +end + +--- Function to query the detected contacts +-- @param #INTEL_DLINK self +-- @return #table Table of #INTEL.Contact contacts +function INTEL_DLINK:GetContactTable() + self:T(self.lid .. "GetContactTable") + return self.contacts +end + +--- Function to query the detected clusters -- not yet implemented! +-- @param #INTEL_DLINK self +-- @return #table Table of #INTEL.Cluster clusters +function INTEL_DLINK:GetClusterTable() + self:T(self.lid .. "GetClusterTable") + return self.clusters +end + +--- Function to query the detected contact coordinates +-- @param #INTEL_DLINK self +-- @return #table Table of the contacts' Core.Point#COORDINATE objects. +function INTEL_DLINK:GetDetectedItemCoordinates() + self:T(self.lid .. "GetDetectedItemCoordinates") + return self.contactcoords +end + +---------------------------------------------------------------------------------------------- +-- End INTEL_DLINK +---------------------------------------------------------------------------------------------- From 684c4ea113840606c4e3f97b4912be2249a248aa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Jul 2021 18:22:46 +0200 Subject: [PATCH 370/382] Allow MANTIS to use INTEL_DLINK --- Moose Development/Moose/Functional/Mantis.lua | 107 ++++++++++++------ 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index c381fc5f8..6891fd3fd 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -59,7 +59,7 @@ -- @extends Core.Base#BASE ---- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat +--- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frdric Bastiat -- -- Simple Class for a more intelligent Air Defense System -- @@ -195,6 +195,8 @@ MANTIS = { TimeStamp = 0, state2flag = false, SamStateTracker = {}, + DLink = false, + DLTimeStamp = 0, } --- Advanced state enumerator @@ -278,7 +280,8 @@ do self.relointerval = math.random(1800,3600) -- random between 30 and 60 mins self.state2flag = false self.SamStateTracker = {} -- table to hold alert states, so we don't trigger state changes twice in adv mode - + self.DLink = false + if EmOnOff then if EmOnOff == false then self.UseEmOnOff = false @@ -325,7 +328,7 @@ do end -- @field #string version - self.version="0.5.2" + self.version="0.6.2" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -593,7 +596,7 @@ do -- E.g. `mymantis:SetAdvancedMode(true, 90)` function MANTIS:SetAdvancedMode(onoff, ratio) self:T(self.lid .. "SetAdvancedMode") - self:T({onoff, ratio}) + --self.T({onoff, ratio}) local onoff = onoff or false local ratio = ratio or 100 if (type(self.HQ_Template_CC) == "string") and onoff and self.dynamic then @@ -619,6 +622,17 @@ do return self end + --- Set using an #INTEL_DLINK object instead of #DETECTION + -- @param #MANTIS self + -- @param Ops.Intelligence#INTEL_DLINK DLink The data link object to be used. + function MANTIS:SetUsingDLink(DLink) + self:T(self.lid .. "SetUsingDLink") + self.DLink = true + self.Detection = DLink + self.DLTimeStamp = timer.getAbsTime() + return self + end + --- [Internal] Function to check if HQ is alive -- @param #MANTIS self -- @return #boolean True if HQ is alive, else false @@ -633,10 +647,10 @@ do local hqgrp = GROUP:FindByName(hq) if hqgrp then if hqgrp:IsAlive() then -- ok we're on, hq exists and as alive - self:T(self.lid.." HQ is alive!") + --self.T(self.lid.." HQ is alive!") return true else - self:T(self.lid.." HQ is dead!") + --self.T(self.lid.." HQ is dead!") return false end end @@ -650,7 +664,7 @@ do function MANTIS:_CheckEWRState() self:T(self.lid .. "CheckEWRState") local text = self.lid.." Checking EWR State" - self:T(text) + --self.T(text) local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(text) end -- start check @@ -666,7 +680,7 @@ do end end end - self:T(self.lid..string.format(" No of EWR alive is %d", nalive)) + --self.T(self.lid..string.format(" No of EWR alive is %d", nalive)) if nalive > 0 then return true else @@ -682,10 +696,8 @@ do -- @return #number Previous state for tracking 0, 1, or 2 function MANTIS:_CalcAdvState() self:T(self.lid .. "CalcAdvState") - local text = self.lid.." Calculating Advanced State" - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + local m=MESSAGE:New(self.lid.." Calculating Advanced State",10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid.." Calculating Advanced State") end -- start check local currstate = self.adv_state -- save curr state for comparison later local EWR_State = self:_CheckEWRState() @@ -703,10 +715,12 @@ do local ratio = self.adv_ratio / 100 -- e.g. 80/100 = 0.8 ratio = ratio * self.adv_state -- e.g 0.8*2 = 1.6 local newinterval = interval + (interval * ratio) -- e.g. 30+(30*1.6) = 78 - local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) - self:T(text) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(text) end + if self.debug or self.verbose then + local text = self.lid..string.format(" Calculated OldState/NewState/Interval: %d / %d / %d", currstate, self.adv_state, newinterval) + --self.T(text) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(text) end + end return newinterval, currstate end @@ -716,13 +730,13 @@ do -- @param #boolean ewr If true, will relocate EWR objects function MANTIS:SetAutoRelocate(hq, ewr) self:T(self.lid .. "SetAutoRelocate") - self:T({hq, ewr}) + --self.T({hq, ewr}) local hqrel = hq or false local ewrel = ewr or false if hqrel or ewrel then self.autorelocate = true self.autorelocateunits = { HQ = hqrel, EWR = ewrel } - self:T({self.autorelocate, self.autorelocateunits}) + --self.T({self.autorelocate, self.autorelocateunits}) end return self end @@ -739,7 +753,7 @@ do local HQGroup = self.HQ_CC if self.autorelocateunits.HQ and self.HQ_CC and HQGroup:IsAlive() then --only relocate if HQ exists local _hqgrp = self.HQ_CC - self:T(self.lid.." Relocating HQ") + --self.T(self.lid.." Relocating HQ") local text = self.lid.." Relocating HQ" --local m= MESSAGE:New(text,10,"MANTIS"):ToAll() _hqgrp:RelocateGroundRandomInRadius(20,500,true,true) @@ -752,7 +766,7 @@ do local EWR_Grps = EWR_GRP.Set --table of objects in SET_GROUP for _,_grp in pairs (EWR_Grps) do if _grp:IsAlive() and _grp:IsGround() then - self:T(self.lid.." Relocating EWR ".._grp:GetName()) + --self.T(self.lid.." Relocating EWR ".._grp:GetName()) local text = self.lid.." Relocating EWR ".._grp:GetName() local m= MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then self:I(text) end @@ -778,12 +792,14 @@ do for _,_coord in pairs (set) do local coord = _coord -- get current coord to check -- output for cross-check - local dectstring = coord:ToStringLLDMS() - local samstring = samcoordinate:ToStringLLDMS() local targetdistance = samcoordinate:DistanceFromPointVec2(coord) - local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) - local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.verbose or self.debug then + local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringLLDMS() + local text = string.format("Checking SAM at % s - Distance %d m - Target %s", samstring, targetdistance, dectstring) + local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) + self:I(self.lid..text) + end -- end output to cross-check if targetdistance <= radius then return true, targetdistance @@ -999,9 +1015,11 @@ do self:__ShoradActivated(1,name, radius, ontime) end -- debug output - local text = string.format("SAM %s switched to alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state RED!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive else if samgroup:IsAlive() then @@ -1014,9 +1032,11 @@ do self:__GreenState(1,samgroup) self.SamStateTracker[name] = "GREEN" end - local text = string.format("SAM %s switched to alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) - if self.verbose then self:I(self.lid..text) end + if self.debug or self.verbose then + local text = string.format("SAM %s switched to alarm state GREEN!", name) + local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + if self.verbose then self:I(self.lid..text) end + end end --end alive end --end check end --for for loop @@ -1066,6 +1086,20 @@ do end -- end newstate vs oldstate return self end + + --- [Internal] Check DLink state + -- @param #MANTIS self + -- @return #MANTIS self + function MANTIS:_CheckDLinkState() + self:T(self.lid .. "_CheckDLinkState") + local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK + local TS = timer.getAbsTime() + if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then + self.DLink = false + self.Detection = self:StartDetection() -- fall back + self:I(self.lid .. "Intel DLink not running - switching back to single detection!") + end + end --- [Internal] Function to set start state -- @param #MANTIS self @@ -1077,7 +1111,9 @@ do self:T({From, Event, To}) self:T(self.lid.."Starting MANTIS") self:SetSAMStartState() - self.Detection = self:StartDetection() + if not self.DLink then + self.Detection = self:StartDetection() + end if self.advAwacs then self.AWACS_Detection = self:StartAwacsDetection() end @@ -1120,11 +1156,16 @@ do end end - -- timer for advanced state check + -- advanced state check if self.advanced then self:_CheckAdvState() end + -- check DLink state + if self.DLink then + self:_CheckDLinkState() + end + return self end From 552eb5b9d83eaeb57ec1894989946319226f4a56 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Jul 2021 18:40:43 +0200 Subject: [PATCH 371/382] Update CTLD.lua Add housekeeping for dropped troops --- Moose Development/Moose/Ops/CTLD.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 33c33a9d5..9143eacf1 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -528,7 +528,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.4r2" +CTLD.version="0.1.4r3" --- Instantiate a new CTLD. -- @param #CTLD self @@ -961,7 +961,7 @@ end local nearestDistance = 10000000 for k,v in pairs(self.DroppedTroops) do local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) - if distance < nearestDistance then + if distance < nearestDistance and distance ~= -1 then nearestGroup = v nearestGroupIndex = k nearestDistance = distance @@ -2349,6 +2349,20 @@ end return self end + --- (Internal) Run through DroppedTroops and capture alive units + -- @param #CTLD self + -- @return #CTLD self + function CTLD:CleanDroppedTroops() + local troops = self.DroppedTroops + local newtable = {} + for _index, _group in pairs (troops) do + if _group and _group:IsAlive() then + newtable[_index] = _group + end + end + self.DroppedTroops = newtable + return self + end ------------------------------------------------------------------- -- FSM functions ------------------------------------------------------------------- @@ -2388,6 +2402,7 @@ end -- @return #CTLD self function CTLD:onbeforeStatus(From, Event, To) self:T({From, Event, To}) + self:CleanDroppedTroops() self:_RefreshF10Menus() self:_RefreshRadioBeacons() self:CheckAutoHoverload() From d517cba7653683eab27d25803822d93bfdcdf273 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 21 Jul 2021 18:45:57 +0200 Subject: [PATCH 372/382] Update Intelligence.lua no dupe docs --- Moose Development/Moose/Ops/Intelligence.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 98c62cd68..0b98a85ed 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1456,8 +1456,6 @@ end --- === -- -- ### Author: **applevangelist** --- @module Ops.Intel_DLink --- @image OPS_Intel.png --- INTEL_DLINK class. -- @type INTEL_DLINK From f5b25370b0968c64d83cbbd51b91abf6dd215013 Mon Sep 17 00:00:00 2001 From: madmoney99 Date: Wed, 21 Jul 2021 22:51:29 -0700 Subject: [PATCH 373/382] Change to wind measure for Skipper Previous value 50m produced an incorrect heading and strength based on the actual wind over deck. --- Moose Development/Moose/Ops/Airboss.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 2ce869f2c..97e6cb764 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -11639,7 +11639,7 @@ end --- Get wind speed on carrier deck parallel and perpendicular to runway. -- @param #AIRBOSS self --- @param #number alt Altitude in meters. Default 50 m. +-- @param #number alt Altitude in meters. Default 15 m. (change made from 50m from Discord discussion from Sickdog) -- @return #number Wind component parallel to runway im m/s. -- @return #number Wind component perpendicular to runway in m/s. -- @return #number Total wind strength in m/s. @@ -11662,7 +11662,7 @@ function AIRBOSS:GetWindOnDeck(alt) zc=UTILS.Rotate2D(zc, -self.carrierparam.rwyangle) -- Wind (from) vector - local vw=cv:GetWindWithTurbulenceVec3(alt or 50) + local vw=cv:GetWindWithTurbulenceVec3(alt or 15) -- Total wind velocity vector. -- Carrier velocity has to be negative. If carrier drives in the direction the wind is blowing from, we have less wind in total. From 5bebbcf5eb15dba84312977836fe28dc29defdfa Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 22 Jul 2021 11:36:21 +0200 Subject: [PATCH 374/382] Update Intelligence.lua Added FSM functions and documentation for INTEL_DLINK --- Moose Development/Moose/Ops/Intelligence.lua | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 0b98a85ed..0bd198387 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1494,6 +1494,28 @@ INTEL_DLINK.version = "0.0.1" -- @param #string Alias (optional) Name of this instance. Default "SPECTRE" -- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds). -- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds). +-- @usage Use #INTEL_DLINK if you want to merge data from a number of #INTEL objects into one. This might be useful to simulate a Data Link, e.g. for Russian-tech based EWR, +-- realising a Star Topology @{https://en.wikipedia.org/wiki/Network_topology#Star} in a basic setup. +-- It will collect the contacts and clusters from the #INTEL objects. +-- Contact duplicates are removed. Clusters might contain duplicates (Might fix that later, WIP). +-- +-- Basic setup: +-- local datalink = INTEL_DLINK:New({myintel1,myintel2}), "FSB", 20, 300) +-- datalink:__Start(2) +-- +-- Add an Intel while running: +-- datalink:AddIntel(myintel3) +-- +-- Gather the data: +-- datalink:GetContactTable() -- #table of #INTEL.Contact contacts. +-- datalink:GetClusterTable() -- #table of #INTEL.Cluster clusters. +-- datalink:GetDetectedItemCoordinates() -- #table of contact coordinates, to be compatible with @{Functional.Detection#DETECTION}. +-- +-- Gather data with the event function: +-- function datalink:OnAfterCollected(From, Event, To, Contacts, Clusters) +-- ... ... +-- end +-- function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #INTEL @@ -1529,6 +1551,44 @@ function INTEL_DLINK:New(Intels, Alias, Interval, Cachetime) self:AddTransition("*", "Collected", "*") -- Collection of data done. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + ---------------------------------------------------------------------------------------------- + -- Pseudo Functions + ---------------------------------------------------------------------------------------------- + --- Triggers the FSM event "Start". Starts the INTEL_DLINK. + -- @function [parent=#INTEL_DLINK] Start + -- @param #INTEL_DLINK self + + --- Triggers the FSM event "Start" after a delay. Starts the INTEL_DLINK. + -- @function [parent=#INTEL_DLINK] __Start + -- @param #INTEL_DLINK self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the INTEL_DLINK. + -- @param #INTEL_DLINK self + + --- Triggers the FSM event "Stop" after a delay. Stops the INTEL_DLINK. + -- @function [parent=#INTEL_DLINK] __Stop + -- @param #INTEL_DLINK self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Collect". Used internally to collect all data. + -- @function [parent=#INTEL_DLINK] Collect + -- @param #INTEL_DLINK self + + --- Triggers the FSM event "Collect" after a delay. + -- @function [parent=#INTEL_DLINK] __Status + -- @param #INTEL_DLINK self + -- @param #number delay Delay in seconds. + + --- On After "Collected" event. Data tables have been refreshed. + -- @function [parent=#INTEL_DLINK] OnAfterCollected + -- @param #INTEL_DLINK self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table Contacts Table of #INTEL.Contact Contacts. + -- @param #table Clusters Table of #INTEL.Cluster Clusters. + return self end ---------------------------------------------------------------------------------------------- From 49397df90b8f2fab3f11bc178fae5b3cff3ee100 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 22 Jul 2021 20:17:40 +0200 Subject: [PATCH 375/382] CTLD - added repair capabilties for VEHICLE and FOB types of CTLD_CARGO --- Moose Development/Moose/Ops/CTLD.lua | 267 ++++++++++++++++++- Moose Development/Moose/Ops/Intelligence.lua | 8 +- 2 files changed, 261 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 9143eacf1..880606d07 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -59,6 +59,7 @@ CTLD_CARGO = { ["TROOPS"] = "Troops", -- #string troops ["FOB"] = "FOB", -- #string FOB ["CRATE"] = "Crate", -- #string crate + ["REPAIR"] = "Repair", -- #string repair } --- Function to create new CTLD_CARGO object. @@ -169,13 +170,25 @@ CTLD_CARGO = { return false end end + --- Set WasDropped. -- @param #CTLD_CARGO self -- @param #boolean dropped function CTLD_CARGO:SetWasDropped(dropped) self.HasBeenDropped = dropped or false end - + + --- Query crate type for REPAIR + -- @param #CTLD_CARGO self + -- @param #boolean + function CTLD_CARGO:IsRepair() + if self.CargoType == "Repair" then + return true + else + return false + end + end + end do @@ -235,6 +248,9 @@ do -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) -- +-- -- add crates to repair FOB or VEHICLE type units - the 2nd parameter needs to match the template you want to repair +-- my_ctld:AddCratesRepair("Humvee Repair","Humvee",CTLD_CARGO.Enum.REPAIR,1) +-- -- ## 1.3 Add logistics zones -- -- Add zones for loading troops and crates and dropping, building crates @@ -272,6 +288,7 @@ do -- my_ctld.movetroopsdistance = 5000 -- .. but only if this far away (in meters) -- my_ctld.smokedistance = 2000 -- Only smoke or flare zones if requesting player unit is this far away (in meters) -- my_ctld.suppressmessages = false -- Set to true if you want to script your own messages. +-- my_ctld.repairtime = 300 -- Number of seconds it takes to repair a unit. -- -- ## 2.1 User functions -- @@ -353,12 +370,16 @@ do -- ... your code here ... -- end -- --- ## 3.6 OnAfterCratesBuild +-- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired -- -- This function is called when a player has build a vehicle or FOB: -- -- function my_ctld:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- ... your code here ... +-- end +-- +-- function my_ctld:OnAfterCratesRepaired(From, Event, To, Group, Unit, Vehicle) +-- ... your code here ... -- end -- -- ## 3.7 A simple SCORING example: @@ -385,7 +406,7 @@ do -- -- ## 4.1 Manage Crates -- --- Use this entry to get, load, list nearby, drop, and build crates. Also @see options. +-- Use this entry to get, load, list nearby, drop, build and repair crates. Also @see options. -- -- ## 4.2 Manage Troops -- @@ -591,7 +612,8 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "TroopsDeployed", "*") -- CTLD deploy event. self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. - self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables @@ -652,6 +674,9 @@ function CTLD:New(Coalition, Prefixes, Alias) -- message suppression self.suppressmessages = false + -- time to repair a unit/group + self.repairtime = 300 + for i=1,100 do math.random() end @@ -745,7 +770,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. -- @return #CTLD self - --- FSM Function OnAfterCratesBuild. + --- FSM Function OnAfterCratesBuild. -- @function [parent=#CTLD] OnAfterCratesBuild -- @param #CTLD self -- @param #string From State. @@ -755,7 +780,18 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self - + + --- FSM Function OnAfterCratesRepaired. + -- @function [parent=#CTLD] OnAfterCratesRepaired + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param Wrapper.Group#GROUP Group Group Object. + -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB repaired. + -- @return #CTLD self + --- FSM Function OnAfterTroopsRTB. -- @function [parent=#CTLD] OnAfterTroopsRTB -- @param #CTLD self @@ -930,6 +966,111 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) return self end +function CTLD:_FindRepairNearby(Group, Unit, Repairtype) + self:T(self.lid .. " _FindRepairNearby") + local unitcoord = Unit:GetCoordinate() + + -- find nearest group of deployed groups + local nearestGroup = nil + local nearestGroupIndex = -1 + local nearestDistance = 10000000 + for k,v in pairs(self.DroppedTroops) do + local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) + if distance < nearestDistance and distance ~= -1 then + nearestGroup = v + nearestGroupIndex = k + nearestDistance = distance + end + end + + -- found one and matching distance? + if nearestGroup == nil or nearestDistance > 1000 then + self:_SendMessage("No unit close enough to repair!", 10, false, Group) + return nil, nil + end + + local groupname = nearestGroup:GetName() + --self:I(string.format("***** Found Group %s",groupname)) + + -- helper to find matching template + local function matchstring(String,Table) + local match = false + if type(Table) == "table" then + for _,_name in pairs (Table) do + if string.find(String,_name) then + match = true + break + end + end + else + if type(String) == "string" then + if string.find(String,Table) then match = true end + end + end + return match + end + + -- walk through generics and find matching type + local Cargotype = nil + for k,v in pairs(self.Cargo_Crates) do + --self:I({groupname,v.Templates}) + if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then + Cargotype = v -- #CTLD_CARGO + break + end + end + + if Cargotype == nil then + --self:_SendMessage("Can't find a matching group for " .. Repairtype, 10, false, Group) + return nil, nil + else + return nearestGroup, Cargotype + end + +end + +--- (Internal) Function to repair an object. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #table Crates Table of #CTLD_CARGO objects near the unit. +-- @param #CTLD.Buildable Build Table build object. +-- @param #number Number Number of objects in Crates (found) to limit search. +function CTLD:_RepairObjectFromCrates(Group,Unit,Crates,Build,Number) + self:T(self.lid .. " _RepairObjectFromCrates") + local build = Build -- -- #CTLD.Buildable + --self:I({Build=Build}) + local Repairtype = build.Template -- #string + local NearestGroup, CargoType = self:_FindRepairNearby(Group,Unit,Repairtype) -- Wrapper.Group#GROUP, #CTLD_CARGO + --self:I({Repairtype=Repairtype, CargoType=CargoType, NearestGroup=NearestGroup}) + if NearestGroup ~= nil then + if self.repairtime < 2 then self.repairtime = 30 end -- noob catch + self:_SendMessage(string.format("Repair started using %s taking %d secs", build.Name, self.repairtime), 10, false, Group) + -- now we can build .... + --NearestGroup:Destroy(false) + local name = CargoType:GetName() + local required = CargoType:GetCratesNeeded() + local template = CargoType:GetTemplates() + local ctype = CargoType:GetType() + local object = {} -- #CTLD.Buildable + object.Name = CargoType:GetName() + object.Required = required + object.Found = required + object.Template = template + object.CanBuild = true + object.Type = ctype -- #CTLD_CARGO.Enum + self:_CleanUpCrates(Crates,Build,Number) + local desttimer = TIMER:New(function() NearestGroup:Destroy(false) end, self) + desttimer:Start(self.repairtime - 1) + local buildtimer = TIMER:New(self._BuildObjectFromCrates,self,Group,Unit,object,true) + buildtimer:Start(self.repairtime) + --self:_BuildObjectFromCrates(Group,Unit,object) + else + self:_SendMessage("Can't repair this unit with " .. build.Name, 10, false, Group) + end + return self +end + --- (Internal) Function to extract (load from the field) troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -1533,7 +1674,7 @@ function CTLD:_BuildCrates(Group, Unit) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() then + if Crate:WasDropped() and not Crate:IsRepair() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -1596,12 +1737,91 @@ function CTLD:_BuildCrates(Group, Unit) return self end +--- (Internal) Function to repair nearby vehicles / FOBs +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrappe.Unit#UNIT Unit +function CTLD:_RepairCrates(Group, Unit) + self:T(self.lid .. " _RepairCrates") + -- get nearby crates + local finddist = self.CrateDistance or 30 + local crates,number = self:_FindCratesNearby(Group,Unit, finddist) -- #table + local buildables = {} + local foundbuilds = false + local canbuild = false + if number > 0 then + -- get dropped crates + for _,_crate in pairs(crates) do + local Crate = _crate -- #CTLD_CARGO + if Crate:WasDropped() and Crate:IsRepair() then + -- we can build these - maybe + local name = Crate:GetName() + local required = Crate:GetCratesNeeded() + local template = Crate:GetTemplates() + local ctype = Crate:GetType() + if not buildables[name] then + local object = {} -- #CTLD.Buildable + object.Name = name + object.Required = required + object.Found = 1 + object.Template = template + object.CanBuild = false + object.Type = ctype -- #CTLD_CARGO.Enum + buildables[name] = object + foundbuilds = true + else + buildables[name].Found = buildables[name].Found + 1 + foundbuilds = true + end + if buildables[name].Found >= buildables[name].Required then + buildables[name].CanBuild = true + canbuild = true + end + self:T({repair = buildables}) + end -- end dropped + end -- end crate loop + -- ok let\'s list what we have + local report = REPORT:New("Checklist Repairs") + report:Add("------------------------------------------------------------") + for _,_build in pairs(buildables) do + local build = _build -- Object table from above + local name = build.Name + local needed = build.Required + local found = build.Found + local txtok = "NO" + if build.CanBuild then + txtok = "YES" + end + local text = string.format("Type: %s | Required %d | Found %d | Can Repair %s", name, needed, found, txtok) + report:Add(text) + end -- end list buildables + if not foundbuilds then report:Add(" --- None Found ---") end + report:Add("------------------------------------------------------------") + local text = report:Text() + self:_SendMessage(text, 30, true, Group) + -- let\'s get going + if canbuild then + -- loop again + for _,_build in pairs(buildables) do + local build = _build -- #CTLD.Buildable + if build.CanBuild then + self:_RepairObjectFromCrates(Group,Unit,crates,build,number) + end + end + end + else + self:_SendMessage(string.format("No crates within %d meters!",finddist), 10, false, Group) + end -- number > 0 + return self +end + --- (Internal) Function to actually SPAWN buildables in the mission. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Group#UNIT Unit -- @param #CTLD.Buildable Build -function CTLD:_BuildObjectFromCrates(Group,Unit,Build) +-- @param #boolean Repair If true this is a repair and not a new build +function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair) self:T(self.lid .. " _BuildObjectFromCrates") -- Spawn-a-crate-content local position = Unit:GetCoordinate() or Group:GetCoordinate() @@ -1615,15 +1835,26 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build) local randomcoord = zone:GetRandomCoordinate(35):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 + if canmove then local alias = string.format("%s-%d", _template, math.random(1,100000)) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + --:InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end if self.movetroopstowpzone and canmove then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) end - self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + if Repair then + self:__CratesRepaired(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + else + self:__CratesBuild(1,Group,Unit,self.DroppedTroops[self.TroopCounter]) + end end -- template loop return self end @@ -1756,7 +1987,8 @@ function CTLD:_RefreshF10Menus() end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit):Refresh() + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) + local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() end -- sub menu troops management if cantroops then @@ -1815,6 +2047,21 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates) return self end +--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built. +-- @param #CTLD self +-- @param #string Name Unique name of this type of cargo. E.g. "Humvee". +-- @param #string Template Template of VEHICLE or FOB cargo that this can repair. +-- @param #CTLD_CARGO.Enum Type Type of cargo, here REPAIR. +-- @param #number NoCrates Number of crates needed to build this cargo. +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates) + self:T(self.lid .. " AddCratesRepair") + self.CargoCounter = self.CargoCounter + 1 + -- Crates are not directly loadable + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates) + table.insert(self.Cargo_Crates,cargo) + return self +end + --- User function - Add a #CTLD.CargoZoneType zone for this CTLD instance. -- @param #CTLD self -- @param #CTLD.CargoZone Zone Zone #CTLD.CargoZone describing the zone. diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 0bd198387..aee683f96 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1494,9 +1494,9 @@ INTEL_DLINK.version = "0.0.1" -- @param #string Alias (optional) Name of this instance. Default "SPECTRE" -- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds). -- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds). --- @usage Use #INTEL_DLINK if you want to merge data from a number of #INTEL objects into one. This might be useful to simulate a Data Link, e.g. for Russian-tech based EWR, --- realising a Star Topology @{https://en.wikipedia.org/wiki/Network_topology#Star} in a basic setup. --- It will collect the contacts and clusters from the #INTEL objects. +-- @usage Use #INTEL_DLINK if you want to merge data from a number of #INTEL objects into one. This might be useful to simulate a +-- Data Link, e.g. for Russian-tech based EWR, realising a Star Topology @{https://en.wikipedia.org/wiki/Network_topology#Star} +-- in a basic setup. It will collect the contacts and clusters from the #INTEL objects. -- Contact duplicates are removed. Clusters might contain duplicates (Might fix that later, WIP). -- -- Basic setup: @@ -1723,7 +1723,7 @@ function INTEL_DLINK:GetContactTable() return self.contacts end ---- Function to query the detected clusters -- not yet implemented! +--- Function to query the detected clusters -- @param #INTEL_DLINK self -- @return #table Table of #INTEL.Cluster clusters function INTEL_DLINK:GetClusterTable() From 61481e6e9a1756959542d3ed9095fed820e84f1b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Jul 2021 11:19:53 +0200 Subject: [PATCH 376/382] CSAR updates to messaging. Detect if far approach note exceeds 8km for smoke/flare request --- Moose Development/Moose/Ops/CSAR.lua | 35 +++++++++++++++++----------- Moose Development/Moose/Ops/CTLD.lua | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index df419a5b9..80f1fa642 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -1010,6 +1010,8 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then self.heliVisibleMessage[_lookupKeyHeli] = nil + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away _downedpilot.timestamp = timer.getAbsTime() self:__Approach(-10,heliname,woundedgroupname) @@ -1520,8 +1522,9 @@ function CSAR:_SignalFlare(_unitName) end local _closest = self:_GetClosestDownedPilot(_heli) - - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local smokedist = 8000 + if self.approachdist_far > smokedist then smokedist = self.approachdist_far end + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 @@ -1536,11 +1539,13 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + local _distance = smokedist + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) end return self end @@ -1572,8 +1577,10 @@ function CSAR:_Reqsmoke( _unitName ) if _heli == nil then return end + local smokedist = 8000 + if smokedist < self.approachdist_far then smokedist = self.approachdist_far end local _closest = self:_GetClosestDownedPilot(_heli) - if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) local _distance = 0 if _SETTINGS:IsImperial() then @@ -1587,11 +1594,13 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - local disttext = "4.3nm" - if _SETTINGS:IsMetric() then - disttext = "8km" - end - self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) + local _distance = 0 + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(smokedist)) + else + _distance = string.format("%.1fkm",smokedist/1000) + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime) end return self end diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 880606d07..3ab505402 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1,4 +1,4 @@ ---- **Ops** -- Combat Troops & Logistics Deployment. +--- **Ops** -- Combat Troops & Logistics Department. -- -- === -- From 8f698e3e623c141059ac69494f4ce6db57fb1f6e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Jul 2021 16:29:56 +0200 Subject: [PATCH 377/382] Changed Messaging Structure on Approach --- Moose Development/Moose/Ops/CSAR.lua | 50 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 80f1fa642..8b718f7be 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -368,7 +368,7 @@ function CSAR:New(Coalition, Template, Alias) self.mashprefix = {"MASH"} -- prefixes used to find MASHes self.mash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near - self.autosmokedistance = 1000 -- distance for autosmoke + self.autosmokedistance = 2000 -- distance for autosmoke -- added 0.1.4 self.limitmaxdownedpilots = true self.maxdownedpilots = 25 @@ -998,10 +998,15 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:T("...helinunit nil!") return end - + local _heliCoord = _heliUnit:GetCoordinate() local _leaderCoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + -- autosmoke + if (self.autosmoke == true) and (_distance < self.autosmokedistance) and (_distance ~= -1) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedGroup) + end + if _distance < self.approachdist_near and _distance > 0 then if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then -- we\'re close, reschedule @@ -1009,7 +1014,23 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) self:__Approach(-5,heliname,woundedgroupname) end elseif _distance >= self.approachdist_near and _distance < self.approachdist_far then - self.heliVisibleMessage[_lookupKeyHeli] = nil + -- message once + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + local _pilotName = _downedpilot.desc + if self.autosmoke == true then + local dist = self.autosmokedistance / 1000 + local disttext = string.format("%.0fkm",dist) + if _SETTINGS:IsImperial() then + local dist = UTILS.MetersToNM(self.autosmokedistance) + disttext = string.format("%.0fnm",dist) + end + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end self.heliCloseMessage[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil --reschedule as units aren\'t dead yet , schedule for a bit slower though as we\'re far away @@ -1171,27 +1192,13 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG local _reset = true - if (self.autosmoke == true) and (_distance < self.autosmokedistance) then - self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) - end - - if self.heliVisibleMessage[_lookupKeyHeli] == nil then - if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) - else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) - end - --mark as shown for THIS heli and THIS group - self.heliVisibleMessage[_lookupKeyHeli] = true - end - if (_distance < 500) then if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1208,7 +1215,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, false) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1785,7 +1792,8 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - clock = math.floor(Aspect / 30) + --clock = math.floor(Aspect / 30) + clock = math.abs(UTILS.Round((Aspect / 30),0)) if clock == 0 then clock = 12 end end return clock From 65e852f341cd8c6826a60afba42c4bfa39012e9b Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 01:06:44 +0200 Subject: [PATCH 378/382] OPS Transport --- Moose Development/Moose/Ops/Auftrag.lua | 7 ++- Moose Development/Moose/Ops/OpsGroup.lua | 78 ++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4c55f5119..6dd08b4e6 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -3149,8 +3149,9 @@ end --- Get coordinate of target. First unit/group of the set is used. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP group Group. +-- @param #number randomradius Random radius in meters. -- @return Core.Point#COORDINATE Coordinate where the mission is executed. -function AUFTRAG:GetMissionWaypointCoord(group) +function AUFTRAG:GetMissionWaypointCoord(group, randomradius) -- Check if a coord has been explicitly set. if self.missionWaypointCoord then @@ -3166,7 +3167,9 @@ function AUFTRAG:GetMissionWaypointCoord(group) local alt=waypointcoord.y -- Add some randomization. - waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), 1000):GetRandomCoordinate():SetAltitude(alt, false) + if randomradius then + waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate():SetAltitude(alt, false) + end -- Set altitude of mission waypoint. if self.missionAltitude then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6983f7dba..a4ec949b2 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3009,6 +3009,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Parameters. local zone=Task.dcstask.params.zone --Core.Zone#ZONE local Coordinate=zone:GetRandomCoordinate() + Coordinate:MarkToAll("Random Patrol Zone Coordinate") local Speed=UTILS.KmphToKnots(Task.dcstask.params.speed or self.speedCruise) local Altitude=Task.dcstask.params.altitude and UTILS.MetersToFeet(Task.dcstask.params.altitude) or nil @@ -3695,15 +3696,21 @@ function OPSGROUP:RouteToMission(mission, delay) self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) else - if self:IsDead() then + if self:IsDead() or self:IsStopped() then return end -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid - + + -- Random radius. + local randomradius=1000 + if mission.type==AUFTRAG.Type.PATROLZONE then + randomradius=nil + end + -- Get coordinate where the mission is executed. - local waypointcoord=mission:GetMissionWaypointCoord(self.group) + local waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius) -- Add enroute tasks. for _,task in pairs(mission.enrouteTasks) do @@ -5423,6 +5430,39 @@ function OPSGROUP:_CheckDelivered(CargoTransport) return done end + +--- Check if all cargo of this transport assignment was delivered. +-- @param #OPSGROUP self +-- @param Ops.OpsTransport#OPSTRANSPORT CargoTransport The next due cargo transport or `nil`. +-- @return #boolean If true, all cargo was delivered. +function OPSGROUP:_CheckGoPickup(CargoTransport) + + local done=true + for _,_cargo in pairs(CargoTransport.cargos) do + local cargo=_cargo --Ops.OpsGroup#OPSGROUP.CargoGroup + + if self:CanCargo(cargo.opsgroup) then + + if cargo.delivered then + -- This one is delivered. + elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then + -- This one is dead. + elseif cargo.opsgroup:IsLoaded() then + -- This one is loaded into a(nother) carrier. + else + done=false --Someone is not done! + end + + end + + end + + -- Debug info. + self:T(self.lid..string.format("Cargotransport UID=%d Status=%s: delivered=%s", CargoTransport.uid, CargoTransport:GetState(), tostring(done))) + + return done +end + --- Create a cargo transport assignment. -- @param #OPSGROUP self -- @param Ops.OpsTransport#OPSTRANSPORT OpsTransport The troop transport assignment. @@ -5517,6 +5557,32 @@ function OPSGROUP:GetFreeCargobay(UnitName, IncludeReserved) return Free end +--- Get relative free cargo bay in percent. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. +-- @return #number Free cargo bay in percent. +function OPSGROUP:GetFreeCargobayRelative(UnitName, IncludeReserved) + + local free=self:GetFreeCargobay(UnitName, IncludeReserved) + + local total=self:GetWeightCargoMax(UnitName) + + local percent=free/total*100 + + return percent +end + +--- Get relative used (loaded) cargo bay in percent. +-- @param #OPSGROUP self +-- @param #string UnitName Name of the unit. Default is of the whole group. +-- @param #boolean IncludeReserved If `false`, cargo weight that is only *reserved* is **not** counted. By default (`true` or `nil`), the reserved cargo is included. +-- @return #number Used cargo bay in percent. +function OPSGROUP:GetUsedCargobayRelative(UnitName, IncludeReserved) + local free=self:GetFreeCargobayRelative(UnitName, IncludeReserved) + return 100-free +end + --- Get max weight of cargo (group) this group can load. This is the largest free cargo bay of any (not dead) element of the group. -- Optionally, you can calculate the current max weight possible, which accounts for currently loaded cargo. -- @param #OPSGROUP self @@ -6563,7 +6629,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To) end -- Check everything was delivered (or is dead). - local delivered=self:_CheckDelivered(self.cargoTransport) + local delivered=self:_CheckGoPickup(self.cargoTransport) if not delivered then @@ -9295,9 +9361,11 @@ end -- @return #OPSGROUP self function OPSGROUP:_SetTemplate(Template) + -- Set the template. self.template=Template or self.group:GetTemplate() - self:I(self.lid.."Setting group template") + -- Debug info. + self:T3(self.lid.."Setting group template") return self end From c718584755960bb7c312072b49466befaed51511 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 23:25:26 +0200 Subject: [PATCH 379/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a4ec949b2..b3c912dfa 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -6087,12 +6087,18 @@ end --- Clear waypoints. -- @param #OPSGROUP self -function OPSGROUP:ClearWaypoints() +-- @param #number IndexMin Clear waypoints up to this min WP index. Default 1. +-- @param #number IndexMax Clear waypoints up to this max WP index. Default `#self.waypoints`. +function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) + + IndexMin=IndexMin or 1 + IndexMax=IndexMax or #self.waypoints + -- Clear all waypoints. - for i=1,#self.waypoints do + for i=IndexMax,IndexMin,-1 do table.remove(self.waypoints, i) end - self.waypoints={} + --self.waypoints={} end --- Set (new) cargo status. @@ -6768,7 +6774,7 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) local Coordinate=Carrier.unit:GetCoordinate() -- Clear all waypoints. - self:ClearWaypoints() + self:ClearWaypoints(self.currentwp+1) if self.isArmygroup then local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 From 615a220acb19322624d3de1e35ccd3ece071f815 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 24 Jul 2021 23:56:27 +0200 Subject: [PATCH 380/382] Update OpsGroup.lua --- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b3c912dfa..78885d372 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -5447,7 +5447,7 @@ function OPSGROUP:_CheckGoPickup(CargoTransport) -- This one is delivered. elseif cargo.opsgroup==nil or cargo.opsgroup:IsDead() or cargo.opsgroup:IsStopped() then -- This one is dead. - elseif cargo.opsgroup:IsLoaded() then + elseif cargo.opsgroup:IsLoaded(CargoTransport:_GetCarrierNames()) then -- This one is loaded into a(nother) carrier. else done=false --Someone is not done! From d64de26ded20fa60375c21fca5ab8dc97f516237 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 29 Jul 2021 13:43:29 +0200 Subject: [PATCH 381/382] OPS - many fixes and improvements --- .../Moose/Functional/Warehouse.lua | 14 +- Moose Development/Moose/Ops/AirWing.lua | 4 +- Moose Development/Moose/Ops/Airboss.lua | 10 +- Moose Development/Moose/Ops/ArmyGroup.lua | 36 ++-- Moose Development/Moose/Ops/Auftrag.lua | 57 ++++-- Moose Development/Moose/Ops/FlightGroup.lua | 167 +++++++----------- Moose Development/Moose/Ops/NavyGroup.lua | 38 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 143 +++++++++++---- Moose Development/Moose/Ops/OpsTransport.lua | 4 +- Moose Development/Moose/Ops/Squadron.lua | 17 +- 10 files changed, 288 insertions(+), 202 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cf02c728f..1c571b93c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4882,6 +4882,13 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group) local asset=self:FindAssetInDB(group) if asset then + + if asset.flightgroup and not asset.arrived then + --env.info("FF asset has a flightgroup. arrival will be handled there!") + asset.arrived=true + return false + end + if asset.arrived==true then -- Asset already arrived (e.g. if multiple units trigger the event via landing). return false @@ -4889,6 +4896,7 @@ function WAREHOUSE:onbeforeArrived(From, Event, To, group) asset.arrived=true --ensure this is not called again from the same asset group. return true end + end end @@ -6042,7 +6050,7 @@ function WAREHOUSE:_RouteGround(group, request) end for n,wp in ipairs(Waypoints) do - env.info(n) + --env.info(n) local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group, n, #Waypoints) group:SetTaskWaypoint(wp, tf) end @@ -9046,11 +9054,11 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) -- Hot start. if asset.takeoffType and asset.takeoffType==COORDINATE.WaypointType.TakeOffParkingHot then - env.info("FF hot") + --env.info("FF hot") _type=COORDINATE.WaypointType.TakeOffParkingHot _action=COORDINATE.WaypointAction.FromParkingAreaHot else - env.info("FF cold") + --env.info("FF cold") end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 4b44b6b91..5e702896b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -612,9 +612,9 @@ function AIRWING:RemoveAssetFromSquadron(Asset) end end ---- Add mission to queue. +--- Add a mission for the airwing. The airwing will pick the best available assets for the mission and lauch it when ready. -- @param #AIRWING self --- @param Ops.Auftrag#AUFTRAG Mission for this group. +-- @param Ops.Auftrag#AUFTRAG Mission Mission for this airwing. -- @return #AIRWING self function AIRWING:AddMission(Mission) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index c3a45c8c5..e0cff612e 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -6118,6 +6118,11 @@ function AIRBOSS:_ScanCarrierZone() -- Get flight group if possible. local knownflight=self:_GetFlightFromGroupInQueue(group, self.flights) + + -- Unknown new AI flight. Create a new flight group. + if not knownflight and not self:_IsHuman(group) then + knownflight=self:_CreateFlightGroup(group) + end -- Get aircraft type name. local actype=group:GetTypeName() @@ -6175,10 +6180,7 @@ function AIRBOSS:_ScanCarrierZone() else - -- Unknown new AI flight. Create a new flight group. - if not self:_IsHuman(group) then - self:_CreateFlightGroup(group) - end + end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index b0c0b2c91..2bcef539a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -538,6 +538,24 @@ end function ARMYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Army Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end + -- Update position. self:_UpdatePosition() @@ -1179,24 +1197,6 @@ function ARMYGROUP:_InitGroup(Template) -- Set type name. self.actype=units[1]:GetTypeName() - - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Army Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) - end -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 6dd08b4e6..c9bd4ca98 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -7,7 +7,7 @@ -- * Set mission start/stop times -- * Set mission priority and urgency (can cancel running missions) -- * Specific mission options for ROE, ROT, formation, etc. --- * Compatible with FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, WINGCOMMANDER and CHIEF classes +-- * Compatible with OPS classes like FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, etc. -- * FSM events when a mission is done, successful or failed -- -- === @@ -106,6 +106,7 @@ -- @field #number missionFraction Mission coordiante fraction. Default is 0.5. -- @field #number missionRange Mission range in meters. Used in AIRWING class. -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. +-- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. -- -- @field #table enrouteTasks Mission enroute tasks. -- @@ -434,8 +435,9 @@ AUFTRAG.TargetType={ --- Group specific data. Each ops group subscribed to this mission has different data for this. -- @type AUFTRAG.GroupData -- @field Ops.OpsGroup#OPSGROUP opsgroup The OPS group. --- @field Core.Point#COORDINATE waypointcoordinate Waypoint coordinate. +-- @field Core.Point#COORDINATE waypointcoordinate Ingress waypoint coordinate. -- @field #number waypointindex Waypoint index. +-- @field Core.Point#COORDINATE wpegresscoordinate Egress waypoint coordinate. -- @field Ops.OpsGroup#OPSGROUP.Task waypointtask Waypoint task. -- @field #string status Group mission status. -- @field Ops.AirWing#AIRWING.SquadronAsset asset The squadron asset. @@ -443,7 +445,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.6.1" +AUFTRAG.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -938,7 +940,7 @@ end --- Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT or STATIC object. +-- @param Core.Point#COORDINATE Target The target coordinate. Can also be given as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 2000 ft. -- @return #AUFTRAG self function AUFTRAG:NewSTRIKE(Target, Altitude) @@ -966,7 +968,7 @@ end --- Create a BOMBING mission. Flight will drop bombs a specified coordinate. -- @param #AUFTRAG self --- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT or STATIC object. +-- @param Core.Point#COORDINATE Target Target coordinate. Can also be specified as a GROUP, UNIT, STATIC or TARGET object. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @return #AUFTRAG self function AUFTRAG:NewBOMBING(Target, Altitude) @@ -1006,10 +1008,6 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) if type(Airdrome)=="string" then Airdrome=AIRBASE:FindByName(Airdrome) end - - if Airdrome:IsInstanceOf("AIRBASE") then - - end local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY) @@ -1110,9 +1108,7 @@ end function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) - - --mission.carrier=Carrier - + mission:_TargetFromObject(Carrier) -- Mission options: @@ -3138,12 +3134,45 @@ function AUFTRAG:GetMissionTypesText(MissionTypes) return text end ---- Set the mission waypoint coordinate where the mission is executed. +--- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. -- @param #AUFTRAG self --- @return Core.Point#COORDINATE Coordinate where the mission is executed. +-- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @return #AUFTRAG self function AUFTRAG:SetMissionWaypointCoord(Coordinate) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + self.missionWaypointCoord=Coordinate + return self +end + +--- Set the mission egress coordinate. This is the coordinate where the assigned group will go once the mission is finished. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Egrees coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionEgressCoord=Coordinate + + if Altitude then + self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) + end +end + +--- Get the mission egress coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionEgressCoord() + return self.missionEgressCoord end --- Get coordinate of target. First unit/group of the set is used. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index a10db29bf..c7a7e89c5 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -134,8 +134,8 @@ FLIGHTGROUP = { fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, - outofAAMrtb = true, - outofAGMrtb = true, + outofAAMrtb = false, + outofAGMrtb = false, squadron = nil, flightcontrol = nil, flaghold = nil, @@ -171,10 +171,6 @@ FLIGHTGROUP.Attribute = { OTHER="Other", } ---- Flight group element. --- @type FLIGHTGROUP.Element --- @extends Ops.OpsGroup#OPSGROUP.Element - --- FLIGHTGROUP class version. -- @field #string version FLIGHTGROUP.version="0.7.0" @@ -251,9 +247,9 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A missiles. - self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. - self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S(ship) missiles. Not implemented yet! + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A (air) missiles. + self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G (ground) missiles. + self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2S (ship) missiles. self:AddTransition("Airborne", "EngageTarget", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. @@ -1036,6 +1032,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:FuelCritical() end + -- This causes severe problems as OutOfMissiles is called over and over again leading to many RTB calls. + if false then + -- Out of AA Missiles? CAP, GCICAP, INTERCEPT local CurrIsCap = false -- Out of AG Missiles? BAI, SEAD, CAS, STRIKE @@ -1057,6 +1056,8 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if (not self:CanAirToGround(false)) and CurrIsA2G then self:OutOfMissilesAG() end + + end end @@ -1595,6 +1596,34 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Flight spawned")) + + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Flight Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) + text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) + text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) + text=text..string.format("AI = %s\n", tostring(self.isAI)) + text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) + text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) + text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) + text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) + text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) + self:I(self.lid..text) + end -- Update position. self:_UpdatePosition() @@ -1858,9 +1887,10 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - -- Despawn in 5 min. + -- Check what to do. if self.airwing then - -- Let airwing do its thing. + -- Add the asset back to the airwing. + self.airwing:AddAsset(self.group, 1) elseif self.isLandingAtAirbase then local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -1963,7 +1993,7 @@ function FLIGHTGROUP:onafterDead(From, Event, To) else if self.airwing then -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. - self.airwing:AddAsset(self.group, 1) + --self.airwing:AddAsset(self.group, 1) end end @@ -2131,7 +2161,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAA(From, Event, To) if self.outofAAMrtb then -- Back to destination or home. local airbase=self.destbase or self.homebase - self:__RTB(-5,airbase) + self:__RTB(-5, airbase) end end @@ -2145,7 +2175,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAG(From, Event, To) if self.outofAGMrtb then -- Back to destination or home. local airbase=self.destbase or self.homebase - self:__RTB(-5,airbase) + self:__RTB(-5, airbase) end end @@ -2288,20 +2318,31 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) -- Check that coaliton is okay. We allow same (blue=blue, red=red) or landing on neutral bases. if airbase and airbase:GetCoalition()~=self.group:GetCoalition() and airbase:GetCoalition()>0 then - self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0.", airbase:GetCoalition(), self.group:GetCoalition())) - allowed=false + self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0", airbase:GetCoalition(), self.group:GetCoalition())) + return false + end + + if self.currbase and self.currbase:GetName()==airbase:GetName() then + self:E(self.lid.."WARNING: Currbase is already same as RTB airbase. RTB canceled!") + return false + end + + -- Check if the group has landed at an airbase. If so, we lost control and RTBing is not possible (only after a respawn). + if self:IsLanded() then + self:E(self.lid.."WARNING: Flight has already landed. RTB canceled!") + return false end if not self.group:IsAirborne(true) then -- this should really not happen, either the AUFTRAG is cancelled before the group was airborne or it is stuck at the ground for some reason - self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec.")) + self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 20 sec")) allowed=false Tsuspend=-20 local groupspeed = self.group:GetVelocityMPS() if groupspeed<=1 and not self:IsParking() then self.RTBRecallCount = self.RTBRecallCount+1 end - if self.RTBRecallCount > 6 then + if self.RTBRecallCount>6 then self:I(self.lid..string.format("WARNING: Group is not moving and was called RTB %d times. Assuming a problem and despawning!", self.RTBRecallCount)) self.RTBRecallCount=0 self:Despawn(5) @@ -3108,103 +3149,19 @@ function FLIGHTGROUP:_InitGroup(Template) self.tankertype=select(2, unit:IsTanker()) self.refueltype=select(2, unit:IsRefuelable()) - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Flight Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) - text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) - text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) - text=text..string.format("AI = %s\n", tostring(self.isAI)) - text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) - text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) - text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) - text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) - text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) - self:I(self.lid..text) - end - --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) -- Init done. self.groupinitialized=true - + + else + self:E(self.lid.."ERROR: no unit in _InigGroup!") end return self end ---- Add an element to the flight group. --- @param #FLIGHTGROUP self --- @param #string unitname Name of unit. --- @return Ops.OpsGroup#OPSGROUP.Element The element or nil. -function FLIGHTGROUP:AddElementByName(unitname) - - local unit=UNIT:FindByName(unitname) - - if unit then - - local element={} --Ops.OpsGroup#OPSGROUP.Element - - element.name=unitname - element.status=OPSGROUP.ElementStatus.INUTERO - element.unit=unit - element.group=unit:GetGroup() - - - -- TODO: this is wrong when grouping is used! - local unittemplate=element.unit:GetTemplate() - - element.modex=unittemplate.onboard_num - element.skill=unittemplate.skill - element.payload=unittemplate.payload - element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil --element.unit:GetTemplatePylons() - element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 --element.unit:GetTemplatePayload().fuel - element.fuelmass=element.fuelmass0 - element.fuelrel=element.unit:GetFuel() - element.category=element.unit:GetUnitCategory() - element.categoryname=element.unit:GetCategoryName() - element.callsign=element.unit:GetCallsign() - element.size=element.unit:GetObjectSize() - - if element.skill=="Client" or element.skill=="Player" then - element.ai=false - element.client=CLIENT:FindByName(unitname) - else - element.ai=true - end - - -- Debug text. - local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", - element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) - self:T(self.lid..text) - - -- Add element to table. - table.insert(self.elements, element) - - if unit:IsAlive() then - self:ElementSpawned(element) - end - - return element - end - - return nil -end - --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a1209e0db..4925626c2 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -648,7 +648,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #NAVYGROUP.Element Element The group element. +-- @param Ops.OpsGroup#OPSGROUP.Element Element The group element. function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) self:T(self.lid..string.format("Element spawned %s", Element.name)) @@ -665,6 +665,24 @@ end function NAVYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- Debug info. + if self.verbose>=1 then + local text=string.format("Initialized Navy Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) + text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end + -- Update position. self:_UpdatePosition() @@ -1199,24 +1217,6 @@ function NAVYGROUP:_InitGroup(Template) -- Set type name. self.actype=units[1]:GetTypeName() - - -- Debug info. - if self.verbose>=1 then - local text=string.format("Initialized Navy Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) - text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) - end -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 78885d372..40d29583f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -620,6 +620,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "Unloading", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Unload", "*") -- Carrier unload a cargo group. self:AddTransition("*", "Unloaded", "*") -- Carrier unloaded all its current cargo. + self:AddTransition("*", "UnloadingDone", "*") -- Carrier is unloading the cargo. self:AddTransition("*", "Delivered", "*") -- Carrier delivered ALL cargo of the transport assignment. ------------------------ @@ -1883,13 +1884,33 @@ end --- Check if the group is **not** cargo. -- @param #OPSGROUP self --- @param #boolean CheckTransport If true or nil, also check if cargo is associated with a transport assignment. If not, we consider it not cargo. +-- @param #boolean CheckTransport If `true` or `nil`, also check if cargo is associated with a transport assignment. If not, we consider it not cargo. -- @return #boolean If true, group is *not* cargo. function OPSGROUP:IsNotCargo(CheckTransport) local notcargo=self.cargoStatus==OPSGROUP.CargoStatus.NOTCARGO - if self.cargoTransportUID==nil then - --notcargo=true + + if notcargo then + -- Not cargo. + return true + else + -- Is cargo (e.g. loaded or boarding) + + if CheckTransport then + -- Check if transport UID was set. + if self.cargoTransportUID==nil then + return true + else + -- Some transport UID was assigned. + return false + end + else + -- Is cargo. + return false + end + end + + return notcargo end @@ -2397,9 +2418,13 @@ function OPSGROUP:OnEventBirth(EventData) -- Get element. local element=self:GetElementByName(unitname) + + if element then - -- Set element to spawned state. - self:ElementSpawned(element) + -- Set element to spawned state. + self:ElementSpawned(element) + + end end @@ -2412,7 +2437,7 @@ function OPSGROUP:OnEventDead(EventData) -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then - self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) + self:T2(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) local unit=EventData.IniUnit local group=EventData.IniGroup @@ -2421,7 +2446,7 @@ function OPSGROUP:OnEventDead(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.DEAD then self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) self:ElementDestroyed(element) end @@ -2437,6 +2462,8 @@ function OPSGROUP:OnEventRemoveUnit(EventData) -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + self:T2(self.lid..string.format("EVENT: Unit %s removed!", EventData.IniUnitName)) + local unit=EventData.IniUnit local group=EventData.IniGroup local unitname=EventData.IniUnitName @@ -2444,7 +2471,7 @@ function OPSGROUP:OnEventRemoveUnit(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then + if element and element.status~=OPSGROUP.ElementStatus.DEAD then self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) self:ElementDead(element) end @@ -3026,7 +3053,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- If task is scheduled (not waypoint) set task. if Task.type==OPSGROUP.TaskType.SCHEDULED or Task.ismission then - + local DCStasks={} if Task.dcstask.id=='ComboTask' then -- Loop over all combo tasks. @@ -3053,7 +3080,12 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) -- Set task for group. - self:SetTask(TaskFinal) + -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. + -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles + -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. + self:PushTask(TaskFinal) + --self:SetTask(TaskFinal) + elseif Task.type==OPSGROUP.TaskType.WAYPOINT then -- Waypoint tasks are executed elsewhere! @@ -3780,6 +3812,8 @@ function OPSGROUP:RouteToMission(mission, delay) if self.isGround and mission.optionFormation then formation=mission.optionFormation end + + --waypointcoord:MarkToAll(string.format("Mission %s alt=%d m", mission:GetName(), waypointcoord.y)) -- Add waypoint. local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, formation, false) @@ -3793,6 +3827,11 @@ function OPSGROUP:RouteToMission(mission, delay) -- Set waypoint index. mission:SetGroupWaypointIndex(self, waypoint.uid) + + local egress=mission:GetMissionEgressCoord() + if egress then + local waypoint=self:AddWaypoint(egress, SpeedToMission, nil, formation, false) + end --- -- Mission Specific Settings @@ -4038,6 +4077,7 @@ function OPSGROUP:_SetWaypointTasks(Waypoint) -- Check if there is mission task if missiontask then + self:T(self.lid.."Executing mission task") self:TaskExecute(missiontask) return 1 end @@ -5218,8 +5258,8 @@ function OPSGROUP:_CheckCargoTransport() -- Unloading finished ==> pickup next batch or call it a day. if delivered then - self:T(self.lid.."Unloading finished ==> Unloaded") - self:Unloaded() + self:T(self.lid.."Unloading finished ==> UnloadingDone") + self:UnloadingDone() else self:Unloading() end @@ -5954,7 +5994,7 @@ function OPSGROUP:onafterPickup(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Pickup zone! Specify a ZONE_AIRBASE as pickup zone") @@ -5982,7 +6022,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 -- Give cruise command. self:__Cruise(-2) @@ -6010,7 +6050,7 @@ function OPSGROUP:onafterPickup(From, Event, To) end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 self:__Cruise(-2) @@ -6045,7 +6085,7 @@ function OPSGROUP:onafterLoading(From, Event, To) -- Check that group is NOT cargo and NOT acting as carrier already -- TODO: Need a better :IsBusy() function or :IsReadyForMission() :IsReadyForBoarding() :IsReadyForTransport() - if cargo.opsgroup:IsNotCargo() and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then + if cargo.opsgroup:IsNotCargo(true) and not (cargo.opsgroup:IsPickingup() or cargo.opsgroup:IsLoading() or cargo.opsgroup:IsTransporting() or cargo.opsgroup:IsUnloading()) then -- Check if cargo is in embark/pickup zone. local inzone=self.cargoTransport.embarkzone:IsCoordinateInZone(cargo.opsgroup:GetCoordinate()) @@ -6333,7 +6373,7 @@ function OPSGROUP:onafterTransport(From, Event, To) -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. Coordinate:SetAltitude(200) - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=true + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 else self:E(self.lid.."ERROR: Carrier aircraft cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -6356,7 +6396,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- ARMYGROUP - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=true + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -6379,7 +6419,7 @@ function OPSGROUP:onafterTransport(From, Event, To) end -- NAVYGROUP - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=true + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate, nil, uid) ; waypoint.detour=1 -- Give cruise command. self:Cruise() @@ -6612,6 +6652,9 @@ function OPSGROUP:onafterUnload(From, Event, To, OpsGroup, Coordinate, Activated -- Trigger "Disembarked" event. OpsGroup:Disembarked(OpsGroup:_GetMyCarrierGroup(), OpsGroup:_GetMyCarrierElement()) + + -- Trigger "Unloaded" event. + self:Unloaded(OpsGroup) -- Remove my carrier. OpsGroup:_RemoveMyCarrier() @@ -6623,10 +6666,21 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function OPSGROUP:onafterUnloaded(From, Event, To) +-- @param #OPSGROUP OpsGroupCargo Cargo OPSGROUP that was unloaded from a carrier. +function OPSGROUP:onafterUnloaded(From, Event, To, OpsGroupCargo) + self:I(self.lid..string.format("Unloaded OPSGROUP %s", OpsGroupCargo:GetName())) +end + + +--- On after "UnloadingDone" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterUnloadingDone(From, Event, To) -- Debug info - self:T(self.lid.."Cargo unloaded..") + self:T(self.lid.."Cargo unloading done..") -- Cancel landedAt task. if self:IsFlightgroup() and self:IsLandedAt() then @@ -6777,10 +6831,10 @@ function OPSGROUP:onafterBoard(From, Event, To, CarrierGroup, Carrier) self:ClearWaypoints(self.currentwp+1) if self.isArmygroup then - local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 + local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 self:Cruise() else - local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=0 + local waypoint=NAVYGROUP.AddWaypoint(self, Coordinate) ; waypoint.detour=1 self:Cruise() end @@ -7216,7 +7270,7 @@ function OPSGROUP:_CheckAmmoStatus() -- Guns. if self.outofGuns and ammo.Guns>0 then - self.outoffGuns=false + self.outofGuns=false end if ammo.Guns==0 and self.ammo.Guns>0 and not self.outofGuns then self.outofGuns=true @@ -7225,7 +7279,7 @@ function OPSGROUP:_CheckAmmoStatus() -- Rockets. if self.outofRockets and ammo.Rockets>0 then - self.outoffRockets=false + self.outofRockets=false end if ammo.Rockets==0 and self.ammo.Rockets>0 and not self.outofRockets then self.outofRockets=true @@ -7234,22 +7288,50 @@ function OPSGROUP:_CheckAmmoStatus() -- Bombs. if self.outofBombs and ammo.Bombs>0 then - self.outoffBombs=false + self.outofBombs=false end if ammo.Bombs==0 and self.ammo.Bombs>0 and not self.outofBombs then self.outofBombs=true self:OutOfBombs() end - -- Missiles. + -- Missiles (All). if self.outofMissiles and ammo.Missiles>0 then - self.outoffMissiles=false + self.outofMissiles=false end if ammo.Missiles==0 and self.ammo.Missiles>0 and not self.outofMissiles then self.outofMissiles=true self:OutOfMissiles() end + -- Missiles AA. + if self.outofMissilesAA and ammo.MissilesAA>0 then + self.outofMissilesAA=false + end + if ammo.MissilesAA and self.ammo.MissilesAA>0 and not self.outofMissilesAA then + self.outofMissilesAA=true + self:OutOfMissilesAA() + end + + -- Missiles AG. + if self.outofMissilesAG and ammo.MissilesAG>0 then + self.outofMissilesAG=false + end + if ammo.MissilesAG and self.ammo.MissilesAG>0 and not self.outofMissilesAG then + self.outofMissilesAG=true + self:OutOfMissilesAG() + end + + -- Missiles AS. + if self.outofMissilesAS and ammo.MissilesAS>0 then + self.outofMissilesAS=false + end + if ammo.MissilesAS and self.ammo.MissilesAS>0 and not self.outofMissilesAS then + self.outofMissilesAS=true + self:OutOfMissilesAS() + end + + -- Check if group is engaging. if self:IsEngaging() and ammo.Total==0 then self:Disengage() @@ -7725,7 +7807,7 @@ function OPSGROUP._TaskDone(group, opsgroup, task) -- Debug message. local text=string.format("_TaskDone %s", task.description) - opsgroup:T3(opsgroup.lid..text) + opsgroup:T(opsgroup.lid..text) -- Set current task to nil so that the next in line can be executed. if opsgroup then @@ -9352,7 +9434,8 @@ function OPSGROUP:_AddElementByName(unitname) -- Trigger spawned event if alive. if unit:IsAlive() then - self:ElementSpawned(element) + -- This needs to be slightly delayed (or moved elsewhere) or the first element will always trigger the group spawned event as it is not known that more elements are in the group. + self:__ElementSpawned(0.05, element) end return element diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 61bd09885..351669719 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -849,8 +849,8 @@ function OPSTRANSPORT:onafterStatus(From, Event, To) local carrier=cargo.opsgroup:_GetMyCarrierElement() local name=carrier and carrier.name or "none" local cstate=carrier and carrier.status or "N/A" - text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s", - cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered)) + text=text..string.format("\n- %s: %s [%s], weight=%d kg, carrier=%s [%s], delivered=%s [UID=%s]", + cargo.opsgroup:GetName(), cargo.opsgroup.cargoStatus:upper(), cargo.opsgroup:GetState(), cargo.opsgroup:GetWeightTotal(), name, cstate, tostring(cargo.delivered), tostring(cargo.opsgroup.cargoTransportUID)) end text=text..string.format("\nCarriers:") diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 266fe7f4c..2a535bbe2 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -608,15 +608,22 @@ end -- @return #number TACAN channel or *nil* if no channel is free. function SQUADRON:FetchTacan() + -- Get the smallest free channel if there is one. + local freechannel=nil for channel,free in pairs(self.tacanChannel) do - if free then - self:T(self.lid..string.format("Checking out Tacan channel %d", channel)) - self.tacanChannel[channel]=false - return channel + if free then + if freechannel==nil or channel Date: Mon, 2 Aug 2021 11:57:45 +0200 Subject: [PATCH 382/382] OPS --- .../Moose/Functional/Warehouse.lua | 21 ++++++- Moose Development/Moose/Ops/AirWing.lua | 29 +++++++++- Moose Development/Moose/Ops/Airboss.lua | 43 ++++++++------- Moose Development/Moose/Ops/Auftrag.lua | 54 ++++++++++++++++-- Moose Development/Moose/Ops/FlightGroup.lua | 12 ++-- Moose Development/Moose/Ops/OpsGroup.lua | 55 +++++++++---------- Moose Development/Moose/Ops/OpsTransport.lua | 2 +- Moose Development/Moose/Ops/Squadron.lua | 39 ++++++++++--- Moose Development/Moose/Ops/Target.lua | 26 +++++++-- 9 files changed, 204 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1c571b93c..e352dce84 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2647,6 +2647,25 @@ function WAREHOUSE:_CheckParkingValid(spot) return false end +--- Check parking ID for an asset. +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot Parking spot. +-- @return #boolean If true, parking is valid. +function WAREHOUSE:_CheckParkingAsset(spot, asset) + + if asset.parkingIDs==nil then + return true + end + + for _,id in pairs(asset.parkingIDs or {}) do + if spot.TerminalID==id then + return true + end + end + + return false +end + --- Enable auto save of warehouse assets at mission end event. -- @param #WAREHOUSE self @@ -7848,7 +7867,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) 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) and self:_CheckParkingValid(parkingspot) and airbase:_CheckParkingLists(parkingspot.TerminalID) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 5e702896b..58e8ebb95 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -155,7 +155,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.6.0" +AIRWING.version="0.7.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -745,6 +745,11 @@ end -- @return #AIRWING.PatrolData Patrol point table. function AIRWING:NewPatrolPoint(Type, Coordinate, Altitude, Speed, Heading, LegLength, RefuelSystem) + -- Check if a zone was passed instead of a coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + local patrolpoint={} --#AIRWING.PatrolData patrolpoint.type=Type or "Unknown" patrolpoint.coord=Coordinate or self:GetCoordinate():Translate(UTILS.NMToMeters(math.random(10, 15)), math.random(360)) @@ -994,7 +999,7 @@ function AIRWING:CheckTANKER() for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER then + if mission:IsNotOver() and mission.type==AUFTRAG.Type.TANKER and mission.patroldata then if mission.refuelSystem==Unit.RefuelingSystem.BOOM_AND_RECEPTACLE then Nboom=Nboom+1 elseif mission.refuelSystem==Unit.RefuelingSystem.PROBE_AND_DROGUE then @@ -1566,6 +1571,9 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Set takeoff type. asset.takeoffType=squad.takeoffType + + -- Set parking IDs. + asset.parkingIDs=squad.parkingIDs -- Create callsign and modex (needs to be after grouping). squad:GetCallsign(asset) @@ -2032,6 +2040,23 @@ function AIRWING:CountAssets() return N end + +--- Count total number of assets that are in the warehouse stock (not spawned). +-- @param #AIRWING self +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @return #number Amount of asset groups in stock. +function AIRWING:CountAssetsInStock(MissionTypes) + + local N=0 + + for _,_squad in pairs(self.squadrons) do + local squad=_squad --Ops.Squadron#SQUADRON + N=N+squad:CountAssetsInStock(MissionTypes) + end + + return N +end + --- Count assets on mission. -- @param #AIRWING self -- @param #table MissionTypes Types on mission to be checked. Default all. diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index e0cff612e..d49d52b6f 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3601,36 +3601,39 @@ function AIRBOSS:_CheckAIStatus() -- Unit local unit=element.unit - -- Get lineup and distance to carrier. - local lineup=self:_Lineup(unit, true) + if unit and unit:IsAlive() then - local unitcoord=unit:GetCoord() + -- Get lineup and distance to carrier. + local lineup=self:_Lineup(unit, true) - local dist=unitcoord:Get2DDistance(self:GetCoord()) + local unitcoord=unit:GetCoord() - -- Distance in NM. - local distance=UTILS.MetersToNM(dist) + local dist=unitcoord:Get2DDistance(self:GetCoord()) - -- Altitude in ft. - local alt=UTILS.MetersToFeet(unitcoord.y) + -- Distance in NM. + local distance=UTILS.MetersToNM(dist) - -- Check if parameters are right and flight is in the groove. - if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then + -- Altitude in ft. + local alt=UTILS.MetersToFeet(unitcoord.y) - -- Paddles: Call the ball! - self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) + -- Check if parameters are right and flight is in the groove. + if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then - -- Pilot: "405, Hornet Ball, 3.2" - self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) + -- Paddles: Call the ball! + self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) - -- Paddles: Roger ball after 0.5 seconds. - self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true) + -- Pilot: "405, Hornet Ball, 3.2" + self:_LSOCallAircraftBall(element.onboard,self:_GetACNickname(unit:GetTypeName()), self:_GetFuelState(unit)/1000) - -- Flight element called the ball. - element.ballcall=true + -- Paddles: Roger ball after 0.5 seconds. + self:RadioTransmission(self.LSORadio, self.LSOCall.ROGERBALL, nil, nil, 0.5, true) - -- This is for the whole flight. Maybe we need it. - flight.ballcall=true + -- Flight element called the ball. + element.ballcall=true + + -- This is for the whole flight. Maybe we need it. + flight.ballcall=true + end end end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index c9bd4ca98..de8f0e82b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -53,8 +53,9 @@ -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. -- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. --- @field #table conditionSuccess If all stop conditions are true, the mission is cancelled. --- @field #table conditionFailure If all stop conditions are true, the mission is cancelled. +-- @field #table conditionSuccess If all conditions are true, the mission is cancelled. +-- @field #table conditionFailure If all conditions are true, the mission is cancelled. +-- @field #table conditionPush If all conditions are true, the mission is executed. Before, the group(s) wait at the mission execution waypoint. -- -- @field #number orbitSpeed Orbit speed in m/s. -- @field #number orbitAltitude Orbit altitude in meters. @@ -288,6 +289,7 @@ AUFTRAG = { conditionStart = {}, conditionSuccess = {}, conditionFailure = {}, + conditionPush = {}, } --- Global mission counter. @@ -445,7 +447,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.7.0" +AUFTRAG.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1825,6 +1827,26 @@ function AUFTRAG:AddConditionFailure(ConditionFunction, ...) return self end +--- Add push condition. +-- @param #AUFTRAG self +-- @param #function ConditionFunction If this function returns `true`, the mission is executed. +-- @param ... Condition function arguments if any. +-- @return #AUFTRAG self +function AUFTRAG:AddConditionPush(ConditionFunction, ...) + + local condition={} --#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionPush, condition) + + return self +end + --- Assign airwing squadron(s) to the mission. Only these squads will be considered for the job. -- @param #AUFTRAG self @@ -1983,12 +2005,12 @@ function AUFTRAG:IsReadyToGo() local Tnow=timer.getAbsTime() -- Start time did not pass yet. - if self.Tstart and Tnowself.Tstop or false then + if self.Tstop and Tnow>self.Tstop then return false end @@ -2014,7 +2036,7 @@ function AUFTRAG:IsReadyToCancel() local Tnow=timer.getAbsTime() -- Stop time already passed. - if self.Tstop and Tnow>self.Tstop then + if self.Tstop and Tnow>=self.Tstop then return true end @@ -2038,6 +2060,26 @@ function AUFTRAG:IsReadyToCancel() return false end +--- Check if mission is ready to be pushed. +-- * Mission push time already passed. +-- * All push conditions are true. +-- @param #AUFTRAG self +-- @return #boolean If true, mission groups can push. +function AUFTRAG:IsReadyToPush() + + local Tnow=timer.getAbsTime() + + -- Push time passed? + if self.Tpush and Tnow0) then + + if Mission:IsReadyToPush() then - if Mission.Tpush then + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil - local Tnow=timer.getAbsTime() + else - -- Time to push - local dt=Mission.Tpush-Tnow - - -- Push time not reached. - if Tnow