diff --git a/.github/workflows/build-includes.yml b/.github/workflows/build-includes.yml index dd1f90e6a..2d421a8fa 100644 --- a/.github/workflows/build-includes.yml +++ b/.github/workflows/build-includes.yml @@ -5,6 +5,8 @@ on: branches: - master - develop + - Apple/Develop + paths: - 'Moose Setup/**/*.lua' - 'Moose Development/**/*.lua' diff --git a/Moose Development/Moose/.vscode/settings.json b/Moose Development/Moose/.vscode/settings.json index 13211d027..4656f0257 100644 --- a/Moose Development/Moose/.vscode/settings.json +++ b/Moose Development/Moose/.vscode/settings.json @@ -1,7 +1,17 @@ { - "Lua.workspace.preloadFileSize": 1000, + "Lua.workspace.preloadFileSize": 10000, "Lua.diagnostics.disable": [ - "undefined-doc-name" + "undefined-doc-name", + "duplicate-set-field", + "trailing-space", + "need-check-nil", + "ambiguity-1", + "undefined-doc-param", + "redundant-parameter", + "param-type-mismatch", + "deprecated", + "undefined-global", + "lowercase-global" ], "Lua.diagnostics.globals": [ "BASE", diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 0c8aff8fa..d86c20bbe 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -11,7 +11,7 @@ -- @module AI.AI_A2A_Cap -- @image AI_Combat_Air_Patrol.JPG ---- @type AI_A2A_CAP +-- @type AI_A2A_CAP -- @extends AI.AI_Air_Patrol#AI_AIR_PATROL -- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 626e36d60..8f85f3cd2 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -13,8 +13,8 @@ ---- @type AI_A2A_GCI --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE +-- @type AI_A2A_GCI +-- @extends AI.AI_A2A#AI_A2A --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. @@ -39,6 +39,8 @@ -- -- ## 2. AI_A2A_GCI is a FSM -- +-- ![Process](..\Presentations\AI_GCI\Dia2.JPG) +-- -- ### 2.1 AI_A2A_GCI States -- -- * **None** ( Group ): The process is not started yet. diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 1e66eb167..71b392db1 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -10,8 +10,8 @@ -- @image AI_Air_Patrolling.JPG ---- @type AI_A2A_PATROL --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL +-- @type AI_A2A_PATROL +-- @extends AI.AI_A2A#AI_A2A --- Implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}. -- diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index 28acea07f..7a6a80fca 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -11,9 +11,8 @@ -- @module AI.AI_A2G_BAI -- @image AI_Air_To_Ground_Engage.JPG ---- @type AI_A2G_BAI --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE +-- @type AI_A2G_BAI +-- @extends AI.AI_A2A_Engage#AI_A2A_Engage -- TODO: Documentation. This class does not exist, unable to determine what it extends. --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- @@ -47,7 +46,7 @@ AI_A2G_BAI = { function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local self = BASE:Inherit( self, AI_Air_Engage ) diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index 7bae80ea3..16c9cb976 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -11,9 +11,8 @@ -- @module AI.AI_A2G_CAS -- @image AI_Air_To_Ground_Engage.JPG ---- @type AI_A2G_CAS --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE +-- @type AI_A2G_CAS +-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL TODO: Documentation. This class does not exist, unable to determine what it extends. --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- @@ -47,7 +46,7 @@ AI_A2G_CAS = { function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) local AI_Air = AI_AIR:New( AIGroup ) - local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local self = BASE:Inherit( self, AI_Air_Engage ) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 451414f7f..0396e4e9f 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3895,10 +3895,14 @@ do -- AI_A2G_DISPATCHER if Squadron then local FirstUnit = AttackSetUnit:GetRandomSurely() + if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE if self.SetSendPlayerMessages then Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end + else + return + end end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -4784,4 +4788,5 @@ end Squadron.ResourceCount = Squadron.ResourceCount - Amount end self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount}) - end \ No newline at end of file + end + \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index 42662862c..17f9f86f5 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -13,9 +13,8 @@ ---- @type AI_A2G_SEAD --- @extends AI.AI_Air_Patrol#AI_AIR_PATROL --- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE +-- @type AI_A2G_SEAD +-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL --- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders. diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 08c85e751..d3d11cf16 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -9,6 +9,7 @@ -- @module AI.AI_Air -- @image MOOSE.JPG +--- -- @type AI_AIR -- @extends Core.Fsm#FSM_CONTROLLABLE @@ -656,8 +657,8 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Create a route point of type air. local FromRTBRoutePoint = FromCoord:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) @@ -665,8 +666,8 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) --- Create a route point of type air. local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, RTBSpeed, true ) @@ -760,10 +761,10 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) --- Create a route point of type air. - local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) + local FromRefuelRoutePoint = FromRefuelCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) --- Create a route point of type air. NOT used! - local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToRefuelSpeed, true) + local ToRefuelRoutePoint = Tanker:GetCoordinate():WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToRefuelSpeed, true) self:F( { ToRefuelSpeed = ToRefuelSpeed } ) diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index 70898d2ba..ff3327421 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -453,7 +453,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac --- Calculate the target route point. - local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = FromWP @@ -462,7 +462,7 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = ToWP @@ -536,7 +536,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU local EngageRoute = {} local AttackTasks = {} - local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = FromWP self:SetTargetDistance( TargetCoord ) -- For RTB status check @@ -544,7 +544,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ) - local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true) + local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, EngageSpeed, true) EngageRoute[#EngageRoute+1] = ToWP -- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task! diff --git a/Moose Development/Moose/AI/AI_Air_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua index 3185f987e..2b4e1a937 100644 --- a/Moose Development/Moose/AI/AI_Air_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -9,7 +9,7 @@ -- @module AI.AI_Air_Patrol -- @image AI_Air_To_Ground_Patrol.JPG ---- @type AI_AIR_PATROL +-- @type AI_AIR_PATROL -- @extends AI.AI_Air#AI_AIR --- The AI_AIR_PATROL class implements the core functions to patrol a @{Core.Zone} by an AI @{Wrapper.Group} @@ -309,7 +309,7 @@ function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) local speedkmh=ToTargetSpeed - local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) + local FromWP = CurrentCoord:WaypointAir(self.PatrolAltType or "RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) PatrolRoute[#PatrolRoute+1] = FromWP if self.racetrack then @@ -359,9 +359,9 @@ function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) else --- Create a route point of type air. - local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, ToTargetSpeed, true) + local ToWP = ToTargetCoord:WaypointAir(self.PatrolAltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true) PatrolRoute[#PatrolRoute+1] = ToWP - + local Tasks = {} Tasks[#Tasks+1] = AIPatrol:TaskFunction("AI_AIR_PATROL.___PatrolRoute", self) PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) diff --git a/Moose Development/Moose/AI/AI_BAI.lua b/Moose Development/Moose/AI/AI_BAI.lua index 9877ac51b..237bb9ab0 100644 --- a/Moose Development/Moose/AI/AI_BAI.lua +++ b/Moose Development/Moose/AI/AI_BAI.lua @@ -174,8 +174,7 @@ function AI_BAI_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. - -- @param #string To The To State string. - + -- @param #string To The To State string. -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Engage. @@ -408,7 +407,7 @@ function AI_BAI_ZONE:onafterStart( Controllable, From, Event, To ) self:SetDetectionDeactivated() -- When not engaging, set the detection off. end ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable +-- @param Wrapper.Controllable#CONTROLLABLE AIControllable function _NewEngageRoute( AIControllable ) AIControllable:T( "NewEngageRoute" ) @@ -417,7 +416,7 @@ function _NewEngageRoute( AIControllable ) end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -429,7 +428,7 @@ function AI_BAI_ZONE:onbeforeEngage( Controllable, From, Event, To ) end end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -478,7 +477,7 @@ function AI_BAI_ZONE:onafterTarget( Controllable, From, Event, To ) end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -488,7 +487,7 @@ function AI_BAI_ZONE:onafterAbort( Controllable, From, Event, To ) self:__Route( 1 ) end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -522,12 +521,12 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -578,13 +577,13 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, self:T2( ToTargetVec2 ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -612,7 +611,7 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To, end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -623,7 +622,7 @@ function AI_BAI_ZONE:onafterAccomplish( Controllable, From, Event, To ) end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -637,7 +636,7 @@ function AI_BAI_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) end ---- @param #AI_BAI_ZONE self +-- @param #AI_BAI_ZONE self -- @param Core.Event#EVENTDATA EventData function AI_BAI_ZONE:OnEventDead( EventData ) self:F( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/AI/AI_Balancer.lua b/Moose Development/Moose/AI/AI_Balancer.lua index b64e27057..827a17764 100644 --- a/Moose Development/Moose/AI/AI_Balancer.lua +++ b/Moose Development/Moose/AI/AI_Balancer.lua @@ -27,7 +27,7 @@ -- @module AI.AI_Balancer -- @image AI_Balancing.JPG ---- @type AI_BALANCER +-- @type AI_BALANCER -- @field Core.Set#SET_CLIENT SetClient -- @field Core.Spawn#SPAWN SpawnAI -- @field Wrapper.Group#GROUP Test @@ -220,16 +220,9 @@ function AI_BALANCER:onenterReturning( SetGroup, From, Event, To, AIGroup ) AIGroup:MessageToRed( "Returning to home base ...", 30 ) else -- Okay, we need to send this Group back to the nearest base of the Coalition of the AI. - --TODO: i need to rework the POINT_VEC2 thing. - local PointVec2 = POINT_VEC2:New( AIGroup:GetVec2().x, AIGroup:GetVec2().y ) + local PointVec2 = COORDINATE:New(AIGroup:GetVec2().x, 0, AIGroup:GetVec2().y) local ClosestAirbase = self.ReturnAirbaseSet:FindNearestAirbaseFromPointVec2( PointVec2 ) self:T( ClosestAirbase.AirbaseName ) - --[[ - AIGroup:MessageToRed( "Returning to " .. ClosestAirbase:GetName().. " ...", 30 ) - local RTBRoute = AIGroup:RouteReturnToAirbase( ClosestAirbase ) - AIGroupTemplate.route = RTBRoute - AIGroup:Respawn( AIGroupTemplate ) - ]] AIGroup:RouteRTB(ClosestAirbase) end diff --git a/Moose Development/Moose/AI/AI_CAP.lua b/Moose Development/Moose/AI/AI_CAP.lua index d59931452..35604e9f9 100644 --- a/Moose Development/Moose/AI/AI_CAP.lua +++ b/Moose Development/Moose/AI/AI_CAP.lua @@ -31,7 +31,7 @@ -- @module AI.AI_CAP -- @image AI_Combat_Air_Patrol.JPG ---- @type AI_CAP_ZONE +-- @type AI_CAP_ZONE -- @field Wrapper.Controllable#CONTROLLABLE AIControllable The @{Wrapper.Controllable} patrolling. -- @field Core.Zone#ZONE_BASE TargetZone The @{Core.Zone} where the patrol needs to be executed. -- @extends AI.AI_Patrol#AI_PATROL_ZONE @@ -344,7 +344,7 @@ function AI_CAP_ZONE:onafterStart( Controllable, From, Event, To ) end ---- @param AI.AI_CAP#AI_CAP_ZONE +-- @param AI.AI_CAP#AI_CAP_ZONE -- @param Wrapper.Group#GROUP EngageGroup function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm ) @@ -355,7 +355,7 @@ function AI_CAP_ZONE.EngageRoute( EngageGroup, Fsm ) end end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -367,7 +367,7 @@ function AI_CAP_ZONE:onbeforeEngage( Controllable, From, Event, To ) end end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -395,7 +395,7 @@ function AI_CAP_ZONE:onafterDetected( Controllable, From, Event, To ) end end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -405,7 +405,7 @@ function AI_CAP_ZONE:onafterAbort( Controllable, From, Event, To ) self:__Route( 1 ) end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -423,12 +423,12 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToEngageZoneSpeed, true ) @@ -445,13 +445,13 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToPatrolRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) @@ -505,7 +505,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To ) end end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -515,7 +515,7 @@ function AI_CAP_ZONE:onafterAccomplish( Controllable, From, Event, To ) self:SetDetectionOff() end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -528,7 +528,7 @@ function AI_CAP_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) end end ---- @param #AI_CAP_ZONE self +-- @param #AI_CAP_ZONE self -- @param Core.Event#EVENTDATA EventData function AI_CAP_ZONE:OnEventDead( EventData ) self:F( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/AI/AI_CAS.lua b/Moose Development/Moose/AI/AI_CAS.lua index c6d2c11f4..d1ac6cdac 100644 --- a/Moose Development/Moose/AI/AI_CAS.lua +++ b/Moose Development/Moose/AI/AI_CAS.lua @@ -162,7 +162,6 @@ function AI_CAS_ZONE:New( PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Engage. @@ -363,7 +362,7 @@ function AI_CAS_ZONE:onafterStart( Controllable, From, Event, To ) self:SetDetectionDeactivated() -- When not engaging, set the detection off. end ---- @param AI.AI_CAS#AI_CAS_ZONE +-- @param AI.AI_CAS#AI_CAS_ZONE -- @param Wrapper.Group#GROUP EngageGroup function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm ) @@ -375,7 +374,7 @@ function AI_CAS_ZONE.EngageRoute( EngageGroup, Fsm ) end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -387,7 +386,7 @@ function AI_CAS_ZONE:onbeforeEngage( Controllable, From, Event, To ) end end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -420,7 +419,7 @@ function AI_CAS_ZONE:onafterTarget( Controllable, From, Event, To ) end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -430,7 +429,7 @@ function AI_CAS_ZONE:onafterAbort( Controllable, From, Event, To ) self:__Route( 1 ) end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -466,12 +465,12 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToEngageZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -508,13 +507,13 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, self:T2( ToTargetVec2 ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, self.EngageAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, self.EngageSpeed, true ) @@ -530,7 +529,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To, end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -541,7 +540,7 @@ function AI_CAS_ZONE:onafterAccomplish( Controllable, From, Event, To ) end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -555,7 +554,7 @@ function AI_CAS_ZONE:onafterDestroy( Controllable, From, Event, To, EventData ) end ---- @param #AI_CAS_ZONE self +-- @param #AI_CAS_ZONE self -- @param Core.Event#EVENTDATA EventData function AI_CAS_ZONE:OnEventDead( EventData ) self:F( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index 6088ea865..fc9037fea 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -9,7 +9,7 @@ -- @module AI.AI_Cargo_APC -- @image AI_Cargo_Dispatching_For_APC.JPG ---- @type AI_CARGO_APC +-- @type AI_CARGO_APC -- @extends AI.AI_Cargo#AI_CARGO diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 9dad75f25..3e9589d95 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -9,7 +9,7 @@ -- @module AI.AI_Cargo_Airplane -- @image AI_Cargo_Dispatching_For_Airplanes.JPG ---- @type AI_CARGO_AIRPLANE +-- @type AI_CARGO_AIRPLANE -- @extends Core.Fsm#FSM_CONTROLLABLE @@ -440,7 +440,7 @@ 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, true, Airbase) + local ToWaypoint = AirbasePointVec2:WaypointAir(COORDINATE.WaypointAltType.BARO, "Land", "Landing", Speed or Airplane:GetSpeedMax()*0.8, true, Airbase) --ToWaypoint["airdromeId"] = Airbase:GetID() --ToWaypoint["speed_locked"] = true diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index ab25a8e60..3d98522e1 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -30,7 +30,7 @@ -- @module AI.AI_Cargo_Dispatcher_APC -- @image AI_Cargo_Dispatching_For_APC.JPG ---- @type AI_CARGO_DISPATCHER_APC +-- @type AI_CARGO_DISPATCHER_APC -- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua index a971936f6..d3a7c78ac 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua @@ -24,7 +24,7 @@ -- @image AI_Cargo_Dispatching_For_Airplanes.JPG ---- @type AI_CARGO_DISPATCHER_AIRPLANE +-- @type AI_CARGO_DISPATCHER_AIRPLANE -- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua index c391324e4..b219c78b0 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua @@ -25,7 +25,7 @@ -- @module AI.AI_Cargo_Dispatcher_Helicopter -- @image AI_Cargo_Dispatching_For_Helicopters.JPG ---- @type AI_CARGO_DISPATCHER_HELICOPTER +-- @type AI_CARGO_DISPATCHER_HELICOPTER -- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua index 152ea7881..6fc670e40 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -23,7 +23,7 @@ -- @module AI.AI_Cargo_Dispatcher_Ship -- @image AI_Cargo_Dispatcher.JPG ---- @type AI_CARGO_DISPATCHER_SHIP +-- @type AI_CARGO_DISPATCHER_SHIP -- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER @@ -160,7 +160,7 @@ AI_CARGO_DISPATCHER_SHIP = { -- 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() +-- local ShippingLane = SET_GROUP:New():FilterPrefixes( "ShippingLane" ):FilterOnce():GetSetObjects() -- -- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane ) -- AICargoDispatcherShip:Start() diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 6edfc3894..207a1ab8e 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -9,7 +9,7 @@ -- @module AI.AI_Cargo_Helicopter -- @image AI_Cargo_Dispatching_For_Helicopters.JPG ---- @type AI_CARGO_HELICOPTER +-- @type AI_CARGO_HELICOPTER -- @extends Core.Fsm#FSM_CONTROLLABLE @@ -287,7 +287,7 @@ function AI_CARGO_HELICOPTER:SetLandingSpeedAndHeight(speed, height) return self end ---- @param #AI_CARGO_HELICOPTER self +-- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param From -- @param Event @@ -326,7 +326,7 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) end ---- @param #AI_CARGO_HELICOPTER self +-- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param From -- @param Event @@ -367,8 +367,8 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina -- local CoordinateFrom = Helicopter:GetCoordinate() -- local WaypointFrom = CoordinateFrom:WaypointAir( -- "RADIO", --- POINT_VEC3.RoutePointType.TurningPoint, --- POINT_VEC3.RoutePointAction.TurningPoint, +-- COORDINATE.WaypointType.TurningPoint, +-- COORDINATE.WaypointAction.TurningPoint, -- Speed, -- true -- ) @@ -380,8 +380,8 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina local WaypointTo = CoordinateTo:WaypointAir( "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, 50, true ) @@ -409,7 +409,7 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina end ---- @param #AI_CARGO_HELICOPTER self +-- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param From -- @param Event @@ -427,7 +427,7 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina 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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 50, true) Route[#Route+1] = WaypointTo local Tasks = {} @@ -496,14 +496,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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint,_speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointTo @@ -563,7 +563,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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointFrom Route[#Route+1] = WaypointFrom @@ -573,7 +573,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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, _speed, true) Route[#Route+1] = WaypointTo Route[#Route+1] = WaypointTo @@ -631,7 +631,7 @@ 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", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointFrom --- Create a route point of type air. @@ -639,7 +639,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat local landheight = CoordinateTo:GetLandHeight() -- get target height 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) + local WaypointTo = CoordinateTo:WaypointAir("RADIO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) Route[#Route+1] = WaypointTo diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua index 669da09b5..5639c52da 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Ship.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -9,7 +9,7 @@ -- @module AI.AI_Cargo_Ship -- @image AI_Cargo_Dispatcher.JPG ---- @type AI_CARGO_SHIP +-- @type AI_CARGO_SHIP -- @extends AI.AI_Cargo#AI_CARGO --- Brings a dynamic cargo handling capability for an AI naval group. diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua index 0b3180910..160c2beed 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua @@ -15,7 +15,7 @@ -- @image MOOSE.JPG ---- @type AI_ESCORT_DISPATCHER_REQUEST +-- @type AI_ESCORT_DISPATCHER_REQUEST -- @extends Core.Fsm#FSM @@ -33,7 +33,7 @@ AI_ESCORT_DISPATCHER_REQUEST = { ClassName = "AI_ESCORT_DISPATCHER_REQUEST", } ---- @field #list +-- @field #list AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {} @@ -80,7 +80,7 @@ function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To ) end ---- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param #AI_ESCORT_DISPATCHER_REQUEST self -- @param Core.Event#EVENTDATA EventData function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData ) @@ -97,7 +97,7 @@ function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData ) end ---- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param #AI_ESCORT_DISPATCHER_REQUEST self -- @param Core.Event#EVENTDATA EventData function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData ) diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua index 08bee2f64..3251d3717 100644 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -136,12 +136,12 @@ -- -- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. -- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- + -- + -- # Developer Note + -- + -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE + -- Therefore, this class is considered to be deprecated + -- -- === -- -- ### Authors: **FlightControl** @@ -153,7 +153,7 @@ ---- @type AI_ESCORT_REQUEST +-- @type AI_ESCORT_REQUEST -- @extends AI.AI_Escort#AI_ESCORT --- AI_ESCORT_REQUEST class @@ -228,7 +228,7 @@ function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortNa return self end ---- @param #AI_ESCORT_REQUEST self +-- @param #AI_ESCORT_REQUEST self function AI_ESCORT_REQUEST:SpawnEscort() local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) @@ -253,7 +253,7 @@ function AI_ESCORT_REQUEST:SpawnEscort() self:_InitEscortMenus( EscortGroup ) self:_InitEscortRoute( EscortGroup ) - --- @param #AI_ESCORT self + -- @param #AI_ESCORT self -- @param Core.Event#EVENTDATA EventData function EscortGroup:OnEventDeadOrCrash( EventData ) self:F( { "EventDead", EventData } ) @@ -268,7 +268,7 @@ function AI_ESCORT_REQUEST:SpawnEscort() end ---- @param #AI_ESCORT_REQUEST self +-- @param #AI_ESCORT_REQUEST self -- @param Core.Set#SET_GROUP EscortGroupSet function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) @@ -290,14 +290,14 @@ function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) end ---- @param #AI_ESCORT_REQUEST self +-- @param #AI_ESCORT_REQUEST self -- @param Core.Set#SET_GROUP EscortGroupSet function AI_ESCORT_REQUEST:onafterStop( EscortGroupSet ) self:F() EscortGroupSet:ForEachGroup( - --- @param Wrapper.Group#GROUP EscortGroup + -- @param Core.Group#GROUP EscortGroup function( EscortGroup ) EscortGroup:WayPointInitialize() diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index d7d2d1966..326d6365b 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -34,8 +34,8 @@ -- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. -- @field #number FollowDistance The current follow distance. -- @field #boolean ReportTargets If true, nearby targets are reported. --- @field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. --- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. +-- @Field DCSTypes#AI.Option.Air.val.ROE OptionROE Which ROE is set to the FollowGroup. +-- @field DCSTypes#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the FollowGroup. -- @field #number dtFollow Time step between position updates. @@ -92,12 +92,12 @@ -- local LargeFormation = AI_FORMATION:New( LeaderUnit, FollowGroupSet, "Center Wing Formation", "Briefing" ) -- LargeFormation:FormationCenterWing( 500, 50, 0, 250, 250 ) -- LargeFormation:__Start( 1 ) --- --- # Developer Note --- --- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE --- Therefore, this class is considered to be deprecated --- + -- + -- # Developer Note + -- + -- Note while this class still works, it is no longer supported as the original author stopped active development of MOOSE + -- Therefore, this class is considered to be deprecated + -- -- @field #AI_FORMATION AI_FORMATION = { ClassName = "AI_FORMATION", @@ -117,7 +117,7 @@ AI_FORMATION = { AI_FORMATION.__Enum = {} ---- @type AI_FORMATION.__Enum.Formation +-- @type AI_FORMATION.__Enum.Formation -- @field #number None -- @field #number Line -- @field #number Trail @@ -142,7 +142,7 @@ AI_FORMATION.__Enum.Formation = { Box = 10, } ---- @type AI_FORMATION.__Enum.Mode +-- @type AI_FORMATION.__Enum.Mode -- @field #number Mission -- @field #number Formation AI_FORMATION.__Enum.Mode = { @@ -152,13 +152,13 @@ AI_FORMATION.__Enum.Mode = { Reconnaissance = "R", } ---- @type AI_FORMATION.__Enum.ReportType +-- @type AI_FORMATION.__Enum.ReportType -- @field #number All -- @field #number Airborne -- @field #number GroundRadar -- @field #number Ground AI_FORMATION.__Enum.ReportType = { - Airborne = "*", + All = "*", Airborne = "A", GroundRadar = "R", Ground = "G", @@ -725,7 +725,7 @@ function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, X for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() PointVec3:SetX( XStart + i * XSpace ) PointVec3:SetY( YStart + i * YSpace ) PointVec3:SetZ( ZStart + i * ZSpace ) @@ -877,7 +877,7 @@ function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() local Side = ( i % 2 == 0 ) and 1 or -1 local Row = i / 2 + 1 @@ -936,7 +936,7 @@ function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XS for FollowID, FollowGroup in pairs( FollowSet ) do - local PointVec3 = POINT_VEC3:New() + local PointVec3 = COORDINATE:New() local ZIndex = i % ZLevels local XIndex = math.floor( i / ZLevels ) @@ -996,7 +996,7 @@ function AI_FORMATION:SetFlightModeMission( FollowGroup ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) else self.FollowGroupSet:ForSomeGroupAlive( - --- @param Wrapper.Group#GROUP EscortGroup + -- @param Core.Group#GROUP EscortGroup function( FollowGroup ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) @@ -1020,7 +1020,7 @@ function AI_FORMATION:SetFlightModeAttack( FollowGroup ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) else self.FollowGroupSet:ForSomeGroupAlive( - --- @param Wrapper.Group#GROUP EscortGroup + -- @param Core.Group#GROUP EscortGroup function( FollowGroup ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) @@ -1044,7 +1044,7 @@ function AI_FORMATION:SetFlightModeFormation( FollowGroup ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) else self.FollowGroupSet:ForSomeGroupAlive( - --- @param Wrapper.Group#GROUP EscortGroup + -- @param Core.Group#GROUP EscortGroup function( FollowGroup ) FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) @@ -1222,7 +1222,7 @@ function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) local CVI = { x = CV2.x + CS * 10 * math.sin(Ca), y = GH2.y + Inclination, -- + FollowFormation.y, - y = GH2.y, + --y = GH2.y, z = CV2.z + CS * 10 * math.cos(Ca), } diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index d5ce61d72..408e3b1a6 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -751,12 +751,12 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if not CurrentVec2 then return end --Done: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TakeOffParking, - POINT_VEC3.RoutePointAction.FromParkingArea, + COORDINATE.WaypointType.TakeOffParking, + COORDINATE.WaypointAction.FromParkingArea, ToPatrolZoneSpeed, true ) @@ -767,12 +767,12 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) if not CurrentVec2 then return end --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) @@ -792,13 +792,13 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To ) self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) + local ToTargetPointVec3 = COORDINATE:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) --- Create a route point of type air. local ToTargetRoutePoint = ToTargetPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToTargetSpeed, true ) @@ -846,7 +846,6 @@ function AI_PATROL_ZONE:onafterStatus() OldAIControllable:SetTask( TimedOrbitTask, 10 ) RTB = true - else end -- TODO: Check GROUP damage function. @@ -856,6 +855,16 @@ function AI_PATROL_ZONE:onafterStatus() RTB = true end + if self:IsInstanceOf("AI_CAS") or self:IsInstanceOf("AI_BAI") then + local atotal,shells,rockets,bombs,missiles = self.Controllable:GetAmmunition() + local arelevant = rockets+bombs + if arelevant == 0 or missiles == 0 then + RTB = true + self:T({total=atotal,shells=shells,rockets=rockets,bombs=bombs,missiles=missiles}) + self:T( self.Controllable:GetName() .. " is out of ammo, RTB!" ) + end + end + if RTB == true then self:RTB() else @@ -881,12 +890,12 @@ function AI_PATROL_ZONE:onafterRTB() --DONE: Create GetAltitude function for GROUP, and delete GetUnit(1). --local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() local CurrentAltitude = self.Controllable:GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) + local CurrentPointVec3 = COORDINATE:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) local ToPatrolZoneSpeed = self.PatrolMaxSpeed local CurrentRoutePoint = CurrentPointVec3:WaypointAir( self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, + COORDINATE.WaypointType.TurningPoint, + COORDINATE.WaypointAction.TurningPoint, ToPatrolZoneSpeed, true ) diff --git a/Moose Development/Moose/Actions/Act_Account.lua b/Moose Development/Moose/Actions/Act_Account.lua index 5b809af44..a72a7b445 100644 --- a/Moose Development/Moose/Actions/Act_Account.lua +++ b/Moose Development/Moose/Actions/Act_Account.lua @@ -274,7 +274,7 @@ do -- ACT_ACCOUNT_DEADS --- DCS Events - --- @param #ACT_ACCOUNT_DEADS self + -- @param #ACT_ACCOUNT_DEADS self -- @param Core.Event#EVENTDATA EventData function ACT_ACCOUNT_DEADS:OnEventHit( EventData ) self:T( { "EventDead", EventData } ) @@ -285,7 +285,7 @@ do -- ACT_ACCOUNT_DEADS end end - --- @param #ACT_ACCOUNT_DEADS self + -- @param #ACT_ACCOUNT_DEADS self -- @param Core.Event#EVENTDATA EventData function ACT_ACCOUNT_DEADS:onfuncEventDead( EventData ) self:T( { "EventDead", EventData } ) @@ -297,7 +297,7 @@ do -- ACT_ACCOUNT_DEADS --- DCS Events - --- @param #ACT_ACCOUNT_DEADS self + -- @param #ACT_ACCOUNT_DEADS self -- @param Core.Event#EVENTDATA EventData function ACT_ACCOUNT_DEADS:onfuncEventCrash( EventData ) self:T( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/Actions/Act_Assist.lua b/Moose Development/Moose/Actions/Act_Assist.lua index 9b4744561..2ae132ac1 100644 --- a/Moose Development/Moose/Actions/Act_Assist.lua +++ b/Moose Development/Moose/Actions/Act_Assist.lua @@ -200,7 +200,7 @@ do -- ACT_ASSIST_SMOKE_TARGETS_ZONE function ACT_ASSIST_SMOKE_TARGETS_ZONE:onenterSmoking( ProcessUnit, From, Event, To ) self.TargetSetUnit:ForEachUnit( - --- @param Wrapper.Unit#UNIT SmokeUnit + -- @param Wrapper.Unit#UNIT SmokeUnit function( SmokeUnit ) if math.random( 1, ( 100 * self.TargetSetUnit:Count() ) / 4 ) <= 100 then SCHEDULER:New( self, diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 8b7d6040e..413beb514 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -275,14 +275,14 @@ -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] UnBoard -- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. --- UnBoards the cargo to a Carrier. The event will create a movement (= running or driving) of the cargo from the Carrier. -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] __UnBoard -- @param #CARGO self -- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo should run after onboarding. If not provided, the cargo will run to 60 meters behind the Carrier location. -- Load @@ -307,14 +307,14 @@ -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] UnLoad -- @param #CARGO self --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. --- UnLoads the cargo to a Carrier. The event will unload the cargo from the Carrier. There will be no movement simulated of the cargo loading. -- The cargo must be in the **Loaded** state. -- @function [parent=#CARGO] __UnLoad -- @param #CARGO self -- @param #number DelaySeconds The amount of seconds to delay the action. --- @param Core.Point#POINT_VEC2 ToPointVec2 (optional) @{Core.Point#POINT_VEC2) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. +-- @param Core.Point#COORDINATE ToPointVec2 (optional) @{Core.Point#COORDINATE) to where the cargo will be placed after unloading. If not provided, the cargo will be placed 60 meters behind the Carrier location. -- State Transition Functions @@ -467,7 +467,7 @@ do -- CARGO self.Type = Type self.Name = Name self.Weight = Weight or 0 - self.CargoObject = nil + self.CargoObject = nil -- Wrapper.Group#GROUP self.CargoCarrier = nil -- Wrapper.Client#CLIENT self.Representable = false self.Slingloadable = false @@ -897,7 +897,7 @@ do -- CARGO --- Get the current PointVec2 of the cargo. -- @param #CARGO self - -- @return Core.Point#POINT_VEC2 + -- @return Core.Point#COORDINATE function CARGO:GetPointVec2() return self.CargoObject:GetPointVec2() end @@ -1094,7 +1094,7 @@ do -- CARGO_REPRESENTABLE --- Route a cargo unit to a PointVec2. -- @param #CARGO_REPRESENTABLE self - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number Speed -- @return #CARGO_REPRESENTABLE function CARGO_REPRESENTABLE:RouteTo( ToPointVec2, Speed ) diff --git a/Moose Development/Moose/Cargo/CargoCrate.lua b/Moose Development/Moose/Cargo/CargoCrate.lua index c0fbdd631..c64016fd8 100644 --- a/Moose Development/Moose/Cargo/CargoCrate.lua +++ b/Moose Development/Moose/Cargo/CargoCrate.lua @@ -78,7 +78,7 @@ do -- CARGO_CRATE return self end - --- @param #CARGO_CRATE self + -- @param #CARGO_CRATE self -- @param Core.Event#EVENTDATA EventData function CARGO_CRATE:OnEventCargoDead( EventData ) @@ -114,7 +114,7 @@ do -- CARGO_CRATE -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 + -- @param Core.Point#COORDINATE function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 ) --self:T( { ToPointVec2, From, Event, To } ) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index ca6a96a69..45e1b5948 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -22,6 +22,7 @@ do -- CARGO_GROUP --- @type CARGO_GROUP -- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects. -- @field #string GroupName The name of the CargoGroup. + -- @field Wrapper.Group#GROUÜ CargoCarrier The carrier group. -- @extends Cargo.Cargo#CARGO_REPORTABLE --- Defines a cargo that is represented by a @{Wrapper.Group} object within the simulator. @@ -410,7 +411,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterUnBoard( From, Event, To, ToPointVec2, NearRadius, ... ) self:T( {From, Event, To, ToPointVec2, NearRadius } ) @@ -453,7 +454,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) --self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -491,7 +492,7 @@ do -- CARGO_GROUP -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 function CARGO_GROUP:onafterUnLoad( From, Event, To, ToPointVec2, ... ) --self:T( { From, Event, To, ToPointVec2 } ) @@ -598,7 +599,7 @@ do -- CARGO_GROUP end - --- Get the amount of cargo units in the group. + --- Get the underlying GROUP object from the CARGO_GROUP. -- @param #CARGO_GROUP self -- @return #CARGO_GROUP function CARGO_GROUP:GetGroup( Cargo ) @@ -771,3 +772,4 @@ do -- CARGO_GROUP end -- CARGO_GROUP + diff --git a/Moose Development/Moose/Cargo/CargoSlingload.lua b/Moose Development/Moose/Cargo/CargoSlingload.lua index ad26e8868..81bc5d95e 100644 --- a/Moose Development/Moose/Cargo/CargoSlingload.lua +++ b/Moose Development/Moose/Cargo/CargoSlingload.lua @@ -72,7 +72,7 @@ do -- CARGO_SLINGLOAD end - --- @param #CARGO_SLINGLOAD self + -- @param #CARGO_SLINGLOAD self -- @param Core.Event#EVENTDATA EventData function CARGO_SLINGLOAD:OnEventCargoDead( EventData ) diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index a1d86dd49..a76469870 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -72,7 +72,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 25 m. function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -145,7 +145,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -171,7 +171,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param Core.Point#COORDINATE ToPointVec2 -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:T( { From, Event, To, ToPointVec2, NearRadius } ) @@ -197,7 +197,7 @@ do -- CARGO_UNIT -- @param #string Event -- @param #string From -- @param #string To - -- @param Core.Point#POINT_VEC2 + -- @param Core.Point#COORDINATE function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 ) self:T( { ToPointVec2, From, Event, To } ) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index c4be4ef30..b1a12e740 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -26,7 +26,7 @@ -- @module Core.Base -- @image Core_Base.JPG -local _TraceOnOff = true +local _TraceOnOff = false -- default to no tracing local _TraceLevel = 1 local _TraceAll = false local _TraceClass = {} @@ -34,11 +34,12 @@ local _TraceClassMethod = {} local _ClassID = 0 ---- +--- Base class of everything -- @type BASE --- @field ClassName The name of the class. --- @field ClassID The ID number of the class. --- @field ClassNameAndID The name of the class concatenated with the ID number of the class. +-- @field #string ClassName The name of the class. +-- @field #number ClassID The ID number of the class. +-- @field #string ClassNameAndID The name of the class concatenated with the ID number of the class. +-- @field Core.Scheduler#SCHEDULER Scheduler The scheduler object. --- BASE class -- @@ -200,6 +201,7 @@ BASE = { States = {}, Debug = debug, Scheduler = nil, + Properties = {}, } -- @field #BASE.__ @@ -210,14 +212,6 @@ BASE._ = { Schedules = {}, --- Contains the Schedulers Active } ---- The Formation Class --- @type FORMATION --- @field Cone A cone formation. -FORMATION = { - Cone = "Cone", - Vee = "Vee", -} - --- BASE constructor. -- -- This is an example how to use the BASE:New() constructor in a new class definition when inheriting from BASE. @@ -741,7 +735,31 @@ do -- Event Handling -- @function [parent=#BASE] OnEventPlayerEnterAircraft -- @param #BASE self -- @param Core.Event#EVENTDATA EventData The EventData structure. - + + --- Occurs when a player creates a dynamic cargo object from the F8 ground crew menu. + -- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024. + -- @function [parent=#BASE] OnEventNewDynamicCargo + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Occurs when a player loads a dynamic cargo object with the F8 ground crew menu into a helo. + -- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024. + -- @function [parent=#BASE] OnEventDynamicCargoLoaded + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Occurs when a player unloads a dynamic cargo object with the F8 ground crew menu from a helo. + -- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024. + -- @function [parent=#BASE] OnEventDynamicCargoUnloaded + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + + --- Occurs when a dynamic cargo crate is removed. + -- *** NOTE *** this is a workarounf for DCS not creating these events as of Aug 2024. + -- @function [parent=#BASE] OnEventDynamicCargoRemoved + -- @param #BASE self + -- @param Core.Event#EVENTDATA EventData The EventData structure. + end --- Creation of a Birth Event. @@ -862,6 +880,62 @@ end world.onEvent(Event) end + + --- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event. + -- @param #BASE self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function BASE:CreateEventNewDynamicCargo(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.NewDynamicCargo, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event. + -- @param #BASE self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function BASE:CreateEventDynamicCargoLoaded(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoLoaded, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event. + -- @param #BASE self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function BASE:CreateEventDynamicCargoUnloaded(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoUnloaded, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event. + -- @param #BASE self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function BASE:CreateEventDynamicCargoRemoved(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoRemoved, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end --- The main event handling function... This function captures all events generated for the class. -- @param #BASE self @@ -1036,6 +1110,31 @@ function BASE:ClearState( Object, StateName ) end end +--- Set one property of an object. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @param Value The value that is stored. Note that the value can be a #string, but it can also be any other type! +function BASE:SetProperty(Key,Value) + self.Properties = self.Properties or {} + self.Properties[Key] = Value +end + +--- Get one property of an object by the key. +-- @param #BASE self +-- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! +-- @return Value The value that is stored. Note that the value can be a #string, but it can also be any other type! Nil if not found. +function BASE:GetProperty(Key) + self.Properties = self.Properties or {} + return self.Properties[Key] +end + +--- Get all of the properties of an object in a table. +-- @param #BASE self +-- @return #table of values, indexed by keys. +function BASE:GetProperties() + return self.Properties +end + -- Trace section -- Log a trace (only shown when trace is on) @@ -1200,7 +1299,7 @@ end -- @param Arguments A #table or any field. function BASE:F( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1215,7 +1314,7 @@ end -- @param Arguments A #table or any field. function BASE:F2( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1230,7 +1329,7 @@ end -- @param Arguments A #table or any field. function BASE:F3( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1274,7 +1373,7 @@ end -- @param Arguments A #table or any field. function BASE:T( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1289,7 +1388,7 @@ end -- @param Arguments A #table or any field. function BASE:T2( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 2 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1304,7 +1403,7 @@ end -- @param Arguments A #table or any field. function BASE:T3( Arguments ) - if BASE.Debug and _TraceOnOff then + if BASE.Debug and _TraceOnOff == true and _TraceLevel >= 3 then local DebugInfoCurrent = BASE.Debug.getinfo( 2, "nl" ) local DebugInfoFrom = BASE.Debug.getinfo( 3, "l" ) @@ -1336,7 +1435,7 @@ function BASE:E( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments) ) ) end end @@ -1363,8 +1462,7 @@ function BASE:I( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) ) + env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize(Arguments)) ) end end - diff --git a/Moose Development/Moose/Core/ClientMenu.lua b/Moose Development/Moose/Core/ClientMenu.lua index dae6195d2..5e7219add 100644 --- a/Moose Development/Moose/Core/ClientMenu.lua +++ b/Moose Development/Moose/Core/ClientMenu.lua @@ -20,7 +20,7 @@ -- -- @module Core.ClientMenu -- @image Core_Menu.JPG --- last change: May 2024 +-- last change: Jan 2025 -- TODO ---------------------------------------------------------------------------------------------------------------- @@ -57,9 +57,9 @@ --- -- @field #CLIENTMENU CLIENTMENU = { - ClassName = "CLIENTMENUE", + ClassName = "CLIENTMENU", lid = "", - version = "0.1.2", + version = "0.1.3", name = nil, path = nil, group = nil, @@ -417,7 +417,7 @@ end CLIENTMENUMANAGER = { ClassName = "CLIENTMENUMANAGER", lid = "", - version = "0.1.5a", + version = "0.1.6", name = nil, clientset = nil, menutree = {}, @@ -455,7 +455,7 @@ end -- @param #CLIENTMENUMANAGER self -- @param Core.Event#EVENTDATA EventData -- @return #CLIENTMENUMANAGER self -function CLIENTMENUMANAGER:_EventHandler(EventData) +function CLIENTMENUMANAGER:_EventHandler(EventData,Retry) self:T(self.lid.."_EventHandler: "..EventData.id) --self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName)) if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then @@ -468,6 +468,10 @@ function CLIENTMENUMANAGER:_EventHandler(EventData) if EventData.IniPlayerName and EventData.IniGroup then if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName) + if not Retry then + -- try again in 2 secs + self:ScheduleOnce(2,CLIENTMENUMANAGER._EventHandler,self,EventData,true) + end return self end --self:I(self.lid.."Join event for player: "..EventData.IniPlayerName) @@ -524,7 +528,7 @@ function CLIENTMENUMANAGER:InitAutoPropagation() self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:SetEventPriority(5) + self:SetEventPriority(6) return self end @@ -740,7 +744,7 @@ function CLIENTMENUMANAGER:AddEntry(Entry,Client) for _,_client in pairs(Set) do local client = _client -- Wrapper.Client#CLIENT if client and client:IsAlive() then - local playername = client:GetPlayerName() + local playername = client:GetPlayerName() or "None" local unitname = client:GetName() if not knownunits[unitname] then knownunits[unitname] = true diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index dec6bd64c..4aec2393a 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -20,6 +20,7 @@ -- * Manage database of hits to units and statics. -- * Manage database of destroys of units and statics. -- * Manage database of @{Core.Zone#ZONE_BASE} objects. +-- * Manage database of @{Wrapper.DynamicCargo#DYNAMICCARGO} objects alive in the mission. -- -- === -- @@ -39,6 +40,7 @@ -- @field #table STORAGES DCS warehouse storages. -- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes. -- @field #table SADL Used Link16 octal numbers for A10/C-II planes. +-- @field #table DYNAMICCARGO Dynamic Cargo objects. -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. @@ -54,6 +56,7 @@ -- * PLAYERS -- * CARGOS -- * STORAGES (DCS warehouses) +-- * DYNAMICCARGO -- -- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. -- @@ -97,6 +100,7 @@ DATABASE = { STORAGES = {}, STNS={}, SADL={}, + DYNAMICCARGO={}, } local _DATABASECoalition = @@ -135,7 +139,7 @@ function DATABASE:New() self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) - --self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM + self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM self:HandleEvent( EVENTS.Hit, self.AccountHits ) self:HandleEvent( EVENTS.NewCargo ) self:HandleEvent( EVENTS.DeleteCargo ) @@ -143,6 +147,8 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteZone ) --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) + -- DCS 2.9.7 Moose own dynamic cargo events + self:HandleEvent( EVENTS.DynamicCargoRemoved, self._EventOnDynamicCargoRemoved) self:_RegisterTemplates() self:_RegisterGroupsAndUnits() @@ -170,24 +176,30 @@ end --- Adds a Unit based on the Unit Name in the DATABASE. -- @param #DATABASE self -- @param #string DCSUnitName Unit name. +-- @param #boolean force -- @return Wrapper.Unit#UNIT The added unit. -function DATABASE:AddUnit( DCSUnitName ) - - if not self.UNITS[DCSUnitName] then +function DATABASE:AddUnit( DCSUnitName, force ) + + local DCSunitName = DCSUnitName + + if type(DCSunitName) == "number" then DCSunitName = string.format("%d",DCSUnitName) end + + if not self.UNITS[DCSunitName] or force == true then -- Debug info. - self:T( { "Add UNIT:", DCSUnitName } ) + self:T( { "Add UNIT:", DCSunitName } ) -- Register unit - self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) + self.UNITS[DCSunitName]=UNIT:Register(DCSunitName) end - return self.UNITS[DCSUnitName] + return self.UNITS[DCSunitName] end --- Deletes a Unit from the DATABASE based on the Unit Name. -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) + self:T("DeleteUnit "..tostring(DCSUnitName)) self.UNITS[DCSUnitName] = nil end @@ -199,10 +211,9 @@ function DATABASE:AddStatic( DCSStaticName ) if not self.STATICS[DCSStaticName] then self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) - return self.STATICS[DCSStaticName] end - return nil + return self.STATICS[DCSStaticName] end @@ -212,16 +223,42 @@ function DATABASE:DeleteStatic( DCSStaticName ) self.STATICS[DCSStaticName] = nil end ---- Finds a STATIC based on the StaticName. +--- Finds a STATIC based on the Static Name. -- @param #DATABASE self --- @param #string StaticName +-- @param #string StaticName Name of the static object. -- @return Wrapper.Static#STATIC The found STATIC. function DATABASE:FindStatic( StaticName ) - local StaticFound = self.STATICS[StaticName] return StaticFound end +--- Add a DynamicCargo to the database. +-- @param #DATABASE self +-- @param #string Name Name of the dynamic cargo. +-- @return Wrapper.DynamicCargo#DYNAMICCARGO The dynamic cargo object. +function DATABASE:AddDynamicCargo( Name ) + if not self.DYNAMICCARGO[Name] then + self.DYNAMICCARGO[Name] = DYNAMICCARGO:Register(Name) + end + return self.DYNAMICCARGO[Name] +end + +--- Finds a DYNAMICCARGO based on the Dynamic Cargo Name. +-- @param #DATABASE self +-- @param #string DynamicCargoName +-- @return Wrapper.DynamicCargo#DYNAMICCARGO The found DYNAMICCARGO. +function DATABASE:FindDynamicCargo( DynamicCargoName ) + local StaticFound = self.DYNAMICCARGO[DynamicCargoName] + return StaticFound +end + +--- Deletes a DYNAMICCARGO from the DATABASE based on the Dynamic Cargo Name. +-- @param #DATABASE self +function DATABASE:DeleteDynamicCargo( DynamicCargoName ) + self.DYNAMICCARGO[DynamicCargoName] = nil + return self +end + --- Adds a Airbase based on the Airbase Name in the DATABASE. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase. @@ -813,14 +850,19 @@ end --- Adds a CLIENT based on the ClientName in the DATABASE. -- @param #DATABASE self -- @param #string ClientName Name of the Client unit. +-- @param #boolean Force (optional) Force registration of client. -- @return Wrapper.Client#CLIENT The client object. -function DATABASE:AddClient( ClientName ) - - if not self.CLIENTS[ClientName] then - self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) +function DATABASE:AddClient( ClientName, Force ) + + local DCSUnitName = ClientName + + if type(DCSUnitName) == "number" then DCSUnitName = string.format("%d",ClientName) end + + if not self.CLIENTS[DCSUnitName] or Force == true then + self.CLIENTS[DCSUnitName] = CLIENT:Register( DCSUnitName ) end - return self.CLIENTS[ClientName] + return self.CLIENTS[DCSUnitName] end @@ -831,15 +873,25 @@ end function DATABASE:FindGroup( GroupName ) local GroupFound = self.GROUPS[GroupName] + + if GroupFound == nil and GroupName ~= nil and self.Templates.Groups[GroupName] == nil then + -- see if the group exists in the API, maybe a dynamic slot + self:_RegisterDynamicGroup(GroupName) + return self.GROUPS[GroupName] + end + return GroupFound end --- Adds a GROUP based on the GroupName in the DATABASE. -- @param #DATABASE self -function DATABASE:AddGroup( GroupName ) +-- @param #string GroupName +-- @param #boolean force +-- @return Wrapper.Group#GROUP The Group +function DATABASE:AddGroup( GroupName, force ) - if not self.GROUPS[GroupName] then + if not self.GROUPS[GroupName] or force == true then self:T( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) end @@ -850,9 +902,11 @@ end --- Adds a player based on the Player Name in the DATABASE. -- @param #DATABASE self function DATABASE:AddPlayer( UnitName, PlayerName ) - + + if type(UnitName) == "number" then UnitName = string.format("%d",UnitName) end + if PlayerName then - self:T( { "Add player for unit:", UnitName, PlayerName } ) + self:I( { "Add player for unit:", UnitName, PlayerName } ) self.PLAYERS[PlayerName] = UnitName self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName ) self.PLAYERSJOINED[PlayerName] = PlayerName @@ -860,6 +914,21 @@ function DATABASE:AddPlayer( UnitName, PlayerName ) end +--- Get a PlayerName by UnitName from PLAYERS in DATABASE. +-- @param #DATABASE self +-- @return #string PlayerName +-- @return Wrapper.Unit#UNIT PlayerUnit +function DATABASE:_FindPlayerNameByUnitName(UnitName) + if UnitName then + for playername,unitname in pairs(self.PLAYERS) do + if unitname == UnitName and self.PLAYERUNITS[playername] and self.PLAYERUNITS[playername]:IsAlive() then + return playername, self.PLAYERUNITS[playername] + end + end + end + return nil +end + --- Deletes a player from the DATABASE based on the Player Name. -- @param #DATABASE self function DATABASE:DeletePlayer( UnitName, PlayerName ) @@ -1198,6 +1267,43 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category return self end +--- Get a generic static cargo group template from scratch for dynamic cargo spawns register. Does not register the template! +-- @param #DATABASE self +-- @param #string Name Name of the static. +-- @param #string Typename Typename of the static. Defaults to "container_cargo". +-- @param #number Mass Mass of the static. Defaults to 0. +-- @param #number Coalition Coalition of the static. Defaults to coalition.side.BLUE. +-- @param #number Country Country of the static. Defaults to country.id.GERMANY. +-- @return #table Static template table. +function DATABASE:_GetGenericStaticCargoGroupTemplate(Name,Typename,Mass,Coalition,Country) + local StaticTemplate = {} + StaticTemplate.name = Name or "None" + StaticTemplate.units = { [1] = { + name = Name, + resourcePayload = { + ["weapons"] = {}, + ["aircrafts"] = {}, + ["gasoline"] = 0, + ["diesel"] = 0, + ["methanol_mixture"] = 0, + ["jet_fuel"] = 0, + }, + ["mass"] = Mass or 0, + ["category"] = "Cargos", + ["canCargo"] = true, + ["type"] = Typename or "container_cargo", + ["rate"] = 100, + ["y"] = 0, + ["x"] = 0, + ["heading"] = 0, + }} + StaticTemplate.CategoryID = "static" + StaticTemplate.CoalitionID = Coalition or coalition.side.BLUE + StaticTemplate.CountryID = Country or country.id.GERMANY + --UTILS.PrintTableToLog(StaticTemplate) + return StaticTemplate +end + --- Get static group template. -- @param #DATABASE self -- @param #string StaticName Name of the static. @@ -1274,7 +1380,7 @@ function DATABASE:GetCoalitionFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CoalitionID end - self:E("ERROR: Template does not exist for client "..tostring(ClientName)) + self:E("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1286,7 +1392,7 @@ function DATABASE:GetCategoryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CategoryID end - self:E("ERROR: Template does not exist for client "..tostring(ClientName)) + self:E("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1298,7 +1404,7 @@ function DATABASE:GetCountryFromClientTemplate( ClientName ) if self.Templates.ClientsByName[ClientName] then return self.Templates.ClientsByName[ClientName].CountryID end - self:E("ERROR: Template does not exist for client "..tostring(ClientName)) + self:E("WARNING: Template does not exist for client "..tostring(ClientName)) return nil end @@ -1345,6 +1451,36 @@ function DATABASE:_RegisterPlayers() return self end +--- Private method that registers a single dynamic slot Group and Units within in the mission. +-- @param #DATABASE self +-- @return #DATABASE self +function DATABASE:_RegisterDynamicGroup(Groupname) + local DCSGroup = Group.getByName(Groupname) + if DCSGroup and DCSGroup:isExist() then + + -- Group name. + local DCSGroupName = DCSGroup:getName() + + -- Add group. + self:I(string.format("Register Group: %s", tostring(DCSGroupName))) + self:AddGroup( DCSGroupName, true ) + + -- Loop over units in group. + for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do + + -- Get unit name. + local DCSUnitName = DCSUnit:getName() + + -- Add unit. + self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) + self:AddUnit( tostring(DCSUnitName), true ) + + end + else + self:E({"Group does not exist: ", DCSGroup}) + end + return self +end --- Private method that registers all Groups and Units within in the mission. -- @param #DATABASE self @@ -1443,12 +1579,29 @@ end -- @param DCS#Airbase airbase Airbase. -- @return #DATABASE self function DATABASE:_RegisterAirbase(airbase) - + + local IsSyria = UTILS.GetDCSMap() == "Syria" and true or false + local countHSyria = 0 + if airbase then -- Get the airbase name. local DCSAirbaseName = airbase:getName() - + + -- DCS 2.9.8.1107 added 143 helipads all named H with the same object ID .. + if IsSyria and DCSAirbaseName == "H" and countHSyria > 0 then + --[[ + local p = airbase:getPosition().p + local mgrs = COORDINATE:New(p.x,p.z,p.y):ToStringMGRS() + self:I("Airbase on Syria map named H @ "..mgrs) + countHSyria = countHSyria + 1 + if countHSyria > 1 then return self end + --]] + return self + elseif IsSyria and DCSAirbaseName == "H" and countHSyria == 0 then + countHSyria = countHSyria + 1 + end + -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. local airbaseID=airbase:getID() @@ -1488,7 +1641,7 @@ end -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) - self:F( { Event } ) + self:T( { Event } ) if Event.IniDCSUnit then @@ -1496,7 +1649,17 @@ function DATABASE:_EventOnBirth( Event ) -- Add static object to DB. self:AddStatic( Event.IniDCSUnitName ) + + elseif Event.IniObjectCategory == Object.Category.CARGO and string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then + -- Add dynamic cargo object to DB + + local cargo = self:AddDynamicCargo(Event.IniDCSUnitName) + + self:I(string.format("Adding dynamic cargo %s", tostring(Event.IniDCSUnitName))) + + self:CreateEventNewDynamicCargo( cargo ) + else if Event.IniObjectCategory == Object.Category.UNIT then @@ -1517,9 +1680,9 @@ function DATABASE:_EventOnBirth( Event ) end if Event.IniObjectCategory == Object.Category.UNIT then - - Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) + Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) + Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) -- Client local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT @@ -1535,10 +1698,10 @@ function DATABASE:_EventOnBirth( Event ) -- Debug info. self:I(string.format("Player '%s' joined unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) - + -- Add client in case it does not exist already. - if not client then - client=self:AddClient(Event.IniDCSUnitName) + if client == nil or (client and client:CountPlayers() == 0) then + client=self:AddClient(Event.IniDCSUnitName, true) end -- Add player. @@ -1548,14 +1711,19 @@ function DATABASE:_EventOnBirth( Event ) if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end - - -- Player settings. - local Settings = SETTINGS:Set( PlayerName ) - Settings:SetPlayerMenu(Event.IniUnit) - - -- Create an event. - self:CreateEventPlayerEnterAircraft(Event.IniUnit) - + + local function SetPlayerSettings(self,PlayerName,IniUnit) + -- Player settings. + local Settings = SETTINGS:Set( PlayerName ) + --Settings:SetPlayerMenu(Event.IniUnit) + Settings:SetPlayerMenu(IniUnit) + -- Create an event. + self:CreateEventPlayerEnterAircraft(IniUnit) + --self:CreateEventPlayerEnterAircraft(Event.IniUnit) + end + + self:ScheduleOnce(1,SetPlayerSettings,self,PlayerName,Event.IniUnit) + end end @@ -1569,7 +1737,6 @@ end -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) - if Event.IniDCSUnit then local name=Event.IniDCSUnitName @@ -1577,7 +1744,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniObjectCategory == 3 then --- - -- STATICS + -- STATICS --- if self.STATICS[Event.IniDCSUnitName] then @@ -1587,7 +1754,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) --- -- Maybe a UNIT? --- - + -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName)) @@ -1610,7 +1777,8 @@ function DATABASE:_EventOnDeadOrCrash( Event ) -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then - self:DeleteUnit(Event.IniDCSUnitName) + self:ScheduleOnce(1,self.DeleteUnit,self,Event.IniDCSUnitName) + --self:DeleteUnit(Event.IniDCSUnitName) end -- Remove client players. @@ -1677,6 +1845,15 @@ function DATABASE:_EventOnPlayerEnterUnit( Event ) end end +--- Handles the OnDynamicCargoRemoved event to clean the active dynamic cargo table. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA Event +function DATABASE:_EventOnDynamicCargoRemoved( Event ) + self:T( { Event } ) + if Event.IniDynamicCargoName then + self:DeleteDynamicCargo(Event.IniDynamicCargoName) + end +end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self @@ -1718,6 +1895,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT if client then client:RemovePlayer(PlayerName) + --self.PLAYERSETTINGS[PlayerName] = nil end end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 0f5320ff8..f22185616 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -194,6 +194,11 @@ 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 +-- dynamic cargo +world.event.S_EVENT_NEW_DYNAMIC_CARGO = world.event.S_EVENT_MAX + 1008 +world.event.S_EVENT_DYNAMIC_CARGO_LOADED = world.event.S_EVENT_MAX + 1009 +world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED = world.event.S_EVENT_MAX + 1010 +world.event.S_EVENT_DYNAMIC_CARGO_REMOVED = world.event.S_EVENT_MAX + 1011 --- The different types of events supported by MOOSE. @@ -261,17 +266,29 @@ EVENTS = { SimulationStart = world.event.S_EVENT_SIMULATION_START or -1, WeaponRearm = world.event.S_EVENT_WEAPON_REARM or -1, WeaponDrop = world.event.S_EVENT_WEAPON_DROP or -1, - -- Added with DCS 2.9.0 - UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1, + -- Added with DCS 2.9.x + --UnitTaskTimeout = world.event.S_EVENT_UNIT_TASK_TIMEOUT or -1, + UnitTaskComplete = world.event.S_EVENT_UNIT_TASK_COMPLETE or -1, UnitTaskStage = world.event.S_EVENT_UNIT_TASK_STAGE or -1, - MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1, + --MacSubtaskScore = world.event.S_EVENT_MAC_SUBTASK_SCORE or -1, MacExtraScore = world.event.S_EVENT_MAC_EXTRA_SCORE or -1, MissionRestart = world.event.S_EVENT_MISSION_RESTART or -1, MissionWinner = world.event.S_EVENT_MISSION_WINNER or -1, - PostponedTakeoff = world.event.S_EVENT_POSTPONED_TAKEOFF or -1, - PostponedLand = world.event.S_EVENT_POSTPONED_LAND or -1, + RunwayTakeoff = world.event.S_EVENT_RUNWAY_TAKEOFF or -1, + RunwayTouch = world.event.S_EVENT_RUNWAY_TOUCH or -1, + MacLMSRestart = world.event.S_EVENT_MAC_LMS_RESTART or -1, + SimulationFreeze = world.event.S_EVENT_SIMULATION_FREEZE or -1, + SimulationUnfreeze = world.event.S_EVENT_SIMULATION_UNFREEZE or -1, + HumanAircraftRepairStart = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_START or -1, + HumanAircraftRepairFinish = world.event.S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH or -1, + -- dynamic cargo + NewDynamicCargo = world.event.S_EVENT_NEW_DYNAMIC_CARGO or -1, + DynamicCargoLoaded = world.event.S_EVENT_DYNAMIC_CARGO_LOADED or -1, + DynamicCargoUnloaded = world.event.S_EVENT_DYNAMIC_CARGO_UNLOADED or -1, + DynamicCargoRemoved = world.event.S_EVENT_DYNAMIC_CARGO_REMOVED or -1, } + --- The Event structure -- 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: -- @@ -327,6 +344,9 @@ EVENTS = { -- -- @field Core.Zone#ZONE Zone The zone object. -- @field #string ZoneName The name of the zone. +-- +-- @field Wrapper.DynamicCargo#DYNAMICCARGO IniDynamicCargo The dynamic cargo object. +-- @field #string IniDynamicCargoName The dynamic cargo unit name. @@ -646,24 +666,24 @@ local _EVENTMETA = { Text = "S_EVENT_WEAPON_DROP" }, -- DCS 2.9 - [EVENTS.UnitTaskTimeout] = { - Order = 1, - Side = "I", - Event = "OnEventUnitTaskTimeout", - Text = "S_EVENT_UNIT_TASK_TIMEOUT " - }, + --[EVENTS.UnitTaskTimeout] = { + -- Order = 1, + -- Side = "I", + -- Event = "OnEventUnitTaskTimeout", + -- Text = "S_EVENT_UNIT_TASK_TIMEOUT " + --}, [EVENTS.UnitTaskStage] = { Order = 1, Side = "I", Event = "OnEventUnitTaskStage", Text = "S_EVENT_UNIT_TASK_STAGE " }, - [EVENTS.MacSubtaskScore] = { - Order = 1, - Side = "I", - Event = "OnEventMacSubtaskScore", - Text = "S_EVENT_MAC_SUBTASK_SCORE" - }, + --[EVENTS.MacSubtaskScore] = { + -- Order = 1, + --Side = "I", + --Event = "OnEventMacSubtaskScore", + --Text = "S_EVENT_MAC_SUBTASK_SCORE" + --}, [EVENTS.MacExtraScore] = { Order = 1, Side = "I", @@ -682,20 +702,76 @@ local _EVENTMETA = { Event = "OnEventMissionWinner", Text = "S_EVENT_MISSION_WINNER" }, - [EVENTS.PostponedTakeoff] = { + [EVENTS.RunwayTakeoff] = { Order = 1, Side = "I", - Event = "OnEventPostponedTakeoff", - Text = "S_EVENT_POSTPONED_TAKEOFF" + Event = "OnEventRunwayTakeoff", + Text = "S_EVENT_RUNWAY_TAKEOFF" }, - [EVENTS.PostponedLand] = { + [EVENTS.RunwayTouch] = { Order = 1, Side = "I", - Event = "OnEventPostponedLand", - Text = "S_EVENT_POSTPONED_LAND" + Event = "OnEventRunwayTouch", + Text = "S_EVENT_RUNWAY_TOUCH" + }, + [EVENTS.MacLMSRestart] = { + Order = 1, + Side = "I", + Event = "OnEventMacLMSRestart", + Text = "S_EVENT_MAC_LMS_RESTART" + }, + [EVENTS.SimulationFreeze] = { + Order = 1, + Side = "I", + Event = "OnEventSimulationFreeze", + Text = "S_EVENT_SIMULATION_FREEZE" + }, + [EVENTS.SimulationUnfreeze] = { + Order = 1, + Side = "I", + Event = "OnEventSimulationUnfreeze", + Text = "S_EVENT_SIMULATION_UNFREEZE" + }, + [EVENTS.HumanAircraftRepairStart] = { + Order = 1, + Side = "I", + Event = "OnEventHumanAircraftRepairStart", + Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_START" + }, + [EVENTS.HumanAircraftRepairFinish] = { + Order = 1, + Side = "I", + Event = "OnEventHumanAircraftRepairFinish", + Text = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH" + }, + -- dynamic cargo + [EVENTS.NewDynamicCargo] = { + Order = 1, + Side = "I", + Event = "OnEventNewDynamicCargo", + Text = "S_EVENT_NEW_DYNAMIC_CARGO" + }, + [EVENTS.DynamicCargoLoaded] = { + Order = 1, + Side = "I", + Event = "OnEventDynamicCargoLoaded", + Text = "S_EVENT_DYNAMIC_CARGO_LOADED" + }, + [EVENTS.DynamicCargoUnloaded] = { + Order = 1, + Side = "I", + Event = "OnEventDynamicCargoUnloaded", + Text = "S_EVENT_DYNAMIC_CARGO_UNLOADED" + }, + [EVENTS.DynamicCargoRemoved] = { + Order = 1, + Side = "I", + Event = "OnEventDynamicCargoRemoved", + Text = "S_EVENT_DYNAMIC_CARGO_REMOVED" }, } + --- The Events structure -- @type EVENT.Events -- @field #number IniUnit @@ -1108,7 +1184,63 @@ do -- Event Creation world.onEvent( Event ) end - + + --- Creation of a S_EVENT_NEW_DYNAMIC_CARGO event. + -- @param #EVENT self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function EVENT:CreateEventNewDynamicCargo(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.NewDynamicCargo, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_LOADED event. + -- @param #EVENT self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function EVENT:CreateEventDynamicCargoLoaded(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoLoaded, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_UNLOADED event. + -- @param #EVENT self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function EVENT:CreateEventDynamicCargoUnloaded(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoUnloaded, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + + --- Creation of a S_EVENT_DYNAMIC_CARGO_REMOVED event. + -- @param #EVENT self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DynamicCargo the dynamic cargo object + function EVENT:CreateEventDynamicCargoRemoved(DynamicCargo) + self:F({DynamicCargo}) + local Event = { + id = EVENTS.DynamicCargoRemoved, + time = timer.getTime(), + dynamiccargo = DynamicCargo, + initiator = DynamicCargo:GetDCSObject(), + } + world.onEvent( Event ) + end + end --- Main event function. @@ -1197,6 +1329,7 @@ function EVENT:onEvent( Event ) end Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or "" + Event.IniGroupName=Event.IniDCSGroupName --At least set the group name because group might not exist any more if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) @@ -1223,7 +1356,13 @@ function EVENT:onEvent( Event ) Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName - Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) + if string.match(Event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then + Event.IniDynamicCargo = DYNAMICCARGO:FindByName(Event.IniUnitName) + Event.IniDynamicCargoName = Event.IniUnitName + Event.IniPlayerName = string.match(Event.IniUnitName,"^(.+)|%d%d:%d%d|PKG%d+") + else + Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName ) + end Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.IniDCSUnit:getTypeName() @@ -1233,10 +1372,10 @@ function EVENT:onEvent( Event ) -- Scenery --- Event.IniDCSUnit = Event.initiator - Event.IniDCSUnitName = Event.IniDCSUnit:getName() + Event.IniDCSUnitName = Event.IniDCSUnit.getName and Event.IniDCSUnit:getName() or "Scenery no name "..math.random(1,20000) Event.IniUnitName = Event.IniDCSUnitName Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator ) - Event.IniCategory = Event.IniDCSUnit:getDesc().category + Event.IniCategory = Event.IniDCSUnit.getDesc and Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" elseif Event.IniObjectCategory == Object.Category.BASE then @@ -1335,24 +1474,26 @@ function EVENT:onEvent( Event ) -- SCENERY --- Event.TgtDCSUnit = Event.target - Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() - Event.TgtUnitName = Event.TgtDCSUnitName - Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) - Event.TgtCategory = Event.TgtDCSUnit:getDesc().category - Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + Event.TgtDCSUnitName = Event.TgtDCSUnit.getName and Event.TgtDCSUnit.getName() or nil + if Event.TgtDCSUnitName~=nil then + Event.TgtUnitName = Event.TgtDCSUnitName + Event.TgtUnit = SCENERY:Register( Event.TgtDCSUnitName, Event.target ) + Event.TgtCategory = Event.TgtDCSUnit:getDesc().category + Event.TgtTypeName = Event.TgtDCSUnit:getTypeName() + end end end -- Weapon. - if Event.weapon then + if Event.weapon and type(Event.weapon) == "table" and Event.weapon.isExist and Event.weapon:isExist() then Event.Weapon = Event.weapon Event.WeaponName = Event.weapon:isExist() and Event.weapon:getTypeName() or "Unknown Weapon" Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit! Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName() --Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() - Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition() - Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category - Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() + Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon.getCoalition and Event.Weapon:getCoalition() + Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon.getDesc and Event.Weapon:getDesc().category + Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon.getTypeName and Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end @@ -1387,6 +1528,15 @@ function EVENT:onEvent( Event ) Event.Cargo = Event.cargo Event.CargoName = Event.cargo.Name end + + -- Dynamic cargo Object + if Event.dynamiccargo then + Event.IniDynamicCargo = Event.dynamiccargo + Event.IniDynamicCargoName = Event.IniDynamicCargo.StaticName + if Event.IniDynamicCargo.Owner or Event.IniUnitName then + Event.IniPlayerName = Event.IniDynamicCargo.Owner or string.match(Event.IniUnitName or "None|00:00|PKG00","^(.+)|%d%d:%d%d|PKG%d+") + end + end -- Zone object. if Event.zone then diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index b72ca63d9..af2e971fe 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -79,7 +79,7 @@ do -- FSM - --- @type FSM + -- @type FSM -- @field #string ClassName Name of the class. -- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler. -- @field #table options Options. @@ -948,8 +948,9 @@ do -- FSM end do -- FSM_CONTROLLABLE - - --- @type FSM_CONTROLLABLE + + --- + -- @type FSM_CONTROLLABLE -- @field Wrapper.Controllable#CONTROLLABLE Controllable -- @extends Core.Fsm#FSM @@ -1081,8 +1082,9 @@ do -- FSM_CONTROLLABLE end do -- FSM_PROCESS - - --- @type FSM_PROCESS + + --- + -- @type FSM_PROCESS -- @field Tasking.Task#TASK Task -- @extends Core.Fsm#FSM_CONTROLLABLE diff --git a/Moose Development/Moose/Core/Goal.lua b/Moose Development/Moose/Core/Goal.lua index bc33246f6..cff272dbd 100644 --- a/Moose Development/Moose/Core/Goal.lua +++ b/Moose Development/Moose/Core/Goal.lua @@ -24,7 +24,7 @@ do -- Goal - --- @type GOAL + -- @type GOAL -- @extends Core.Fsm#FSM --- Models processes that have an objective with a defined achievement. Derived classes implement the ways how the achievements can be realized. @@ -71,10 +71,10 @@ do -- Goal ClassName = "GOAL", } - --- @field #table GOAL.Players + -- @field #table GOAL.Players GOAL.Players = {} - --- @field #number GOAL.TotalContributions + -- @field #number GOAL.TotalContributions GOAL.TotalContributions = 0 --- GOAL Constructor. @@ -145,7 +145,7 @@ do -- Goal self.TotalContributions = self.TotalContributions + 1 end - --- @param #GOAL self + -- @param #GOAL self -- @param #number Player contribution. function GOAL:GetPlayerContribution( PlayerName ) return self.Players[PlayerName] or 0 diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 3bc950416..4571eed25 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -108,26 +108,30 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) --- 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 #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. - -- @param #number MarkerID Id of this marker - -- @param #number CoalitionNumber Coalition of the marker creator + -- @param #number MarkerID Id of this marker. + -- @param #number CoalitionNumber Coalition of the marker creator. + -- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained. + -- @param Core.Event#EVENTDATA EventData the event data table. --- 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 #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. - -- @param #number MarkerID Id of this marker - -- @param #number CoalitionNumber Coalition of the marker creator + -- @param #number MarkerID Id of this marker. + -- @param #number CoalitionNumber Coalition of the marker creator. + -- @param #string PlayerName Name of the player creating/changing the mark. nil if it cannot be obtained. + -- @param Core.Event#EVENTDATA EventData the event data table --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted @@ -167,7 +171,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition) + self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) end end elseif Event.id==world.event.S_EVENT_MARK_CHANGE then @@ -177,7 +181,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition,Event.PlayerName,Event) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then diff --git a/Moose Development/Moose/Core/Menu.lua b/Moose Development/Moose/Core/Menu.lua index 44c15b08d..aacb018a6 100644 --- a/Moose Development/Moose/Core/Menu.lua +++ b/Moose Development/Moose/Core/Menu.lua @@ -105,6 +105,7 @@ function MENU_INDEX:PrepareCoalition( CoalitionSide ) self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {} self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {} end + --- -- @param Wrapper.Group#GROUP Group function MENU_INDEX:PrepareGroup( Group ) @@ -118,9 +119,11 @@ end function MENU_INDEX:HasMissionMenu( Path ) return self.MenuMission.Menus[Path] end + function MENU_INDEX:SetMissionMenu( Path, Menu ) self.MenuMission.Menus[Path] = Menu end + function MENU_INDEX:ClearMissionMenu( Path ) self.MenuMission.Menus[Path] = nil end @@ -128,9 +131,11 @@ end function MENU_INDEX:HasCoalitionMenu( Coalition, Path ) return self.Coalition[Coalition].Menus[Path] end + function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu ) self.Coalition[Coalition].Menus[Path] = Menu end + function MENU_INDEX:ClearCoalitionMenu( Coalition, Path ) self.Coalition[Coalition].Menus[Path] = nil end @@ -138,19 +143,24 @@ end function MENU_INDEX:HasGroupMenu( Group, Path ) if Group and Group:IsAlive() then local MenuGroupName = Group:GetName() - return self.Group[MenuGroupName].Menus[Path] + if self.Group[MenuGroupName] and self.Group[MenuGroupName].Menus and self.Group[MenuGroupName].Menus[Path] then + return self.Group[MenuGroupName].Menus[Path] + end end return nil end + function MENU_INDEX:SetGroupMenu( Group, Path, Menu ) local MenuGroupName = Group:GetName() - Group:F({MenuGroupName=MenuGroupName,Path=Path}) + --Group:F({MenuGroupName=MenuGroupName,Path=Path}) self.Group[MenuGroupName].Menus[Path] = Menu end + function MENU_INDEX:ClearGroupMenu( Group, Path ) local MenuGroupName = Group:GetName() self.Group[MenuGroupName].Menus[Path] = nil end + function MENU_INDEX:Refresh( Group ) for MenuID, Menu in pairs( self.MenuMission.Menus ) do Menu:Refresh() diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 61f285881..4165bdc57 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -75,35 +75,37 @@ MESSAGE.Type = { --- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{#MESSAGE.ToClient} or @{#MESSAGE.ToCoalition} or @{#MESSAGE.ToAll} to send these Messages to the respective recipients. -- @param self --- @param #string MessageText is the text of the Message. --- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param #string MessageCategory (optional) is a string expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". +-- @param #string Text is the text of the Message. +-- @param #number Duration Duration in seconds how long the message text is shown. +-- @param #string Category (Optional) String expressing the "category" of the Message. The category will be shown as the first text in the message followed by a ": ". -- @param #boolean ClearScreen (optional) Clear all previous messages if true. --- @return #MESSAGE +-- @return #MESSAGE self -- @usage -- --- -- Create a series of new Messages. --- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". --- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". --- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- Create a series of new Messages. +-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". +-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". +-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score". -- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission" ) -- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty" ) -- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ) -- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -- -function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen ) +function MESSAGE:New( Text, Duration, Category, ClearScreen ) + local self = BASE:Inherit( self, BASE:New() ) - self:F( { MessageText, MessageDuration, MessageCategory } ) + + self:F( { Text, Duration, Category } ) self.MessageType = nil -- When no MessageCategory is given, we don't show it as a title... - if MessageCategory and MessageCategory ~= "" then - if MessageCategory:sub( -1 ) ~= "\n" then - self.MessageCategory = MessageCategory .. ": " + if Category and Category ~= "" then + if Category:sub( -1 ) ~= "\n" then + self.MessageCategory = Category .. ": " else - self.MessageCategory = MessageCategory:sub( 1, -2 ) .. ":\n" + self.MessageCategory = Category:sub( 1, -2 ) .. ":\n" end else self.MessageCategory = "" @@ -114,9 +116,9 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen self.ClearScreen = ClearScreen end - self.MessageDuration = MessageDuration or 5 + self.MessageDuration = Duration or 5 self.MessageTime = timer.getTime() - self.MessageText = MessageText:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) + self.MessageText = Text:gsub( "^\n", "", 1 ):gsub( "\n$", "", 1 ) self.MessageSent = false self.MessageGroup = false @@ -177,22 +179,22 @@ end -- -- -- Send the 2 messages created with the @{New} method to the Client Group. -- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1. --- Client = CLIENT:FindByName("UnitNameOfMyClient") +-- Client = CLIENT:FindByName("NameOfClientUnit") -- --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( Client ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( Client ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score" ):ToClient( Client ) +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score" ):ToClient( Client ) -- or --- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25 ):ToClient( Client ) --- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25 ):ToClient( Client ) +-- MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score"):ToClient( Client ) +-- MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score"):ToClient( Client ) -- or --- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25 ) --- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25 ) +-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", 25, "Score") +-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", 25, "Score") -- MessageClient1:ToClient( Client ) -- MessageClient2:ToClient( Client ) -- function MESSAGE:ToClient( Client, Settings ) self:F( Client ) - self:ToUnit(Client, Settings) + self:ToUnit(Client,Settings) return self end @@ -239,6 +241,7 @@ function MESSAGE:ToUnit( Unit, Settings ) if self.MessageDuration ~= 0 then self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + local ID = Unit:GetID() trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen ) end end @@ -287,11 +290,11 @@ end -- @usage -- -- -- Send a message created with the @{New} method to the BLUE coalition. --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25):ToBlue() +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToBlue() -- or --- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToBlue() +-- MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToBlue() -- or --- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ) +-- MessageBLUE = MESSAGE:New( "To the BLUE Players: You receive a penalty because you've killed one of your own units", 25, "Penalty") -- MessageBLUE:ToBlue() -- function MESSAGE:ToBlue() @@ -308,11 +311,11 @@ end -- @usage -- -- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToRed() +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToRed() -- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToRed() +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToRed() -- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty") -- MessageRED:ToRed() -- function MESSAGE:ToRed() @@ -331,11 +334,11 @@ end -- @usage -- -- -- Send a message created with the @{New} method to the RED coalition. --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToCoalition( coalition.side.RED ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToCoalition( coalition.side.RED ) -- or --- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ):ToCoalition( coalition.side.RED ) +-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty"):ToCoalition( coalition.side.RED ) -- or --- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25 ) +-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", 25, "Penalty") -- MessageRED:ToCoalition( coalition.side.RED ) -- function MESSAGE:ToCoalition( CoalitionSide, Settings ) @@ -381,13 +384,13 @@ end -- @return #MESSAGE self -- @usage -- --- -- Send a message created to all players. --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ):ToAll() --- or --- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ):ToAll() --- or --- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ) --- MessageAll:ToAll() +-- -- Send a message created to all players. +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission"):ToAll() +-- or +-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission"):ToAll() +-- or +-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", 25, "End of Mission") +-- MessageAll:ToAll() -- function MESSAGE:ToAll( Settings, Delay ) self:F() @@ -461,6 +464,7 @@ _MESSAGESRS = {} -- @param #number Volume (optional) Volume, can be between 0.0 and 1.0 (loudest). -- @param #string Label (optional) Label, defaults to "MESSAGE" or the Message Category set. -- @param Core.Point#COORDINATE Coordinate (optional) Coordinate this messages originates from. +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @usage -- -- Mind the dot here, not using the colon this time around! -- -- Needed once only @@ -468,7 +472,7 @@ _MESSAGESRS = {} -- -- later on in your code -- MESSAGE:New("Test message!",15,"SPAWN"):ToSRS() -- -function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate) +function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,Gender,Culture,Voice,Coalition,Volume,Label,Coordinate,Backend) _MESSAGESRS.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" @@ -486,6 +490,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G _MESSAGESRS.MSRS:SetCoordinate(Coordinate) end + if Backend then + _MESSAGESRS.MSRS:SetBackend(Backend) + end + _MESSAGESRS.Culture = Culture or MSRS.culture or "en-GB" _MESSAGESRS.MSRS:SetCulture(Culture) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a18dcce96..fde523c2b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -110,7 +110,7 @@ do -- COORDINATE -- -- ## 4.4) Get the North correction of the current location. -- - -- * @{#COORDINATE.GetNorthCorrection}(): Obtains the north correction at the current 3D point. + -- * @{#COORDINATE.GetNorthCorrectionRadians}(): Obtains the north correction at the current 3D point. -- -- ## 4.5) Point Randomization -- @@ -985,8 +985,13 @@ do -- COORDINATE -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance(TargetCoordinate) if not TargetCoordinate then return 1000000 end - local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} - local norm=UTILS.VecNorm(a) + --local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} + local a = self:GetVec2() + if not TargetCoordinate.ClassName then + TargetCoordinate=COORDINATE:NewFromVec3(TargetCoordinate) + end + local b = TargetCoordinate:GetVec2() + local norm=UTILS.VecDist2D(a,b) return norm end @@ -1169,6 +1174,162 @@ do -- COORDINATE return vec3 end + --- Return the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetX() + return self.x + end + + --- Return the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetY() + if self:IsInstanceOf("POINT_VEC2") then + return self.z + end + return self.y + end + + --- Return the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @return #number The z coordinate. + function COORDINATE:GetZ() + return self.z + end + + --- Set the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetX( x ) + self.x = x + return self + end + + --- Set the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetY( y ) + if self:IsInstanceOf("POINT_VEC2") then + self.z = y + else + self.y = y + end + return self + end + + --- Set the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate. + -- @return #COORDINATE + function COORDINATE:SetZ( z ) + self.z = z + return self + end + + --- Add to the x coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number x The x coordinate value to add to the current x coordinate. + -- @return #COORDINATE + function COORDINATE:AddX( x ) + self.x = self.x + x + return self + end + + + --- Return Return the Lat(itude) coordinate of the COORDINATE (ie: (parent)COORDINATE.x). + -- @param #COORDINATE self + -- @return #number The x coordinate. + function COORDINATE:GetLat() + return self.x + end + + --- Set the Lat(itude) coordinate of the COORDINATE (ie: COORDINATE.x). + -- @param #COORDINATE self + -- @param #number x The x coordinate. + -- @return #COORDINATE + function COORDINATE:SetLat( x ) + self.x = x + return self + end + + --- Return the Lon(gitude) coordinate of the COORDINATE (ie: (parent)COORDINATE.z). + -- @param #COORDINATE self + -- @return #number The y coordinate. + function COORDINATE:GetLon() + return self.z + end + + --- Set the Lon(gitude) coordinate of the COORDINATE (ie: COORDINATE.z). + -- @param #COORDINATE self + -- @param #number y The y coordinate. + -- @return #COORDINATE + function COORDINATE:SetLon( z ) + self.z = z + return self + end + + --- Return the altitude (height) of the land at the COORDINATE. + -- @param #COORDINATE self + -- @return #number The land altitude. + function COORDINATE:GetAlt() + return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) + end + + --- Set the altitude of the COORDINATE. + -- @param #COORDINATE self + -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:SetAlt( Altitude ) + self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) + return self + end + + --- Add to the current land height an altitude. + -- @param #COORDINATE self + -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. + -- @return #COORDINATE + function COORDINATE:AddAlt( Altitude ) + self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 + return self + end + + + --- 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 + function COORDINATE:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) + self:F2( { OuterRadius, InnerRadius } ) + + return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) + end + + --- Add to the y coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number y The y coordinate value to add to the current y coordinate. + -- @return #COORDINATE + function COORDINATE:AddY( y ) + if self:IsInstanceOf("POINT_VEC2") then + return self:AddZ(y) + else + self.y = self.y + y + end + return self + end + + --- Add to the z coordinate of the COORDINATE. + -- @param #COORDINATE self + -- @param #number z The z coordinate value to add to the current z coordinate. + -- @return #COORDINATE + function COORDINATE:AddZ( z ) + self.z = self.z +z + return self + end + --- Returns a text documenting the wind direction (from) and strength according the measurement system @{Core.Settings}. -- The text will reflect the wind like this: @@ -1233,7 +1394,7 @@ do -- COORDINATE local s = string.format( '%03d°', AngleDegrees ) if MagVar then - local variation = UTILS.GetMagneticDeclination() or 0 + local variation = self:GetMagneticDeclination() or 0 local AngleMagnetic = AngleDegrees - variation if AngleMagnetic < 0 then AngleMagnetic = 360-AngleMagnetic end @@ -1346,13 +1507,16 @@ do -- COORDINATE -- @param Core.Settings#SETTINGS Settings -- @param #string Language (Optional) Language "en" or "ru" -- @param #boolean MagVar If true, also state angle in magnetic + -- @param #number Precision Rounding precision, defaults to 0 -- @return #string The BR Text - function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar ) + function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language, MagVar, Precision ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS - + + Precision = Precision or 0 + local BearingText = self:GetBearingText( AngleRadians, 0, Settings, MagVar ) - local DistanceText = self:GetDistanceText( Distance, Settings, Language, 0 ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language, Precision ) local BRText = BearingText .. DistanceText @@ -1974,9 +2138,18 @@ do -- COORDINATE --- Smokes the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor - function COORDINATE:Smoke( SmokeColor ) + -- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + function COORDINATE:Smoke( SmokeColor, name ) self:F2( { SmokeColor } ) - trigger.action.smoke( self:GetVec3(), SmokeColor ) + self.firename = name or "Smoke-"..math.random(1,100000) + trigger.action.smoke( self:GetVec3(), SmokeColor, self.firename ) + end + + --- Stops smoking the point in a color. + -- @param #COORDINATE self + -- @param #string name (Optional) Name if you want to stop the smoke early (normal duration: 5mins) + function COORDINATE:StopSmoke( name ) + self:StopBigSmokeAndFire( name ) end --- Smoke the COORDINATE Green. @@ -2427,7 +2600,7 @@ do -- COORDINATE 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 @@ -2686,9 +2859,9 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun rise at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tonumber(sunrise) or "0", Tdiff)) - if InSeconds then + if InSeconds or type(sunrise) == "string" then return sunrise else return UTILS.SecondsToClock(sunrise, true) @@ -2854,9 +3027,9 @@ do -- COORDINATE local date=UTILS.GetDCSMissionDate() -- Debug output. - --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%d sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), sunrise, Tdiff)) + --self:I(string.format("Sun set at lat=%.3f long=%.3f on %s (DayOfYear=%d): %s (%s sec of the day) (GMT %d)", Latitude, Longitude, date, DayOfYear, tostring(UTILS.SecondsToClock(sunrise)), tostring(sunrise) or "0", Tdiff)) - if InSeconds then + if InSeconds or type(sunrise) == "string" then return sunrise else return UTILS.SecondsToClock(sunrise, true) @@ -2917,12 +3090,13 @@ do -- COORDINATE -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @param #boolean MagVar If true, also get angle in MagVar for BR/BRA + -- @param #number Precision Rounding precision, currently full km as default (=0) -- @return #string The BR text. - function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar ) + function COORDINATE:ToStringBR( FromCoordinate, Settings, MagVar, Precision ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = self:Get2DDistance( FromCoordinate ) - return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar ) + return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings, nil, MagVar, Precision ) end --- Return a BRA string from a COORDINATE to the COORDINATE. @@ -2958,6 +3132,8 @@ do -- COORDINATE local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local bearing = UTILS.Round( UTILS.ToDegree( AngleRadians ),0 ) + local magnetic = self:GetMagneticDeclination() or 0 + bearing = bearing - magnetic local rangeMetres = self:Get2DDistance(currentCoord) local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0) @@ -3330,16 +3506,16 @@ do -- COORDINATE -- @param #COORDINATE self -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. - -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. - function COORDINATE:ToString( Controllable, Settings, Task ) + function COORDINATE:ToString( Controllable, Settings ) -- self:E( { Controllable = Controllable and Controllable:GetName() } ) local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS local ModeA2A = nil - + + --[[ if Task then if Task:IsInstanceOf( TASK_A2A ) then ModeA2A = true @@ -3356,7 +3532,7 @@ do -- COORDINATE end end end - + --]] if ModeA2A == nil then local IsAir = Controllable and ( Controllable:IsAirPlane() or Controllable:IsHelicopter() ) or false @@ -3431,7 +3607,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number Radius (Optional) Radius to check around the coordinate, defaults to 50m (100m diameter) -- @param #number Minelevation (Optional) Elevation from which on a area is defined as steep, defaults to 8% (8m height gain across 100 meters) - -- @return #boolen IsSteep If true, area is steep + -- @return #boolean IsSteep If true, area is steep -- @return #number MaxElevation Elevation in meters measured over 100m function COORDINATE:IsInSteepArea(Radius,Minelevation) local steep = false @@ -3463,7 +3639,7 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #number Radius (Optional) Radius to check around the coordinate, defaults to 50m (100m diameter) -- @param #number Minelevation (Optional) Elevation from which on a area is defined as steep, defaults to 8% (8m height gain across 100 meters) - -- @return #boolen IsFlat If true, area is flat + -- @return #boolean IsFlat If true, area is flat -- @return #number MaxElevation Elevation in meters measured over 100m function COORDINATE:IsInFlatArea(Radius,Minelevation) local steep, elev = self:IsInSteepArea(Radius,Minelevation) @@ -3471,9 +3647,18 @@ do -- COORDINATE return flat, elev end + --- 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 + function COORDINATE:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) + return COORDINATE:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) + end + end -do -- POINT_VEC3 +do --- The POINT_VEC3 class -- @type POINT_VEC3 @@ -3490,6 +3675,8 @@ do -- POINT_VEC3 --- Defines a 3D point in the simulator and with its methods, you can use or manipulate the point in 3D space. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- **Important Note:** Most of the functions in this section were taken from MIST, and reworked to OO concepts. -- In order to keep the credibility of the the author, -- I want to emphasize that the formulas embedded in the MIST framework were created by Grimes or previous authors, @@ -3577,130 +3764,19 @@ do -- POINT_VEC3 return self end - --- Create a new POINT_VEC3 object from Vec2 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @param DCS#Distance LandHeightAdd (optional) Add a landheight. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec2( Vec2, LandHeightAdd ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - --- Create a new POINT_VEC3 object from Vec3 coordinates. - -- @param #POINT_VEC3 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC3 self - function POINT_VEC3:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- Core.Point#POINT_VEC3 - self:F2( self ) - - return self - end - - - - --- Return the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The x coordinate. - function POINT_VEC3:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The y coordinate. - function POINT_VEC3:GetY() - return self.y - end - - --- Return the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @return #number The z coordinate. - function POINT_VEC3:GetZ() - return self.z - end - - --- Set the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetY( y ) - self.y = y - return self - end - - --- Set the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:SetZ( z ) - self.z = z - return self - end - - --- Add to the x coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number x The x coordinate value to add to the current x coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number y The y coordinate value to add to the current y coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddY( y ) - self.y = self.y + y - return self - end - - --- Add to the z coordinate of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param #number z The z coordinate value to add to the current z coordinate. - -- @return #POINT_VEC3 - function POINT_VEC3:AddZ( z ) - self.z = self.z +z - return self - end - - --- Return a random POINT_VEC3 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC3. - -- @param #POINT_VEC3 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC3 - function POINT_VEC3:GetRandomPointVec3InRadius( OuterRadius, InnerRadius ) - - return POINT_VEC3:NewFromVec3( self:GetRandomVec3InRadius( OuterRadius, InnerRadius ) ) - end - end -do -- POINT_VEC2 +do - -- @type POINT_VEC2 + --- @type POINT_VEC2 -- @field DCS#Distance x The x coordinate in meters. -- @field DCS#Distance y the y coordinate in meters. -- @extends Core.Point#COORDINATE --- Defines a 2D point in the simulator. The height coordinate (if needed) will be the land height + an optional added height specified. -- + -- **DEPRECATED - PLEASE USE COORDINATE!** + -- -- ## POINT_VEC2 constructor -- -- A new POINT_VEC2 instance can be created with: @@ -3748,166 +3824,4 @@ do -- POINT_VEC2 return self end - --- Create a new POINT_VEC2 object from Vec2 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec2 Vec2 The Vec2 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec2( Vec2, LandHeightAdd ) - - local LandHeight = land.getHeight( Vec2 ) - - LandHeightAdd = LandHeightAdd or 0 - LandHeight = LandHeight + LandHeightAdd - - local self = BASE:Inherit( self, COORDINATE:NewFromVec2( Vec2, LandHeightAdd ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Create a new POINT_VEC2 object from Vec3 coordinates. - -- @param #POINT_VEC2 self - -- @param DCS#Vec3 Vec3 The Vec3 point. - -- @return Core.Point#POINT_VEC2 self - function POINT_VEC2:NewFromVec3( Vec3 ) - - local self = BASE:Inherit( self, COORDINATE:NewFromVec3( Vec3 ) ) -- #POINT_VEC2 - self:F2( self ) - - return self - end - - --- Return the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetX() - return self.x - end - - --- Return the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetY() - return self.z - end - - --- Set the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetX( x ) - self.x = x - return self - end - - --- Set the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetY( y ) - self.z = y - return self - end - - --- Return Return the Lat(itude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @return #number The x coordinate. - function POINT_VEC2:GetLat() - return self.x - end - - --- Set the Lat(itude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.x). - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLat( x ) - self.x = x - return self - end - - --- Return the Lon(gitude) coordinate of the POINT_VEC2 (ie: (parent)POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @return #number The y coordinate. - function POINT_VEC2:GetLon() - return self.z - end - - --- Set the Lon(gitude) coordinate of the POINT_VEC2 (ie: POINT_VEC3.z). - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:SetLon( z ) - self.z = z - return self - end - - --- Return the altitude (height) of the land at the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @return #number The land altitude. - function POINT_VEC2:GetAlt() - return self.y ~= 0 or land.getHeight( { x = self.x, y = self.z } ) - end - - --- Set the altitude of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number Altitude The land altitude. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:SetAlt( Altitude ) - self.y = Altitude or land.getHeight( { x = self.x, y = self.z } ) - return self - end - - --- Add to the x coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number x The x coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddX( x ) - self.x = self.x + x - return self - end - - --- Add to the y coordinate of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param #number y The y coordinate. - -- @return #POINT_VEC2 - function POINT_VEC2:AddY( y ) - self.z = self.z + y - return self - end - - --- Add to the current land height an altitude. - -- @param #POINT_VEC2 self - -- @param #number Altitude The Altitude to add. If nothing (nil) is given, then the current land altitude is set. - -- @return #POINT_VEC2 - function POINT_VEC2:AddAlt( Altitude ) - self.y = land.getHeight( { x = self.x, y = self.z } ) + Altitude or 0 - return self - end - - - --- Return a random POINT_VEC2 within an Outer Radius and optionally NOT within an Inner Radius of the POINT_VEC2. - -- @param #POINT_VEC2 self - -- @param DCS#Distance OuterRadius - -- @param DCS#Distance InnerRadius - -- @return #POINT_VEC2 - function POINT_VEC2:GetRandomPointVec2InRadius( OuterRadius, InnerRadius ) - self:F2( { OuterRadius, InnerRadius } ) - - return POINT_VEC2:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) ) - end - - -- TODO: Check this to replace - --- Calculate the distance from a reference @{#POINT_VEC2}. - -- @param #POINT_VEC2 self - -- @param #POINT_VEC2 PointVec2Reference The reference @{#POINT_VEC2}. - -- @return DCS#Distance The distance from the reference @{#POINT_VEC2} in meters. - function POINT_VEC2:DistanceFromPointVec2( PointVec2Reference ) - self:F2( PointVec2Reference ) - - local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 - - self:T2( Distance ) - return Distance - end - end diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index d8225adcc..8d55f05d3 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -15,7 +15,8 @@ -- @module Core.Report -- @image Core_Report.JPG ---- @type REPORT +--- +-- @type REPORT -- @extends Core.Base#BASE --- Provides a handy means to create messages and reports. diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 611cb6fba..5eb58e731 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -1,4 +1,4 @@ ---- **Core** - SCHEDULEDISPATCHER dispatches the different schedules. +---- **Core** - SCHEDULEDISPATCHER dispatches the different schedules. -- -- === -- diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index ca82b1ada..c66b3cf57 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -27,6 +27,7 @@ -- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria. -- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria. -- * @{#SET_SCENERY}: Defines a collection of @{Wrapper.Scenery}s added via a filtered @{#SET_ZONE}. +-- * @{#SET_DYNAMICCARGO}: Defines a collection of @{Wrapper.DynamicCargo}s filtered by filter criteria. -- -- These classes are derived from @{#SET_BASE}, which contains the main methods to manage the collections. -- @@ -88,8 +89,7 @@ do -- SET_BASE Index = {}, Database = nil, CallScheduler = nil, - TimeInterval = nil, - YieldInterval = nil, + } --- Filters @@ -181,7 +181,7 @@ do -- SET_BASE return false end end - -- No condition was true. + -- No condition was false. return true end @@ -212,7 +212,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSet() - self:F2() + --self:F2() return self.Set or {} end @@ -221,7 +221,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #table Table of names. function SET_BASE:GetSetNames() -- R2.3 - self:F2() + --self:F2() local Names = {} @@ -236,7 +236,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #table Table of objects. function SET_BASE:GetSetObjects() -- R2.3 - self:F2() + --self:F2() local Objects = {} @@ -252,7 +252,7 @@ do -- SET_BASE -- @param #string ObjectName -- @param #boolean NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) - self:F2( { ObjectName = ObjectName } ) + --self:F2( { ObjectName = ObjectName } ) local TriggerEvent = true if NoTriggerEvent then @@ -288,8 +288,15 @@ do -- SET_BASE function SET_BASE:Add( ObjectName, Object ) -- Debug info. - self:T2( { ObjectName = ObjectName, Object = Object } ) - + --self:T2( { ObjectName = ObjectName, Object = Object } ) + + -- Error ahndling + if not ObjectName or ObjectName == "" then + self:E("SET_BASE:Add - Invalid ObjectName handed") + self:E({ObjectName=ObjectName, Object=Object}) + return self + end + -- 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 ) @@ -312,10 +319,10 @@ do -- SET_BASE -- @param Wrapper.Object#OBJECT Object -- @return Core.Base#BASE The added BASE Object. function SET_BASE:AddObject( Object ) - self:F2( Object.ObjectName ) + --self:F2( Object.ObjectName ) - self:T( Object.UnitName ) - self:T( Object.ObjectName ) + --self:T( Object.UnitName ) + --self:T( Object.ObjectName ) self:Add( Object.ObjectName, Object ) end @@ -424,11 +431,11 @@ do -- SET_BASE -- @param #string ObjectName -- @return Core.Base#BASE function SET_BASE:Get( ObjectName ) - self:F( ObjectName ) + --self:F( ObjectName ) local Object = self.Set[ObjectName] - self:T3( { ObjectName, Object } ) + --self:T3( { ObjectName, Object } ) return Object end @@ -438,7 +445,7 @@ do -- SET_BASE function SET_BASE:GetFirst() local ObjectName = self.Index[1] local FirstObject = self.Set[ObjectName] - self:T3( { FirstObject } ) + --self:T3( { FirstObject } ) return FirstObject end @@ -449,7 +456,7 @@ do -- SET_BASE local tablemax = table.maxn(self.Index) local ObjectName = self.Index[tablemax] local LastObject = self.Set[ObjectName] - self:T3( { LastObject } ) + --self:T3( { LastObject } ) return LastObject end @@ -463,7 +470,7 @@ do -- SET_BASE end --local tablemax = table.maxn(self.Index) local RandomItem = self.Set[self.Index[math.random(1,tablemax)]] - self:T3( { RandomItem } ) + --self:T3( { RandomItem } ) return RandomItem end @@ -480,7 +487,7 @@ do -- SET_BASE --local tablemax = table.maxn(self.Index) --local RandomItem = self.Set[self.Index[math.random(1,tablemax)]] local RandomItem = sorted[math.random(1,tablemax)] - self:T3( { RandomItem } ) + --self:T3( { RandomItem } ) return RandomItem end @@ -506,19 +513,6 @@ do -- SET_BASE return self end - --- Define the SET iterator **"yield interval"** and the **"time interval"**. - -- @param #SET_BASE self - -- @param #number YieldInterval Sets the frequency when the iterator loop will yield after the number of objects processed. The default frequency is 10 objects processed. - -- @param #number TimeInterval Sets the time in seconds when the main logic will resume the iterator loop. The default time is 0.001 seconds. - -- @return #SET_BASE self - function SET_BASE:SetIteratorIntervals( YieldInterval, TimeInterval ) - - self.YieldInterval = YieldInterval - self.TimeInterval = TimeInterval - - return self - end - --- Define the SET iterator **"limit"**. -- @param #SET_BASE self -- @param #number Limit Defines how many objects are evaluated of the set as part of the Some iterators. The default is 1. @@ -537,6 +531,21 @@ do -- SET_BASE return self.SomeIteratorLimit or self:Count() end + + --- Get max threat level of all objects in the SET. + -- @param #SET_BASE self + -- @return #number Max threat level found. + function SET_BASE:GetThreatLevelMax() + local ThreatMax = 0 + for _,_unit in pairs(self.Set or {}) do + local unit = _unit -- Wrapper.Unit#UNIT + local threat = unit.GetThreatLevel and unit:GetThreatLevel() or 0 + if threat > ThreatMax then + ThreatMax = threat + end + end + return ThreatMax + end --- Filters for the defined collection. -- @param #SET_BASE self @@ -620,14 +629,14 @@ do -- SET_BASE return self end - --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_BASE while identifying the nearest object in the set from a @{Core.Point#COORDINATE}. -- @param #SET_BASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#COORDINATE} or @{Core.Point#POINT_VEC2} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object (but **not** a simple DCS#Vec2!) from where to evaluate the closest object in the set. -- @return Core.Base#BASE The closest object. -- @usage -- myset:FindNearestObjectFromPointVec2( ZONE:New("Test Zone"):GetCoordinate() ) - function SET_BASE:FindNearestObjectFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) + function SET_BASE:FindNearestObjectFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestObject = nil local ClosestDistance = nil @@ -635,9 +644,9 @@ do -- SET_BASE for ObjectID, ObjectData in pairs( self.Set ) do if NearestObject == nil then NearestObject = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestObject = ObjectData ClosestDistance = Distance @@ -648,39 +657,17 @@ do -- SET_BASE return NearestObject end - ----- Private method that registers all alive players in the mission. - -- @param #SET_BASE self - -- @return #SET_BASE self - -- function SET_BASE:_RegisterPlayers() - -- - -- local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - -- for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - -- for UnitId, UnitData in pairs( CoalitionData ) do - -- self:T3( { "UnitData:", UnitData } ) - -- if UnitData and UnitData:isExist() then - -- local UnitName = UnitData:getName() - -- if not self.PlayersAlive[UnitName] then - -- self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) - -- self.PlayersAlive[UnitName] = UnitData:getPlayerName() - -- end - -- end - -- end - -- end - -- - -- return self - -- end - --- Events --- Handles the OnBirth event for the Set. -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnBirth( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:AddInDatabase( Event ) - self:T3( ObjectName, Object ) + --self:T3( ObjectName, Object ) if Object and self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) -- self:_EventOnPlayerEnterUnit( Event ) @@ -692,7 +679,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event function SET_BASE:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) + --self:F( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) @@ -706,7 +693,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event -- function SET_BASE:_EventOnPlayerEnterUnit( Event ) - -- self:F3( { Event } ) + -- --self:F3( { Event } ) -- -- if Event.IniDCSUnit then -- local ObjectName, Object = self:AddInDatabase( Event ) @@ -722,7 +709,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Event#EVENTDATA Event -- function SET_BASE:_EventOnPlayerLeaveUnit( Event ) - -- self:F3( { Event } ) + -- --self:F3( { Event } ) -- -- local ObjectName = Event.IniDCSUnit -- if Event.IniDCSUnit then @@ -755,7 +742,7 @@ do -- SET_BASE -- @param #table FunctionArguments (Optional) Function arguments. -- @return #SET_BASE self function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) - self:F3( arg ) + --self:F3( arg ) Set = Set or self:GetSet() arg = arg or {} @@ -764,7 +751,7 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) + --self:T3( Object ) if Function then if Function( unpack( FunctionArguments or {} ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) @@ -787,7 +774,7 @@ do -- SET_BASE -- local status, res = coroutine.resume( co ) local status, res = co() - self:T3( { status, res } ) + --self:T3( { status, res } ) if status == false then error( res ) @@ -810,7 +797,7 @@ do -- SET_BASE -- @param #function IteratorFunction The function that will be called. -- @return #SET_BASE self function SET_BASE:ForSome( IteratorFunction, arg, Set, Function, FunctionArguments ) - self:F3( arg ) + --self:F3( arg ) Set = Set or self:GetSet() arg = arg or {} @@ -821,7 +808,7 @@ do -- SET_BASE local Count = 0 for ObjectID, ObjectData in pairs( Set ) do local Object = ObjectData - self:T3( Object ) + --self:T3( Object ) if Function then if Function( unpack( FunctionArguments ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) @@ -847,7 +834,7 @@ do -- SET_BASE -- local status, res = coroutine.resume( co ) local status, res = co() - self:T3( { status, res } ) + --self:T3( { status, res } ) if status == false then error( res ) @@ -871,7 +858,7 @@ do -- SET_BASE -- @param #function IteratorFunction The function that will be called when there is an alive unit in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self -- function SET_BASE:ForEachDCSUnitAlive( IteratorFunction, ... ) - -- self:F3( arg ) + -- --self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) -- @@ -883,7 +870,7 @@ do -- SET_BASE -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a UNIT parameter. ---- @return #SET_BASE self -- function SET_BASE:ForEachPlayer( IteratorFunction, ... ) - -- self:F3( arg ) + -- --self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- @@ -896,7 +883,7 @@ do -- SET_BASE -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_BASE. The function needs to accept a CLIENT parameter. ---- @return #SET_BASE self -- function SET_BASE:ForEachClient( IteratorFunction, ... ) - -- self:F3( arg ) + -- --self:F3( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- @@ -908,7 +895,7 @@ do -- SET_BASE -- @param #table Object -- @return #SET_BASE self function SET_BASE:IsIncludeObject( Object ) - self:F3( Object ) + --self:F3( Object ) return true end @@ -918,7 +905,7 @@ do -- SET_BASE -- @param #table Object -- @return #boolean `true` if object is in set and `false` otherwise. function SET_BASE:IsInSet( Object ) - self:F3( Object ) + --self:F3( Object ) local outcome = false local name = Object:GetName() --self:I("SET_BASE: Objectname = "..name) @@ -938,7 +925,7 @@ do -- SET_BASE -- @param #table Object -- @return #SET_BASE self function SET_BASE:IsNotInSet( Object ) - self:F3( Object ) + --self:F3( Object ) return not self:IsInSet(Object) end @@ -946,7 +933,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #string A string with the names of the objects. function SET_BASE:GetObjectNames() - self:F3() + --self:F3() local ObjectNames = "" for ObjectName, Object in pairs( self.Set ) do @@ -961,13 +948,13 @@ do -- SET_BASE -- @param Core.Base#BASE MasterObject (Optional) The master object as a reference. -- @return #string A string with the names of the objects. function SET_BASE:Flush( MasterObject ) - self:F3() + --self:F3() local ObjectNames = "" for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - self:F( { MasterObject = MasterObject and MasterObject:GetClassNameAndID(), "Objects in Set:", ObjectNames } ) + --self:F( { MasterObject = MasterObject and MasterObject:GetClassNameAndID(), "Objects in Set:", ObjectNames } ) return ObjectNames end @@ -1059,7 +1046,7 @@ do -- -- 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() } ) + -- --self:F( { GroupObject = GroupObject:GetName() } ) -- end -- -- While this is a good example, there is a catch. @@ -1142,7 +1129,7 @@ do -- @param #SET_GROUP self -- @return #SET_GROUP Set of alive groups. function SET_GROUP:GetAliveSet() - self:F2() + --self:F2() local AliveSet = SET_GROUP:New() @@ -1163,7 +1150,7 @@ do -- @param #SET_GROUP self -- @return Core.Report#REPORT A report of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. function SET_GROUP:GetUnitTypeNames() - self:F2() + --self:F2() local MT = {} -- Message Text local UnitTypes = {} @@ -1255,12 +1242,12 @@ do return GroupFound end - --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_GROUP while identifying the nearest object from a @{Core.Point#COORDINATE}. -- @param #SET_GROUP self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest object in the set. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest object in the set. -- @return Wrapper.Group#GROUP The closest group. - function SET_GROUP:FindNearestGroupFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) + function SET_GROUP:FindNearestGroupFromPointVec2( Coordinate ) + --self:F2( Coordinate ) local NearestGroup = nil -- Wrapper.Group#GROUP local ClosestDistance = nil @@ -1270,9 +1257,9 @@ do for ObjectID, ObjectData in pairs( Set ) do if NearestGroup == nil then NearestGroup = ObjectData - ClosestDistance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + ClosestDistance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) else - local Distance = PointVec2:DistanceFromPointVec2( ObjectData:GetCoordinate() ) + local Distance = Coordinate:DistanceFromPointVec2( ObjectData:GetCoordinate() ) if Distance < ClosestDistance then NearestGroup = ObjectData ClosestDistance = Distance @@ -1516,6 +1503,7 @@ do self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash ) if self.Filter.Zones then self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) @@ -1548,6 +1536,7 @@ do self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.RemoveUnit) + self:UnHandleEvent(EVENTS.UnitLost) if self.Filter.Zones and self.ZoneTimer and self.ZoneTimer:IsRunning() then self.ZoneTimer:Stop() @@ -1563,7 +1552,7 @@ do -- @param #SET_GROUP self -- @param Core.Event#EVENTDATA Event function SET_GROUP:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) + --self:F( { Event } ) if Event.IniDCSUnit then local ObjectName, Object = self:FindInDatabase( Event ) @@ -1571,6 +1560,13 @@ do local size = 1 if Event.IniDCSGroup then size = Event.IniDCSGroup:getSize() + elseif Event.IniDCSGroupName then + local grp = Group.getByName(Event.IniDCSGroupName) + if grp then + size = grp:getSize() + end + elseif Object:IsAlive() then + size = Object:CountAliveUnits() end if size == 1 then -- Only remove if the last unit of the group was destroyed. self:Remove( ObjectName ) @@ -1586,12 +1582,12 @@ do -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) if Event.IniObjectCategory == Object.Category.UNIT then if not self.Database[Event.IniDCSGroupName] then self.Database[Event.IniDCSGroupName] = GROUP:Register( Event.IniDCSGroupName ) - self:T3( self.Database[Event.IniDCSGroupName] ) + --self:T(3( self.Database[Event.IniDCSGroupName] ) end end @@ -1605,7 +1601,7 @@ do -- @return #string The name of the GROUP -- @return #table The GROUP function SET_GROUP:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSGroupName, self.Database[Event.IniDCSGroupName] end @@ -1615,7 +1611,7 @@ do -- @param #function IteratorFunction The function that will be called for all GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroup( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -1627,7 +1623,7 @@ do -- @param #function IteratorFunction The function that will be called for some GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForSomeGroup( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForSome( IteratorFunction, arg, self:GetSet() ) @@ -1639,7 +1635,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupAlive( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetAliveSet() ) @@ -1651,7 +1647,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForSomeGroupAlive( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForSome( IteratorFunction, arg, self:GetAliveSet() ) @@ -1680,7 +1676,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -1702,7 +1698,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupPartlyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -1724,7 +1720,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -1742,7 +1738,7 @@ do --- Iterate the SET_GROUP and return true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if all the @{Wrapper.Group#GROUP} are completely in the @{Core.Zone#ZONE}, false otherwise -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1755,7 +1751,7 @@ do -- MESSAGE:New("Some or all SET's GROUP are outside zone !", 10):ToAll() -- end function SET_GROUP:AllCompletelyInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if not GroupData:IsCompletelyInZone( Zone ) then @@ -1771,7 +1767,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroupAnyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -1789,7 +1785,7 @@ do --- Iterate the SET_GROUP and return true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1802,7 +1798,7 @@ do -- MESSAGE:New("No GROUP is completely in zone !", 10):ToAll() -- end function SET_GROUP:AnyCompletelyInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if GroupData:IsCompletelyInZone( Zone ) then @@ -1814,7 +1810,7 @@ do --- Iterate the SET_GROUP and return true if at least one @{#UNIT} of one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is in @{Core.Zone} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1827,7 +1823,7 @@ do -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end function SET_GROUP:AnyInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if GroupData:IsPartlyInZone( Zone ) or GroupData:IsCompletelyInZone( Zone ) then @@ -1840,7 +1836,7 @@ do --- Iterate the SET_GROUP and return true if at least one @{Wrapper.Group#GROUP} of the @{#SET_GROUP} is partly in @{Core.Zone}. -- Will return false if a @{Wrapper.Group#GROUP} is fully in the @{Core.Zone} -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if at least one of the @{Wrapper.Group#GROUP} is partly or completely inside the @{Core.Zone#ZONE}, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1853,7 +1849,7 @@ do -- MESSAGE:New("No GROUP are in zone, or one (or more) GROUP is completely in it !", 10):ToAll() -- end function SET_GROUP:AnyPartlyInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local IsPartlyInZone = false local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP @@ -1875,7 +1871,7 @@ do -- This could also be achieved with `not SET_GROUP:AnyPartlyInZone(Zone)`, but it's easier for the -- mission designer to add a dedicated method -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean true if no @{Wrapper.Group#GROUP} is inside the @{Core.Zone#ZONE} in any way, false otherwise. -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1888,7 +1884,7 @@ do -- MESSAGE:New("No UNIT of any GROUP is in zone !", 10):ToAll() -- end function SET_GROUP:NoneInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP if not GroupData:IsNotInZone( Zone ) then -- If the GROUP is in Zone in any way @@ -1902,7 +1898,7 @@ do -- That could easily be done with SET_GROUP:ForEachGroupCompletelyInZone(), but this function -- provides an easy to use shortcut... -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #number the number of GROUPs completely in the Zone -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1911,7 +1907,7 @@ do -- -- MESSAGE:New("There are " .. MySetGroup:CountInZone(MyZone) .. " GROUPs in the Zone !", 10):ToAll() function SET_GROUP:CountInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Count = 0 local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP @@ -1924,7 +1920,7 @@ do --- Iterate the SET_GROUP and count how many UNITs are completely in the Zone -- @param #SET_GROUP self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #number the number of GROUPs completely in the Zone -- @usage -- local MyZone = ZONE:New("Zone1") @@ -1933,7 +1929,7 @@ do -- -- MESSAGE:New("There are " .. MySetGroup:CountUnitInZone(MyZone) .. " UNITs in the Zone !", 10):ToAll() function SET_GROUP:CountUnitInZone( Zone ) - self:F2( Zone ) + --self:F2( Zone ) local Count = 0 local Set = self:GetSet() for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP @@ -1976,7 +1972,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. ---- @return #SET_GROUP self -- function SET_GROUP:ForEachPlayer( IteratorFunction, ... ) - -- self:F2( arg ) + -- --self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- @@ -1989,7 +1985,7 @@ do -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a CLIENT parameter. ---- @return #SET_GROUP self -- function SET_GROUP:ForEachClient( IteratorFunction, ... ) - -- self:F2( arg ) + -- --self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- @@ -2001,12 +1997,12 @@ do -- @param Wrapper.Group#GROUP MGroup The group that is checked for inclusion. -- @return #SET_GROUP self function SET_GROUP:IsIncludeObject( MGroup ) - self:F2( MGroup ) + --self:F2( MGroup ) local MGroupInclude = true if self.Filter.Alive == true then local MGroupAlive = false - self:F( { Active = self.Filter.Active } ) + --self:F( { Active = self.Filter.Active } ) if MGroup and MGroup:IsAlive() then MGroupAlive = true end @@ -2015,7 +2011,7 @@ do if self.Filter.Active ~= nil then local MGroupActive = false - self:F( { Active = self.Filter.Active } ) + --self:F( { Active = self.Filter.Active } ) if self.Filter.Active == false or (self.Filter.Active == true and MGroup:IsActive() == true) then MGroupActive = true end @@ -2025,7 +2021,7 @@ do if self.Filter.Coalitions and MGroupInclude then local MGroupCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T3( { "Coalition:", MGroup:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MGroup:GetCoalition() then MGroupCoalition = true end @@ -2036,18 +2032,19 @@ do if self.Filter.Categories and MGroupInclude then local MGroupCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:I( { "Category:", MGroup:GetCategory(), self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MGroup:GetCategory() then MGroupCategory = true end end MGroupInclude = MGroupInclude and MGroupCategory + --self:I("Is Included: "..tostring(MGroupInclude)) end if self.Filter.Countries and MGroupInclude then local MGroupCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MGroup:GetCountry(), CountryName } ) + --self:T3( { "Country:", MGroup:GetCountry(), CountryName } ) if country.id[CountryName] == MGroup:GetCountry() then MGroupCountry = true end @@ -2058,12 +2055,13 @@ do if self.Filter.GroupPrefixes and MGroupInclude then local MGroupPrefix = false for GroupPrefixId, GroupPrefix in pairs( self.Filter.GroupPrefixes ) do - self:T3( { "Prefix:", string.find( MGroup:GetName(), GroupPrefix, 1 ), GroupPrefix } ) - if string.find( MGroup:GetName(), GroupPrefix:gsub( "-", "%%-" ), 1 ) then + --self:I( { "Prefix:", MGroup:GetName(), GroupPrefix } ) + if string.find(MGroup:GetName(), string.gsub(GroupPrefix,"-","%%-"),1) then MGroupPrefix = true end end MGroupInclude = MGroupInclude and MGroupPrefix + --self:I("Is Included: "..tostring(MGroupInclude)) end if self.Filter.Zones and MGroupInclude then @@ -2083,7 +2081,7 @@ do MGroupInclude = MGroupInclude and MGroupFunc end - self:T2( MGroupInclude ) + --self:I( MGroupInclude ) return MGroupInclude end @@ -2105,16 +2103,19 @@ do if group and group:IsAlive() and (Coalitions==nil or UTILS.IsAnyInTable(Coalitions, group:GetCoalition())) then - local coord=group:GetCoord() + local coord=group:GetCoordinate() - -- Distance between ref. coordinate and group coordinate. - local d=UTILS.VecDist3D(Coordinate, coord) - - if d A map of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. function SET_UNIT:GetUnitTypes() - self:F2() + --self:F2() local MT = {} -- Message Text local UnitTypes = {} @@ -2914,7 +2966,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #string The unit types string function SET_UNIT:GetUnitTypesText() - self:F2() + --self:F2() local MT = {} -- Message Text local UnitTypes = self:GetUnitTypes() @@ -2930,7 +2982,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #table. function SET_UNIT:GetUnitThreatLevels() - self:F2() + --self:F2() local UnitThreatLevels = {} @@ -2966,7 +3018,7 @@ do -- SET_UNIT end end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + --self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) return MaxThreatLevelA2G, MaxThreatText end @@ -3016,7 +3068,7 @@ do -- SET_UNIT local velocity = self:GetVelocity() or 0 Coordinate:SetHeading( heading ) Coordinate:SetVelocity( velocity ) - self:T(UTILS.PrintTableToLog(Coordinate)) + --self:T(UTILS.PrintTableToLog(Coordinate)) end return Coordinate @@ -3042,7 +3094,7 @@ do -- SET_UNIT end end - self:F( { MaxVelocity = MaxVelocity } ) + --self:F( { MaxVelocity = MaxVelocity } ) return MaxVelocity end @@ -3085,7 +3137,7 @@ do -- SET_UNIT -- @param DCS#Unit.RadarType RadarType -- @return #number The amount of radars in the Set with the given type function SET_UNIT:HasRadar( RadarType ) - self:F2( RadarType ) + --self:F2( RadarType ) local RadarCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do @@ -3096,7 +3148,7 @@ do -- SET_UNIT else HasSensors = UnitSensorTest:HasSensors( Unit.SensorType.RADAR ) end - self:T3( HasSensors ) + --self:T3( HasSensors ) if HasSensors then RadarCount = RadarCount + 1 end @@ -3109,7 +3161,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #number The amount of SEADable units in the Set function SET_UNIT:HasSEAD() - self:F2() + --self:F2() local SEADCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do @@ -3119,7 +3171,7 @@ do -- SET_UNIT local HasSEAD = UnitSEAD:HasSEAD() - self:T3( HasSEAD ) + --self:T3( HasSEAD ) if HasSEAD then SEADCount = SEADCount + 1 end @@ -3133,7 +3185,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #number The amount of ground targets in the Set. function SET_UNIT:HasGroundUnits() - self:F2() + --self:F2() local GroundUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do @@ -3150,7 +3202,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #number The amount of air targets in the Set. function SET_UNIT:HasAirUnits() - self:F2() + --self:F2() local AirUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do @@ -3167,7 +3219,7 @@ do -- SET_UNIT -- @param #SET_UNIT self -- @return #number The amount of ground targets in the Set. function SET_UNIT:HasFriendlyUnits( FriendlyCoalition ) - self:F2() + --self:F2() local FriendlyUnitCount = 0 for UnitID, UnitData in pairs( self:GetSet() ) do @@ -3187,7 +3239,7 @@ do -- SET_UNIT -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a UNIT parameter. ---- @return #SET_UNIT self -- function SET_UNIT:ForEachPlayer( IteratorFunction, ... ) - -- self:F2( arg ) + -- --self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.PlayersAlive ) -- @@ -3200,7 +3252,7 @@ do -- SET_UNIT -- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_UNIT. The function needs to accept a CLIENT parameter. ---- @return #SET_UNIT self -- function SET_UNIT:ForEachClient( IteratorFunction, ... ) - -- self:F2( arg ) + -- --self:F2( arg ) -- -- self:ForEach( IteratorFunction, arg, self.Clients ) -- @@ -3212,7 +3264,7 @@ do -- SET_UNIT -- @param Wrapper.Unit#UNIT MUnit -- @return #SET_UNIT self function SET_UNIT:IsIncludeObject( MUnit ) - self:F2( {MUnit} ) + --self:F2( {MUnit} ) local MUnitInclude = false @@ -3231,7 +3283,7 @@ do -- SET_UNIT if self.Filter.Coalitions and MUnitInclude then local MUnitCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:F( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:F( { "Coalition:", MUnit:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MUnit:GetCoalition() then MUnitCoalition = true end @@ -3242,7 +3294,7 @@ do -- SET_UNIT if self.Filter.Categories and MUnitInclude then local MUnitCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T3( { "Category:", MUnit:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MUnit:GetDesc().category then MUnitCategory = true end @@ -3253,7 +3305,7 @@ do -- SET_UNIT if self.Filter.Types and MUnitInclude then local MUnitType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) + --self:T3( { "Type:", MUnit:GetTypeName(), TypeName } ) if TypeName == MUnit:GetTypeName() then MUnitType = true end @@ -3264,7 +3316,7 @@ do -- SET_UNIT if self.Filter.Countries and MUnitInclude then local MUnitCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) + --self:T3( { "Country:", MUnit:GetCountry(), CountryName } ) if country.id[CountryName] == MUnit:GetCountry() then MUnitCountry = true end @@ -3275,7 +3327,7 @@ do -- SET_UNIT if self.Filter.UnitPrefixes and MUnitInclude then local MUnitPrefix = false for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do - self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) + --self:T3( { "Prefix:", string.find( MUnit:GetName(), UnitPrefix, 1 ), UnitPrefix } ) if string.find( MUnit:GetName(), UnitPrefix, 1 ) then MUnitPrefix = true end @@ -3286,10 +3338,10 @@ do -- SET_UNIT if self.Filter.RadarTypes and MUnitInclude then local MUnitRadar = false for RadarTypeID, RadarType in pairs( self.Filter.RadarTypes ) do - self:T3( { "Radar:", RadarType } ) + --self:T3( { "Radar:", RadarType } ) if MUnit:HasSensors( Unit.SensorType.RADAR, RadarType ) == true then if MUnit:GetRadar() == true then -- This call is necessary to evaluate the SEAD capability. - self:T3( "RADAR Found" ) + --self:T3( "RADAR Found" ) end MUnitRadar = true end @@ -3300,7 +3352,7 @@ do -- SET_UNIT if self.Filter.SEAD and MUnitInclude then local MUnitSEAD = false if MUnit:HasSEAD() == true then - self:T3( "SEAD Found" ) + --self:T3( "SEAD Found" ) MUnitSEAD = true end MUnitInclude = MUnitInclude and MUnitSEAD @@ -3310,7 +3362,7 @@ do -- SET_UNIT if self.Filter.Zones and MUnitInclude then local MGroupZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T3( "Zone:", ZoneName ) if MUnit:IsInZone(Zone) then MGroupZone = true end @@ -3323,7 +3375,7 @@ do -- SET_UNIT MUnitInclude = MUnitInclude and MUnitFunc end - self:T2( MUnitInclude ) + --self:T2( MUnitInclude ) return MUnitInclude end @@ -3481,7 +3533,7 @@ do -- SET_STATIC -- @param Wrapper.Static#STATIC AddStatic A single STATIC. -- @return #SET_STATIC self function SET_STATIC:AddStatic( AddStatic ) - self:F2( AddStatic:GetName() ) + --self:F2( AddStatic:GetName() ) self:Add( AddStatic:GetName(), AddStatic ) @@ -3496,7 +3548,7 @@ do -- SET_STATIC local AddStaticNamesArray = (type( AddStaticNames ) == "table") and AddStaticNames or { AddStaticNames } - self:T( AddStaticNamesArray ) + --self:T(( AddStaticNamesArray ) for AddStaticID, AddStaticName in pairs( AddStaticNamesArray ) do self:Add( AddStaticName, STATIC:FindByName( AddStaticName ) ) end @@ -3671,7 +3723,7 @@ do -- SET_STATIC self:_FilterStart() self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) - self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) end return self @@ -3702,12 +3754,12 @@ do -- SET_STATIC -- @return #string The name of the STATIC -- @return #table The STATIC function SET_STATIC:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) if Event.IniObjectCategory == Object.Category.STATIC then if not self.Database[Event.IniDCSUnitName] then self.Database[Event.IniDCSUnitName] = STATIC:Register( Event.IniDCSUnitName ) - self:T3( self.Database[Event.IniDCSUnitName] ) + --self:T(3( self.Database[Event.IniDCSUnitName] ) end end @@ -3721,7 +3773,7 @@ do -- SET_STATIC -- @return #string The name of the STATIC -- @return #table The STATIC function SET_STATIC:FindInDatabase( Event ) - self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) + --self:F2( { Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName], Event } ) return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end @@ -3752,7 +3804,7 @@ do -- SET_STATIC --- Check if no element of the SET_STATIC is in the Zone. -- @param #SET_STATIC self - -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. + -- @param Core.Zone#ZONE Zone The Zone to be tested for. -- @return #boolean function SET_STATIC:IsNotInZone( Zone ) @@ -3779,7 +3831,7 @@ do -- SET_STATIC -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStaticInZone( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -3794,7 +3846,7 @@ do -- SET_STATIC -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStatic( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -3807,7 +3859,7 @@ do -- SET_STATIC -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStaticCompletelyInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -3829,7 +3881,7 @@ do -- SET_STATIC -- @param #function IteratorFunction The function that will be called when there is an alive STATIC in the SET_STATIC. The function needs to accept a STATIC parameter. -- @return #SET_STATIC self function SET_STATIC:ForEachStaticNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -3849,7 +3901,7 @@ do -- SET_STATIC -- @param #SET_STATIC self -- @return #map<#string,#number> A map of the unit types found. The key is the StaticTypeName and the value is the amount of unit types found. function SET_STATIC:GetStaticTypes() - self:F2() + --self:F2() local MT = {} -- Message Text local StaticTypes = {} @@ -3878,7 +3930,7 @@ do -- SET_STATIC -- @param #SET_STATIC self -- @return #string The unit types string function SET_STATIC:GetStaticTypesText() - self:F2() + --self:F2() local MT = {} -- Message Text local StaticTypes = self:GetStaticTypes() @@ -3936,7 +3988,7 @@ do -- SET_STATIC Coordinate:SetHeading( AvgHeading ) Coordinate:SetVelocity( MaxVelocity ) - self:F( { Coordinate = Coordinate } ) + --self:F( { Coordinate = Coordinate } ) return Coordinate end @@ -3999,7 +4051,7 @@ do -- SET_STATIC end end - self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) + --self:F( { MaxThreatLevelA2G = MaxThreatLevelA2G, MaxThreatText = MaxThreatText } ) return MaxThreatLevelA2G, MaxThreatText end @@ -4009,13 +4061,13 @@ do -- SET_STATIC -- @param Wrapper.Static#STATIC MStatic -- @return #SET_STATIC self function SET_STATIC:IsIncludeObject( MStatic ) - self:F2( MStatic ) + --self:F2( MStatic ) local MStaticInclude = true if self.Filter.Coalitions then local MStaticCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do - self:T3( { "Coalition:", MStatic:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T(3( { "Coalition:", MStatic:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == MStatic:GetCoalition() then MStaticCoalition = true end @@ -4026,7 +4078,7 @@ do -- SET_STATIC if self.Filter.Categories then local MStaticCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do - self:T3( { "Category:", MStatic:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T(3( { "Category:", MStatic:GetDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == MStatic:GetDesc().category then MStaticCategory = true end @@ -4037,7 +4089,7 @@ do -- SET_STATIC if self.Filter.Types then local MStaticType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MStatic:GetTypeName(), TypeName } ) + --self:T(3( { "Type:", MStatic:GetTypeName(), TypeName } ) if TypeName == MStatic:GetTypeName() then MStaticType = true end @@ -4048,7 +4100,7 @@ do -- SET_STATIC if self.Filter.Countries then local MStaticCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do - self:T3( { "Country:", MStatic:GetCountry(), CountryName } ) + --self:T(3( { "Country:", MStatic:GetCountry(), CountryName } ) if country.id[CountryName] == MStatic:GetCountry() then MStaticCountry = true end @@ -4059,7 +4111,7 @@ do -- SET_STATIC if self.Filter.StaticPrefixes then local MStaticPrefix = false for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do - self:T3( { "Prefix:", string.find( MStatic:GetName(), StaticPrefix, 1 ), StaticPrefix } ) + --self:T(3( { "Prefix:", string.find( MStatic:GetName(), StaticPrefix, 1 ), StaticPrefix } ) if string.find( MStatic:GetName(), StaticPrefix, 1 ) then MStaticPrefix = true end @@ -4070,7 +4122,7 @@ do -- SET_STATIC if self.Filter.Zones then local MStaticZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T(3( "Zone:", ZoneName ) if MStatic and MStatic:IsInZone(Zone) then MStaticZone = true end @@ -4078,7 +4130,12 @@ do -- SET_STATIC MStaticInclude = MStaticInclude and MStaticZone end - self:T2( MStaticInclude ) + if self.Filter.Functions and MStaticInclude then + local MClientFunc = self:_EvalFilterFunctions(MStatic) + MStaticInclude = MStaticInclude and MClientFunc + end + + --self:T(2( MStaticInclude ) return MStaticInclude end @@ -4406,6 +4463,35 @@ do -- SET_CLIENT end return self end + + --- Builds a set of clients which belong to groups with certain **group names**. + -- @param #SET_CLIENT self + -- @param #string Prefixes The (partial) group names to look for. Can be anywhere in the group name. Can be a single string or a table of strings. + -- @return #SET_CLIENT self + function SET_CLIENT:FilterGroupPrefixes(Prefixes) + if type(Prefixes) == "string" then + Prefixes = {Prefixes} + end + self:FilterFunction( + function(unit,prefixes) + local outcome = false + if unit then + local grp = unit:GetGroup() + local gname = grp ~= nil and grp:GetName() or "none" + for _,_fix in pairs(prefixes or {}) do + if string.find(gname,_fix) then + outcome = true + break + end + end + else + return false + end + return outcome + end, Prefixes + ) + return self + end --- Builds a set of clients that are only active. -- Only the clients that are active will be included within the set. @@ -4433,6 +4519,23 @@ do -- SET_CLIENT return self end + --- Builds a set of units which exist and are alive. + -- @param #SET_CLIENT self + -- @return #SET_CLIENT self + function SET_CLIENT:FilterAlive() + self:FilterFunction( + function(unit) + if unit and unit:IsExist() and unit:IsAlive() then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of clients in zones. -- @param #SET_CLIENT self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE @@ -4497,7 +4600,7 @@ do -- SET_CLIENT self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.Crash) --self:UnHandleEvent(EVENTS.PlayerEnterUnit) - --self:UnHandleEvent(EVENTS.PlayerLeaveUnit) + self:UnHandleEvent(EVENTS.PlayerLeaveUnit) if self.Filter.Zones and self.ZoneTimer and self.ZoneTimer:IsRunning() then self.ZoneTimer:Stop() @@ -4517,7 +4620,7 @@ do -- SET_CLIENT self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventPlayerEnterUnit) - --self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventPlayerLeaveUnit) + self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventPlayerLeaveUnit) --self:SetEventPriority(1) if self.Filter.Zones then self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) @@ -4535,12 +4638,12 @@ do -- SET_CLIENT -- @param Core.Event#EVENTDATA Event -- @return #SET_CLIENT self function SET_CLIENT:_EventPlayerEnterUnit(Event) - self:I( "_EventPlayerEnterUnit" ) + --self:I( "_EventPlayerEnterUnit" ) if Event.IniDCSUnit then if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then -- CA Slot entered local ObjectName, Object = self:AddInDatabase( Event ) - self:T( ObjectName, UTILS.PrintTableToLog(Object) ) + --self:T(( ObjectName, UTILS.PrintTableToLog(Object) ) if Object and self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) end @@ -4554,9 +4657,9 @@ do -- SET_CLIENT -- @param Core.Event#EVENTDATA Event -- @return #SET_CLIENT self function SET_CLIENT:_EventPlayerLeaveUnit(Event) - self:I( "_EventPlayerLeaveUnit" ) + --self:I( "_EventPlayerLeaveUnit" ) if Event.IniDCSUnit then - if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then + if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup then --and Event.IniGroup:IsGround() then -- CA Slot left local ObjectName, Object = self:FindInDatabase( Event ) if ObjectName then @@ -4566,6 +4669,16 @@ do -- SET_CLIENT end return self end + + --- Make the SET handle CA slots **only** (GROUND units used by any player). Needs active filtering with `FilterStart()` + -- @param #SET_CLIENT self + -- @return #SET_CLIENT self + function SET_CLIENT:HandleCASlots() + self:HandleEvent(EVENTS.PlayerEnterUnit,SET_CLIENT._EventPlayerEnterUnit) + self:HandleEvent(EVENTS.PlayerLeaveUnit,SET_CLIENT._EventPlayerLeaveUnit) + self:FilterFunction(function(client) if client and client:IsAlive() and client:IsGround() then return true else return false end end) + return self + 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! @@ -4574,7 +4687,7 @@ do -- SET_CLIENT -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -4586,7 +4699,7 @@ do -- SET_CLIENT -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_CLIENT:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -4596,7 +4709,7 @@ do -- SET_CLIENT -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClient( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -4609,7 +4722,7 @@ do -- SET_CLIENT -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -4631,7 +4744,7 @@ do -- SET_CLIENT -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_CLIENT. The function needs to accept a CLIENT parameter. -- @return #SET_CLIENT self function SET_CLIENT:ForEachClientNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -4709,7 +4822,7 @@ do -- SET_CLIENT -- @param Wrapper.Client#CLIENT MClient -- @return #SET_CLIENT self function SET_CLIENT:IsIncludeObject( MClient ) - self:F2( MClient ) + --self:F2( MClient ) local MClientInclude = true @@ -4732,40 +4845,48 @@ do -- SET_CLIENT if ClientCoalitionID==nil and MClient:IsAlive()~=nil then ClientCoalitionID=MClient:GetCoalition() end - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and ClientCoalitionID and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then MClientCoalition = true end end - self:T( { "Evaluated Coalition", MClientCoalition } ) + --self:T( { "Evaluated Coalition", MClientCoalition } ) MClientInclude = MClientInclude and MClientCoalition end - + if self.Filter.Categories and MClientInclude then local MClientCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) - if ClientCategoryID==nil and MClient:IsAlive()~=nil then - ClientCategoryID=MClient:GetCategory() - end - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and ClientCategoryID and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then - MClientCategory = true + local UnitCategory = 0 + if ClientCategoryID==nil and MClient:IsExist() then + ClientCategoryID,UnitCategory=MClient:GetCategory() + --self:T3("Applying Category Workaround .. Outcome: Obj is "..tostring(ClientCategoryID).." Unit is "..tostring(UnitCategory)) + --self:T(3( { "Category:", UnitCategory, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and UnitCategory and self.FilterMeta.Categories[CategoryName] == UnitCategory then + MClientCategory = true + end + --self:T3("Filter Outcome is "..tostring(MClientCategory)) + else + --self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and ClientCategoryID and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then + MClientCategory = true + end end end - self:T( { "Evaluated Category", MClientCategory } ) + --self:T( { "Evaluated Category", MClientCategory } ) MClientInclude = MClientInclude and MClientCategory end if self.Filter.Types and MClientInclude then local MClientType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) + --self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) if TypeName == MClient:GetTypeName() then MClientType = true end end - self:T( { "Evaluated Type", MClientType } ) + --self:T(( { "Evaluated Type", MClientType } ) MClientInclude = MClientInclude and MClientType end @@ -4776,31 +4897,31 @@ do -- SET_CLIENT if ClientCountryID==nil and MClient:IsAlive()~=nil then ClientCountryID=MClient:GetCountry() end - self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) + --self:T(3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) if country.id[CountryName] and ClientCountryID and country.id[CountryName] == ClientCountryID then MClientCountry = true end end - self:T( { "Evaluated Country", MClientCountry } ) + --self:T(( { "Evaluated Country", MClientCountry } ) MClientInclude = MClientInclude and MClientCountry end if self.Filter.ClientPrefixes and MClientInclude then local MClientPrefix = false for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) + --self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) if string.find( MClient.UnitName, ClientPrefix, 1 ) then MClientPrefix = true end end - self:T( { "Evaluated Prefix", MClientPrefix } ) + --self:T( { "Evaluated Prefix", MClientPrefix } ) MClientInclude = MClientInclude and MClientPrefix end if self.Filter.Zones and MClientInclude then local MClientZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T3( "Zone:", ZoneName ) local unit = MClient:GetClientGroupUnit() if unit and unit:IsInZone(Zone) then MClientZone = true @@ -4818,7 +4939,7 @@ do -- SET_CLIENT MClientPlayername = true end end - self:T( { "Evaluated Playername", MClientPlayername } ) + --self:T( { "Evaluated Playername", MClientPlayername } ) MClientInclude = MClientInclude and MClientPlayername end @@ -4831,7 +4952,7 @@ do -- SET_CLIENT MClientCallsigns = true end end - self:T( { "Evaluated Callsign", MClientCallsigns } ) + --self:T( { "Evaluated Callsign", MClientCallsigns } ) MClientInclude = MClientInclude and MClientCallsigns end @@ -4841,7 +4962,7 @@ do -- SET_CLIENT end end - self:T2( MClientInclude ) + --self:T2( MClientInclude ) return MClientInclude end @@ -5096,6 +5217,7 @@ do -- SET_PLAYER self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash ) end return self @@ -5108,7 +5230,7 @@ do -- SET_PLAYER -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_PLAYER:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -5120,7 +5242,7 @@ do -- SET_PLAYER -- @return #string The name of the CLIENT -- @return #table The CLIENT function SET_PLAYER:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -5130,7 +5252,7 @@ do -- SET_PLAYER -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayer( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -5143,7 +5265,7 @@ do -- SET_PLAYER -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayerInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -5165,7 +5287,7 @@ do -- SET_PLAYER -- @param #function IteratorFunction The function that will be called when there is an alive CLIENT in the SET_PLAYER. The function needs to accept a CLIENT parameter. -- @return #SET_PLAYER self function SET_PLAYER:ForEachPlayerNotInZone( ZoneObject, IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet(), -- @param Core.Zone#ZONE_BASE ZoneObject @@ -5186,48 +5308,60 @@ do -- SET_PLAYER -- @param Wrapper.Client#CLIENT MClient -- @return #SET_PLAYER self function SET_PLAYER:IsIncludeObject( MClient ) - self:F2( MClient ) + --self:F2( MClient ) local MClientInclude = true if MClient then local MClientName = MClient.UnitName - if self.Filter.Coalitions then + if self.Filter.Coalitions and MClientInclude then local MClientCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do local ClientCoalitionID = _DATABASE:GetCoalitionFromClientTemplate( MClientName ) - self:T3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) - if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then + if ClientCoalitionID==nil and MClient:IsAlive()~=nil then + ClientCoalitionID=MClient:GetCoalition() + end + --self:T(3( { "Coalition:", ClientCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and ClientCoalitionID and self.FilterMeta.Coalitions[CoalitionName] == ClientCoalitionID then MClientCoalition = true end end - self:T( { "Evaluated Coalition", MClientCoalition } ) + --self:T(( { "Evaluated Coalition", MClientCoalition } ) MClientInclude = MClientInclude and MClientCoalition end - if self.Filter.Categories then + if self.Filter.Categories and MClientInclude then local MClientCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do local ClientCategoryID = _DATABASE:GetCategoryFromClientTemplate( MClientName ) - self:T3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) - if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then - MClientCategory = true + local UnitCategory = 0 + if ClientCategoryID==nil and MClient:IsExist() then + ClientCategoryID,UnitCategory=MClient:GetCategory() + --self:T(3( { "Category:", UnitCategory, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and UnitCategory and self.FilterMeta.Categories[CategoryName] == UnitCategory then + MClientCategory = true + end + else + --self:T(3( { "Category:", ClientCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) + if self.FilterMeta.Categories[CategoryName] and ClientCategoryID and self.FilterMeta.Categories[CategoryName] == ClientCategoryID then + MClientCategory = true + end end end - self:T( { "Evaluated Category", MClientCategory } ) + --self:T(( { "Evaluated Category", MClientCategory } ) MClientInclude = MClientInclude and MClientCategory end if self.Filter.Types then local MClientType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MClient:GetTypeName(), TypeName } ) + --self:T(3( { "Type:", MClient:GetTypeName(), TypeName } ) if TypeName == MClient:GetTypeName() then MClientType = true end end - self:T( { "Evaluated Type", MClientType } ) + --self:T(( { "Evaluated Type", MClientType } ) MClientInclude = MClientInclude and MClientType end @@ -5235,24 +5369,24 @@ do -- SET_PLAYER local MClientCountry = false for CountryID, CountryName in pairs( self.Filter.Countries ) do local ClientCountryID = _DATABASE:GetCountryFromClientTemplate( MClientName ) - self:T3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) + --self:T(3( { "Country:", ClientCountryID, country.id[CountryName], CountryName } ) if country.id[CountryName] and country.id[CountryName] == ClientCountryID then MClientCountry = true end end - self:T( { "Evaluated Country", MClientCountry } ) + --self:T(( { "Evaluated Country", MClientCountry } ) MClientInclude = MClientInclude and MClientCountry end if self.Filter.ClientPrefixes then local MClientPrefix = false for ClientPrefixId, ClientPrefix in pairs( self.Filter.ClientPrefixes ) do - self:T3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) + --self:T(3( { "Prefix:", string.find( MClient.UnitName, ClientPrefix, 1 ), ClientPrefix } ) if string.find( MClient.UnitName, ClientPrefix, 1 ) then MClientPrefix = true end end - self:T( { "Evaluated Prefix", MClientPrefix } ) + --self:T(( { "Evaluated Prefix", MClientPrefix } ) MClientInclude = MClientInclude and MClientPrefix end end @@ -5260,7 +5394,7 @@ do -- SET_PLAYER if self.Filter.Zones then local MClientZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) + --self:T(3( "Zone:", ZoneName ) local unit = MClient:GetClientGroupUnit() if unit and unit:IsInZone(Zone) then MClientZone = true @@ -5269,7 +5403,12 @@ do -- SET_PLAYER MClientInclude = MClientInclude and MClientZone end - self:T2( MClientInclude ) + if self.Filter.Functions and MClientInclude then + local MClientFunc = self:_EvalFilterFunctions(MClient) + MClientInclude = MClientInclude and MClientFunc + end + + --self:T(2( MClientInclude ) return MClientInclude end @@ -5322,6 +5461,7 @@ do -- SET_AIRBASE Airbases = {}, Filter = { Coalitions = nil, + Zones = nil, }, FilterMeta = { Coalitions = { @@ -5415,7 +5555,7 @@ do -- SET_AIRBASE local AirbaseCoordinate = AirbaseObject:GetCoordinate() local Distance = Coordinate:Get2DDistance( AirbaseCoordinate ) - self:F( { Distance = Distance } ) + --self:F( { Distance = Distance } ) if Distance <= Range then AirbaseFound = AirbaseObject @@ -5473,6 +5613,31 @@ do -- SET_AIRBASE end return self end + + --- Builds a set of airbase objects in zones. + -- @param #SET_AIRBASE self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_AIRBASE self + function SET_AIRBASE:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + --self:T((zonename) + self.Filter.Zones[zonename] = Zone + end + return self + end --- Starts the filtering. -- @param #SET_AIRBASE self @@ -5546,7 +5711,7 @@ do -- SET_AIRBASE -- @return #string The name of the AIRBASE. -- @return Wrapper.Airbase#AIRBASE The AIRBASE object. function SET_AIRBASE:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -5556,21 +5721,21 @@ do -- SET_AIRBASE -- @param #function IteratorFunction The function that will be called when there is an alive AIRBASE in the SET_AIRBASE. The function needs to accept a AIRBASE parameter. -- @return #SET_AIRBASE self function SET_AIRBASE:ForEachAirbase( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) return self end - --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#POINT_VEC2}. + --- Iterate the SET_AIRBASE while identifying the nearest @{Wrapper.Airbase#AIRBASE} from a @{Core.Point#COORDINATE}. -- @param #SET_AIRBASE self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Wrapper.Airbase#AIRBASE}. -- @return Wrapper.Airbase#AIRBASE The closest @{Wrapper.Airbase#AIRBASE}. - function SET_AIRBASE:FindNearestAirbaseFromPointVec2( PointVec2 ) - self:F2( PointVec2 ) + function SET_AIRBASE:FindNearestAirbaseFromPointVec2( Coordinate ) + --self:F2( Coordinate ) - local NearestAirbase = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestAirbase = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestAirbase end @@ -5579,7 +5744,7 @@ do -- SET_AIRBASE -- @param Wrapper.Airbase#AIRBASE MAirbase -- @return #SET_AIRBASE self function SET_AIRBASE:IsIncludeObject( MAirbase ) - self:F2( MAirbase ) + --self:F2( MAirbase ) local MAirbaseInclude = true @@ -5590,12 +5755,12 @@ do -- SET_AIRBASE local MAirbaseCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do local AirbaseCoalitionID = _DATABASE:GetCoalitionFromAirbase( MAirbaseName ) - self:T3( { "Coalition:", AirbaseCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T(3( { "Coalition:", AirbaseCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == AirbaseCoalitionID then MAirbaseCoalition = true end end - self:T( { "Evaluated Coalition", MAirbaseCoalition } ) + --self:T(( { "Evaluated Coalition", MAirbaseCoalition } ) MAirbaseInclude = MAirbaseInclude and MAirbaseCoalition end @@ -5603,17 +5768,36 @@ do -- SET_AIRBASE local MAirbaseCategory = false for CategoryID, CategoryName in pairs( self.Filter.Categories ) do local AirbaseCategoryID = _DATABASE:GetCategoryFromAirbase( MAirbaseName ) - self:T3( { "Category:", AirbaseCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) + --self:T(3( { "Category:", AirbaseCategoryID, self.FilterMeta.Categories[CategoryName], CategoryName } ) if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == AirbaseCategoryID then MAirbaseCategory = true end end - self:T( { "Evaluated Category", MAirbaseCategory } ) + --self:T(( { "Evaluated Category", MAirbaseCategory } ) MAirbaseInclude = MAirbaseInclude and MAirbaseCategory end + + if self.Filter.Zones and MAirbaseInclude then + local MAirbaseZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + --self:T(( "Zone:", ZoneName ) + local coord = MAirbase:GetCoordinate() + if coord and Zone:IsCoordinateInZone(coord) then + MAirbaseZone = true + end + --self:T(( { "Evaluated Zone", MSceneryZone } ) + end + MAirbaseInclude = MAirbaseInclude and MAirbaseZone + end + + end + + if self.Filter.Functions and MAirbaseInclude then + local MClientFunc = self:_EvalFilterFunctions(MAirbase) + MAirbaseInclude = MAirbaseInclude and MClientFunc end - self:T2( MAirbaseInclude ) + --self:T(2( MAirbaseInclude ) return MAirbaseInclude end @@ -5852,7 +6036,7 @@ do -- SET_CARGO -- @return #string The name of the CARGO -- @return #table The CARGO function SET_CARGO:AddInDatabase( Event ) -- R2.1 - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -5864,7 +6048,7 @@ do -- SET_CARGO -- @return #string The name of the CARGO -- @return #table The CARGO function SET_CARGO:FindInDatabase( Event ) -- R2.1 - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -5874,24 +6058,26 @@ do -- SET_CARGO -- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. -- @return #SET_CARGO self function SET_CARGO:ForEachCargo( IteratorFunction, ... ) -- R2.1 - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) return self end - --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#POINT_VEC2}. + --- (R2.1) Iterate the SET_CARGO while identifying the nearest @{Cargo.Cargo#CARGO} from a @{Core.Point#COORDINATE}. -- @param #SET_CARGO self - -- @param Core.Point#POINT_VEC2 PointVec2 A @{Core.Point#POINT_VEC2} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. + -- @param Core.Point#COORDINATE Coordinate A @{Core.Point#COORDINATE} object from where to evaluate the closest @{Cargo.Cargo#CARGO}. -- @return Cargo.Cargo#CARGO The closest @{Cargo.Cargo#CARGO}. - function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) -- R2.1 - self:F2( PointVec2 ) + function SET_CARGO:FindNearestCargoFromPointVec2( Coordinate ) -- R2.1 + --self:F2( Coordinate ) - local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) + local NearestCargo = self:FindNearestObjectFromPointVec2( Coordinate ) return NearestCargo end - + + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithState( State ) local FirstCargo = nil @@ -5906,6 +6092,8 @@ do -- SET_CARGO return FirstCargo end + --- + -- @param #SET_CARGO self function SET_CARGO:FirstCargoWithStateAndNotDeployed( State ) local FirstCargo = nil @@ -5957,7 +6145,7 @@ do -- SET_CARGO -- @param AI.AI_Cargo#AI_CARGO MCargo -- @return #SET_CARGO self function SET_CARGO:IsIncludeObject( MCargo ) -- R2.1 - self:F2( MCargo ) + --self:F2( MCargo ) local MCargoInclude = true @@ -5968,41 +6156,46 @@ do -- SET_CARGO local MCargoCoalition = false for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do local CargoCoalitionID = MCargo:GetCoalition() - self:T3( { "Coalition:", CargoCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + --self:T(3( { "Coalition:", CargoCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == CargoCoalitionID then MCargoCoalition = true end end - self:F( { "Evaluated Coalition", MCargoCoalition } ) + --self:F( { "Evaluated Coalition", MCargoCoalition } ) MCargoInclude = MCargoInclude and MCargoCoalition end if self.Filter.Types then local MCargoType = false for TypeID, TypeName in pairs( self.Filter.Types ) do - self:T3( { "Type:", MCargo:GetType(), TypeName } ) + --self:T(3( { "Type:", MCargo:GetType(), TypeName } ) if TypeName == MCargo:GetType() then MCargoType = true end end - self:F( { "Evaluated Type", MCargoType } ) + --self:F( { "Evaluated Type", MCargoType } ) MCargoInclude = MCargoInclude and MCargoType end if self.Filter.CargoPrefixes then local MCargoPrefix = false for CargoPrefixId, CargoPrefix in pairs( self.Filter.CargoPrefixes ) do - self:T3( { "Prefix:", string.find( MCargo.Name, CargoPrefix, 1 ), CargoPrefix } ) + --self:T(3( { "Prefix:", string.find( MCargo.Name, CargoPrefix, 1 ), CargoPrefix } ) if string.find( MCargo.Name, CargoPrefix, 1 ) then MCargoPrefix = true end end - self:F( { "Evaluated Prefix", MCargoPrefix } ) + --self:F( { "Evaluated Prefix", MCargoPrefix } ) MCargoInclude = MCargoInclude and MCargoPrefix end end + + if self.Filter.Functions and MCargoInclude then + local MClientFunc = self:_EvalFilterFunctions(MCargo) + MCargoInclude = MCargoInclude and MClientFunc + end - self:T2( MCargoInclude ) + --self:T(2( MCargoInclude ) return MCargoInclude end @@ -6011,7 +6204,7 @@ do -- SET_CARGO -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventNewCargo( EventData ) -- R2.1 - self:F( { "New Cargo", EventData } ) + --self:F( { "New Cargo", EventData } ) if EventData.Cargo then if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then @@ -6024,7 +6217,7 @@ do -- SET_CARGO -- @param #SET_CARGO self -- @param Core.Event#EVENTDATA EventData function SET_CARGO:OnEventDeleteCargo( EventData ) -- R2.1 - self:F3( { EventData } ) + --self:F3( { EventData } ) if EventData.Cargo then local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) @@ -6036,7 +6229,7 @@ do -- SET_CARGO -- To prevent this from happening, the Cargo object has a flag NoDestroy. -- When true, the SET_CARGO won't Remove the Cargo object from the set. -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { CargoNoDestroy = Cargo.NoDestroy } ) + --self:F( { CargoNoDestroy = Cargo.NoDestroy } ) if Cargo.NoDestroy then else self:Remove( Cargo.Name ) @@ -6259,7 +6452,7 @@ do -- SET_ZONE -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_ZONE:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -6271,7 +6464,7 @@ do -- SET_ZONE -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_ZONE:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -6281,7 +6474,7 @@ do -- SET_ZONE -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE self function SET_ZONE:ForEachZone( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -6336,7 +6529,7 @@ do -- SET_ZONE -- @param Core.Zone#ZONE_BASE MZone -- @return #SET_ZONE self function SET_ZONE:IsIncludeObject( MZone ) - self:F2( MZone ) + --self:F2( MZone ) local MZoneInclude = true @@ -6346,17 +6539,22 @@ do -- SET_ZONE if self.Filter.Prefixes then local MZonePrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do - self:T2( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) + --self:T(2( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) if string.find( MZoneName, ZonePrefix, 1 ) then MZonePrefix = true end end - self:T( { "Evaluated Prefix", MZonePrefix } ) + --self:T(( { "Evaluated Prefix", MZonePrefix } ) MZoneInclude = MZoneInclude and MZonePrefix end end + + if self.Filter.Functions and MZoneInclude then + local MClientFunc = self:_EvalFilterFunctions(MZone) + MZoneInclude = MZoneInclude and MClientFunc + end - self:T2( MZoneInclude ) + --self:T(2( MZoneInclude ) return MZoneInclude end @@ -6365,7 +6563,7 @@ do -- SET_ZONE -- @param Core.Event#EVENTDATA EventData function SET_ZONE:OnEventNewZone( EventData ) -- R2.1 - self:F( { "New Zone", EventData } ) + --self:F( { "New Zone", EventData } ) if EventData.Zone then if EventData.Zone and self:IsIncludeObject( EventData.Zone ) then @@ -6378,7 +6576,7 @@ do -- SET_ZONE -- @param #SET_ZONE self -- @param Core.Event#EVENTDATA EventData function SET_ZONE:OnEventDeleteZone( EventData ) -- R2.1 - self:F3( { EventData } ) + --self:F3( { EventData } ) if EventData.Zone then local Zone = _DATABASE:FindZone( EventData.Zone.ZoneName ) @@ -6390,7 +6588,7 @@ do -- SET_ZONE -- To prevent this from happening, the Zone object has a flag NoDestroy. -- When true, the SET_ZONE won't Remove the Zone object from the set. -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy = Zone.NoDestroy } ) + --self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -6795,7 +6993,7 @@ do -- SET_ZONE_GOAL -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_ZONE_GOAL:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -6807,7 +7005,7 @@ do -- SET_ZONE_GOAL -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_ZONE_GOAL:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -6817,7 +7015,7 @@ do -- SET_ZONE_GOAL -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter. -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:ForEachZone( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -6829,7 +7027,7 @@ do -- SET_ZONE_GOAL -- @param Core.Zone#ZONE_BASE MZone -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:IsIncludeObject( MZone ) - self:F2( MZone ) + --self:F2( MZone ) local MZoneInclude = true @@ -6839,17 +7037,22 @@ do -- SET_ZONE_GOAL if self.Filter.Prefixes then local MZonePrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do - self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) + --self:T(3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) if string.find( MZoneName, ZonePrefix, 1 ) then MZonePrefix = true end end - self:T( { "Evaluated Prefix", MZonePrefix } ) + --self:T(( { "Evaluated Prefix", MZonePrefix } ) MZoneInclude = MZoneInclude and MZonePrefix end end + + if self.Filter.Functions and MZoneInclude then + local MClientFunc = self:_EvalFilterFunctions(MZone) + MZoneInclude = MZoneInclude and MClientFunc + end - self:T2( MZoneInclude ) + --self:T(2( MZoneInclude ) return MZoneInclude end @@ -6859,12 +7062,12 @@ do -- SET_ZONE_GOAL function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData ) -- Debug info. - self:T( { "New Zone Capture Coalition", EventData } ) - self:T( { "Zone Capture Coalition", EventData.ZoneGoal } ) + --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:T( { "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 @@ -6874,7 +7077,7 @@ do -- SET_ZONE_GOAL -- @param #SET_ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) -- R2.1 - self:F3( { EventData } ) + --self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) @@ -6886,7 +7089,7 @@ do -- SET_ZONE_GOAL -- To prevent this from happening, the Zone object has a flag NoDestroy. -- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set. -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy = Zone.NoDestroy } ) + --self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -7172,7 +7375,7 @@ do -- SET_OPSZONE -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_OPSZONE:AddInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -7184,7 +7387,7 @@ do -- SET_OPSZONE -- @return #string The name of the AIRBASE -- @return #table The AIRBASE function SET_OPSZONE:FindInDatabase( Event ) - self:F3( { Event } ) + --self:F3( { Event } ) return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end @@ -7194,7 +7397,7 @@ do -- SET_OPSZONE -- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_OPSZONE. The function needs to accept a AIRBASE parameter. -- @return #SET_OPSZONE self function SET_OPSZONE:ForEachZone( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) @@ -7206,7 +7409,7 @@ do -- SET_OPSZONE -- @param Ops.OpsZone#OPSZONE MZone The OPSZONE object. -- @return #SET_OPSZONE self function SET_OPSZONE:IsIncludeObject( MZone ) - self:F2( MZone ) + --self:F2( MZone ) local MZoneInclude = true @@ -7222,7 +7425,7 @@ do -- SET_OPSZONE for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do -- Prifix - self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) + --self:T(3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } ) if string.find(MZoneName, ZonePrefix, 1) then MZonePrefix = true @@ -7231,7 +7434,7 @@ do -- SET_OPSZONE end - self:T( { "Evaluated Prefix", MZonePrefix } ) + --self:T(( { "Evaluated Prefix", MZonePrefix } ) MZoneInclude = MZoneInclude and MZonePrefix end @@ -7256,8 +7459,13 @@ do -- SET_OPSZONE end end - - self:T2( MZoneInclude ) + + if self.Filter.Functions and MZoneInclude then + local MClientFunc = self:_EvalFilterFunctions(MZone) + MZoneInclude = MZoneInclude and MClientFunc + end + + --self:T(2( MZoneInclude ) return MZoneInclude end @@ -7267,12 +7475,12 @@ do -- SET_OPSZONE function SET_OPSZONE:OnEventNewZoneGoal( EventData ) -- Debug info. - self:T( { "New Zone Capture Coalition", EventData } ) - self:T( { "Zone Capture Coalition", EventData.ZoneGoal } ) + --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:T( { "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 @@ -7282,7 +7490,7 @@ do -- SET_OPSZONE -- @param #SET_OPSZONE self -- @param Core.Event#EVENTDATA EventData function SET_OPSZONE:OnEventDeleteZoneGoal( EventData ) -- R2.1 - self:F3( { EventData } ) + --self:F3( { EventData } ) if EventData.ZoneGoal then local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName ) @@ -7294,7 +7502,7 @@ do -- SET_OPSZONE -- To prevent this from happening, the Zone object has a flag NoDestroy. -- When true, the SET_OPSZONE won't Remove the Zone object from the set. -- This flag is switched off after the event handlers have been called in the EVENT class. - self:F( { ZoneNoDestroy = Zone.NoDestroy } ) + --self:F( { ZoneNoDestroy = Zone.NoDestroy } ) if Zone.NoDestroy then else self:Remove( Zone.ZoneName ) @@ -7451,7 +7659,7 @@ do -- SET_OPSGROUP -- -- 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() } ) + -- --self:F( { GroupObject = GroupObject:GetName() } ) -- end -- -- @@ -7522,7 +7730,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:T( { 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 @@ -7845,6 +8053,7 @@ do -- SET_OPSGROUP self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) end return self @@ -7868,22 +8077,31 @@ do -- SET_OPSGROUP --- Handles the OnBirth event for the Set. -- @param #SET_OPSGROUP self -- @param Core.Event#EVENTDATA Event Event data. - function SET_OPSGROUP:_EventOnBirth( Event ) - self:F3( { Event } ) +function SET_OPSGROUP:_EventOnBirth(Event) + --self:F3( { Event } ) if Event.IniDCSUnit and Event.IniDCSGroup then - local DCSgroup=Event.IniDCSGroup --DCS#Group + local DCSgroup = Event.IniDCSGroup --DCS#Group - if DCSgroup:getInitialSize() == DCSgroup:getSize() then -- This seems to be not a good check as even for the first birth event, getSize returns the total number of units in the group. - - local groupname, group = self:AddInDatabase( Event ) - - if group and group:CountAliveUnits()==DCSgroup:getInitialSize() then - if group and self:IsIncludeObject( group ) then - self:Add( groupname, group ) - end + -- group:CountAliveUnits() alternative as this fails for Respawn/Teleport + local CountAliveActive = 0 + for index, data in pairs(DCSgroup:getUnits()) do + if data:isExist() and data:isActive() then + CountAliveActive = CountAliveActive + 1 + end + end + + if DCSgroup:getInitialSize() == DCSgroup:getSize() then + + local groupname, group = self:AddInDatabase(Event) + + -- group:CountAliveUnits() alternative + if group and CountAliveActive == DCSgroup:getInitialSize() then + if group and self:IsIncludeObject(group) then + self:Add(groupname, group) + end + end end - end end end @@ -7892,9 +8110,9 @@ do -- SET_OPSGROUP -- @param #SET_OPSGROUP self -- @param Core.Event#EVENTDATA Event function SET_OPSGROUP:_EventOnDeadOrCrash( Event ) - self:F( { Event } ) + --self:F( { Event } ) - if Event.IniDCSUnit then + if Event.IniDCSGroup 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. @@ -8018,7 +8236,12 @@ do -- SET_OPSGROUP MGroupInclude = MGroupInclude and MGroupPrefix end - + + if self.Filter.Functions and MGroupInclude then + local MClientFunc = self:_EvalFilterFunctions(MGroup) + MGroupInclude = MGroupInclude and MClientFunc + end + return MGroupInclude end @@ -8091,7 +8314,7 @@ do -- SET_SCENERY if ZoneSet then for _,_zone in pairs(ZoneSet.Set) do - self:T("Zone type handed: "..tostring(_zone.ClassName)) + --self:T(("Zone type handed: "..tostring(_zone.ClassName)) table.insert(zonenames,_zone:GetName()) end self:AddSceneryByName(zonenames) @@ -8118,7 +8341,7 @@ do -- SET_SCENERY -- @param Wrapper.Scenery#SCENERY AddScenery A single SCENERY object. -- @return #SET_SCENERY self function SET_SCENERY:AddScenery( AddScenery ) - self:F2( AddScenery:GetName() ) + --self:F2( AddScenery:GetName() ) self:Add( AddScenery:GetName(), AddScenery ) @@ -8134,7 +8357,7 @@ do -- SET_SCENERY local AddSceneryNamesArray = ( type( AddSceneryNames ) == "table" ) and AddSceneryNames or { AddSceneryNames } - self:T( AddSceneryNamesArray ) + --self:T(( AddSceneryNamesArray ) for AddSceneryID, AddSceneryName in pairs( AddSceneryNamesArray ) do self:Add( AddSceneryName, SCENERY:FindByZoneName( AddSceneryName ) ) end @@ -8185,7 +8408,7 @@ do -- SET_SCENERY end for _,Zone in pairs( zones ) do local zonename = Zone:GetName() - self:T(zonename) + --self:T((zonename) self.Filter.Zones[zonename] = Zone end return self @@ -8204,7 +8427,7 @@ do -- SET_SCENERY Prefixes = { Prefixes } end for PrefixID, Prefix in pairs( Prefixes ) do - --self:T(Prefix) + --self:T((Prefix) self.Filter.SceneryPrefixes[Prefix] = Prefix end return self @@ -8251,7 +8474,7 @@ do -- SET_SCENERY -- @return #table Table of alive objects -- @return Core.Set#SET_SCENERY SET of alive objects function SET_SCENERY:GetAliveSet() - self:F2() + --self:F2() local AliveSet = SET_SCENERY:New() @@ -8273,7 +8496,7 @@ do -- SET_SCENERY -- @param #function IteratorFunction The function that will be called when there is an alive SCENERY in the SET_SCENERY. The function needs to accept a SCENERY parameter. -- @return #SET_SCENERY self function SET_SCENERY:ForEachScenery( IteratorFunction, ... ) - self:F2( arg ) + --self:F2( arg ) self:ForEach( IteratorFunction, arg, self:GetSet() ) return self end @@ -8282,14 +8505,17 @@ do -- SET_SCENERY -- @param #SET_SCENERY self -- @return Core.Point#COORDINATE The center coordinate of all the objects in the set. function SET_SCENERY:GetCoordinate() - + --[[ local Coordinate = COORDINATE:New({0,0,0}) - + local Item = self:GetRandomSurely() if Item then Coordinate:GetCoordinate() end + --]] + + local Coordinate = self:GetFirst():GetCoordinate() local x1 = Coordinate.x local x2 = Coordinate.x @@ -8316,7 +8542,7 @@ do -- SET_SCENERY Coordinate.y = ( y2 - y1 ) / 2 + y1 Coordinate.z = ( z2 - z1 ) / 2 + z1 - self:F( { Coordinate = Coordinate } ) + --self:F( { Coordinate = Coordinate } ) return Coordinate end @@ -8326,7 +8552,7 @@ do -- SET_SCENERY -- @param Wrapper.Scenery#SCENERY MScenery -- @return #SET_SCENERY self function SET_SCENERY:IsIncludeObject( MScenery ) - self:T( MScenery.SceneryName ) + --self:T(( MScenery.SceneryName ) local MSceneryInclude = true @@ -8337,24 +8563,24 @@ do -- SET_SCENERY if self.Filter.Prefixes then local MSceneryPrefix = false for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do - self:T( { "Prefix:", string.find( MSceneryName, ZonePrefix, 1 ), ZonePrefix } ) + --self:T(( { "Prefix:", string.find( MSceneryName, ZonePrefix, 1 ), ZonePrefix } ) if string.find( MSceneryName, ZonePrefix, 1 ) then MSceneryPrefix = true end end - self:T( { "Evaluated Prefix", MSceneryPrefix } ) + --self:T(( { "Evaluated Prefix", MSceneryPrefix } ) MSceneryInclude = MSceneryInclude and MSceneryPrefix end if self.Filter.Zones then local MSceneryZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - --self:T( "Zone:", ZoneName ) + --self:T(( "Zone:", ZoneName ) local coord = MScenery:GetCoordinate() if coord and Zone:IsCoordinateInZone(coord) then MSceneryZone = true end - self:T( { "Evaluated Zone", MSceneryZone } ) + --self:T(( { "Evaluated Zone", MSceneryZone } ) end MSceneryInclude = MSceneryInclude and MSceneryZone end @@ -8364,17 +8590,22 @@ do -- SET_SCENERY local MSceneryRole = false local Role = MScenery:GetProperty("ROLE") or "none" for ZoneRoleId, ZoneRole in pairs( self.Filter.SceneryRoles ) do - self:T( { "Role:", ZoneRole, Role } ) + --self:T(( { "Role:", ZoneRole, Role } ) if ZoneRole == Role then MSceneryRole = true end end - self:T( { "Evaluated Role ", MSceneryRole } ) + --self:T(( { "Evaluated Role ", MSceneryRole } ) MSceneryInclude = MSceneryInclude and MSceneryRole end end - - self:T2( MSceneryInclude ) + + if self.Filter.Functions and MSceneryInclude then + local MClientFunc = self:_EvalFilterFunctions(MScenery) + MSceneryInclude = MSceneryInclude and MClientFunc + end + + --self:T(2( MSceneryInclude ) return MSceneryInclude end @@ -8384,7 +8615,7 @@ do -- SET_SCENERY function SET_SCENERY:FilterOnce() for ObjectName, Object in pairs( self:GetSet() ) do - self:T(ObjectName) + --self:T((ObjectName) if self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) else @@ -8433,9 +8664,554 @@ do -- SET_SCENERY function SET_SCENERY:GetRelativeLife() local life = self:GetLife() local life0 = self:GetLife0() - self:T2(string.format("Set Lifepoints: %d life0 | %d life",life0,life)) + --self:T(2(string.format("Set Lifepoints: %d life0 | %d life",life0,life)) local rlife = math.floor((life / life0) * 100) return rlife end end + +-- TODO SET_DYNAMICCARGO + +do -- SET_DYNAMICCARGO + + --- + -- @type SET_DYNAMICCARGO + -- @field #table Filter Table of filters. + -- @field #table Set Table of objects. + -- @field #table Index Table of indices. + -- @field #table List Unused table. + -- @field Core.Scheduler#SCHEDULER CallScheduler. + -- @field #SET_DYNAMICCARGO.Filters Filter Filters. + -- @field #number ZoneTimerInterval. + -- @field Core.Timer#TIMER ZoneTimer Timer for active filtering of zones. + -- @extends Core.Set#SET_BASE + + --- + -- @type SET_DYNAMICCARGO.Filters + -- @field #string Coalitions + -- @field #string Types + -- @field #string Countries + -- @field #string StaticPrefixes + -- @field #string Zones + + --- The @{Core.Set#SET_DYNAMICCARGO} class defines the functions that define a collection of objects form @{Wrapper.DynamicCargo#DYNAMICCARGO}. + -- A SET provides iterators to iterate the SET. + --- Mission designers can use the SET_DYNAMICCARGO class to build sets of cargos belonging to certain: + -- + -- * Coalitions + -- * Categories + -- * Countries + -- * Static types + -- * Starting with certain prefix strings. + -- * Etc. + -- + -- ## SET_DYNAMICCARGO constructor + -- + -- Create a new SET_DYNAMICCARGO object with the @{#SET_DYNAMICCARGO.New} method: + -- + -- * @{#SET_DYNAMICCARGO.New}: Creates a new SET_DYNAMICCARGO object. + -- + -- ## SET_DYNAMICCARGO filter criteria + -- + -- You can set filter criteria to define the set of objects within the SET_DYNAMICCARGO. + -- Filter criteria are defined by: + -- + -- * @{#SET_DYNAMICCARGO.FilterCoalitions}: Builds the SET_DYNAMICCARGO with the objects belonging to the coalition(s). + -- * @{#SET_DYNAMICCARGO.FilterTypes}: Builds the SET_DYNAMICCARGO with the cargos belonging to the statiy type name(s). + -- * @{#SET_DYNAMICCARGO.FilterCountries}: Builds the SET_DYNAMICCARGO with the objects belonging to the country(ies). + -- * @{#SET_DYNAMICCARGO.FilterNamePatterns}, @{#SET_DYNAMICCARGO.FilterPrefixes}: Builds the SET_DYNAMICCARGO with the cargo containing the same string(s) in their name. **Attention!** LUA regular expression apply here, so special characters in names like minus, dot, hash (#) etc might lead to unexpected results. + -- Have a read through here to understand the application of regular expressions: [LUA regular expressions](https://riptutorial.com/lua/example/20315/lua-pattern-matching) + -- * @{#SET_DYNAMICCARGO.FilterZones}: Builds the SET_DYNAMICCARGO with the cargo within a @{Core.Zone#ZONE}. + -- * @{#SET_DYNAMICCARGO.FilterFunction}: Builds the SET_DYNAMICCARGO with a custom condition. + -- * @{#SET_DYNAMICCARGO.FilterCurrentOwner}: Builds the SET_DYNAMICCARGO with a specific owner name. + -- * @{#SET_DYNAMICCARGO.FilterIsLoaded}: Builds the SET_DYNAMICCARGO which is in state LOADED. + -- * @{#SET_DYNAMICCARGO.FilterIsNew}: Builds the SET_DYNAMICCARGO with is in state NEW. + -- * @{#SET_DYNAMICCARGO.FilterIsUnloaded}: Builds the SET_DYNAMICCARGO with is in state UNLOADED. + -- + -- Once the filter criteria have been set for the SET\_DYNAMICCARGO, you can start and stop filtering using: + -- + -- * @{#SET_DYNAMICCARGO.FilterStart}: Starts the continous filtering of the objects within the SET_DYNAMICCARGO. + -- * @{#SET_DYNAMICCARGO.FilterStop}: Stops the continous filtering of the objects within the SET_DYNAMICCARGO. + -- * @{#SET_DYNAMICCARGO.FilterOnce}: Filters once for the objects within the SET_DYNAMICCARGO. + -- + -- ## SET_DYNAMICCARGO iterators + -- + -- Once the filters have been defined and the SET\_DYNAMICCARGO has been built, you can iterate the SET\_DYNAMICCARGO with the available iterator methods. + -- The iterator methods will walk the SET\_DYNAMICCARGO set, and call for each element within the set a function that you provide. + -- The following iterator methods are currently available within the SET\_DYNAMICCARGO: + -- + -- * @{#SET_DYNAMICCARGO.ForEach}: Calls a function for each alive dynamic cargo it finds within the SET\_DYNAMICCARGO. + -- + -- ## SET_DYNAMICCARGO atomic methods + -- + -- Various methods exist for a SET_DYNAMICCARGO to perform actions or calculations and retrieve results from the SET\_DYNAMICCARGO: + -- + -- * @{#SET_DYNAMICCARGO.GetOwnerClientObjects}(): Retrieve the type names of the @{Wrapper.Static}s in the SET, delimited by a comma. + -- * @{#SET_DYNAMICCARGO.GetOwnerNames}(): Retrieve the type names of the @{Wrapper.Static}s in the SET, delimited by a comma. + -- * @{#SET_DYNAMICCARGO.GetStorageObjects}(): Retrieve the type names of the @{Wrapper.Static}s in the SET, delimited by a comma. + -- + -- === + -- @field #SET_DYNAMICCARGO SET_DYNAMICCARGO + SET_DYNAMICCARGO = { + ClassName = "SET_DYNAMICCARGO", + Filter = {}, + Set = {}, + List = {}, + Index = {}, + Database = nil, + CallScheduler = nil, + Filter = { + Coalitions = nil, + Types = nil, + Countries = nil, + StaticPrefixes = nil, + Zones = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + } + }, + ZoneTimerInterval = 20, + ZoneTimer = nil, + } + + --- Creates a new SET_DYNAMICCARGO object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO + -- @usage + -- -- Define a new SET_DYNAMICCARGO Object. This DBObject will contain a reference to all alive Statics. + -- DBObject = SET_DYNAMICCARGO:New() + function SET_DYNAMICCARGO:New() + + --- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.DYNAMICCARGO ) ) -- Core.Set#SET_DYNAMICCARGO + + return self + end + + --- + -- @param #SET_DYNAMICCARGO self + -- @param Wrapper.DynamicCargo#DYNAMICCARGO DCargo + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:IsIncludeObject( DCargo ) + --self:F2( DCargo ) + local DCargoInclude = true + + if self.Filter.Coalitions then + local DCargoCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + --self:T2( { "Coalition:", DCargo:GetCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == DCargo:GetCoalition() then + DCargoCoalition = true + end + end + DCargoInclude = DCargoInclude and DCargoCoalition + end + + if self.Filter.Types then + local DCargoType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + --self:T2( { "Type:", DCargo:GetTypeName(), TypeName } ) + if TypeName == DCargo:GetTypeName() then + DCargoType = true + end + end + DCargoInclude = DCargoInclude and DCargoType + end + + if self.Filter.Countries then + local DCargoCountry = false + for CountryID, CountryName in pairs( self.Filter.Countries ) do + --self:T2( { "Country:", DCargo:GetCountry(), CountryName } ) + if country.id[CountryName] == DCargo:GetCountry() then + DCargoCountry = true + end + end + DCargoInclude = DCargoInclude and DCargoCountry + end + + if self.Filter.StaticPrefixes then + local DCargoPrefix = false + for StaticPrefixId, StaticPrefix in pairs( self.Filter.StaticPrefixes ) do + --self:T2( { "Prefix:", string.find( DCargo:GetName(), StaticPrefix, 1 ), StaticPrefix } ) + if string.find( DCargo:GetName(), StaticPrefix, 1 ) then + DCargoPrefix = true + end + end + DCargoInclude = DCargoInclude and DCargoPrefix + end + + if self.Filter.Zones then + local DCargoZone = false + for ZoneName, Zone in pairs( self.Filter.Zones ) do + --self:T2( "In zone: "..ZoneName ) + if DCargo and DCargo:IsInZone(Zone) then + DCargoZone = true + end + end + DCargoInclude = DCargoInclude and DCargoZone + end + + if self.Filter.Functions and DCargoInclude then + local MClientFunc = self:_EvalFilterFunctions(DCargo) + DCargoInclude = DCargoInclude and MClientFunc + end + + --self:T2( DCargoInclude ) + return DCargoInclude + end + + --- Builds a set of dynamic cargo of defined coalitions. + -- Possible current coalitions are red, blue and neutral. + -- @param #SET_DYNAMICCARGO self + -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCoalitions( Coalitions ) + if not self.Filter.Coalitions then + self.Filter.Coalitions = {} + end + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } + end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self + end + + --- Builds a set of dynamic cargo of defined dynamic cargo type names. + -- @param #SET_DYNAMICCARGO self + -- @param #string Types Can take those type name strings known within DCS world. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self + end + + --- [User] Add a custom condition function. + -- @function [parent=#SET_DYNAMICCARGO] FilterFunction + -- @param #SET_DYNAMICCARGO self + -- @param #function ConditionFunction If this function returns `true`, the object is added to the SET. The function needs to take a DYNAMICCARGO object as first argument. + -- @param ... Condition function arguments if any. + -- @return #SET_DYNAMICCARGO self + -- @usage + -- -- Image you want to exclude a specific DYNAMICCARGO from a SET: + -- local cargoset = SET_DYNAMICCARGO:New():FilterCoalitions("blue"):FilterFunction( + -- -- The function needs to take a DYNAMICCARGO object as first - and in this case, only - argument. + -- function(dynamiccargo) + -- local isinclude = true + -- if dynamiccargo:GetName() == "Exclude Me" then isinclude = false end + -- return isinclude + -- end + -- ):FilterOnce() + -- BASE:I(cargoset:Flush()) + + --- Builds a set of dynamic cargo of defined countries. + -- Possible current countries are those known within DCS world. + -- @param #SET_DYNAMICCARGO self + -- @param #string Countries Can take those country strings known within DCS world. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self + end + + --- Builds a set of DYNAMICCARGOs that contain the given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all names that **contain** the string. LUA Regex applies. + -- @param #SET_DYNAMICCARGO self + -- @param #string Prefixes The string pattern(s) that need to be contained in the dynamic cargo name. Can also be passed as a `#table` of strings. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterPrefixes( Prefixes ) + if not self.Filter.StaticPrefixes then + self.Filter.StaticPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.StaticPrefixes[Prefix] = Prefix + end + return self + end + + --- Builds a set of DYNAMICCARGOs that contain the given string in their name. + -- **Attention!** LUA Regex applies! + -- @param #SET_DYNAMICCARGO self + -- @param #string Patterns The string pattern(s) that need to be contained in the dynamic cargo name. Can also be passed as a `#table` of strings. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterNamePattern( Patterns ) + return self:FilterPrefixes(Patterns) + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.LOADED (i.e. is on board of a Chinook). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsLoaded() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.LOADED then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.LOADED (i.e. was on board of a Chinook previously and is now unloaded). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsUnloaded() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.UNLOADED then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are in state DYNAMICCARGO.State.NEW (i.e. new and never loaded into a Chinook). + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterIsNew() + self:FilterFunction( + function(cargo) + if cargo and cargo.CargoState and cargo.CargoState == DYNAMICCARGO.State.NEW then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of DYNAMICCARGOs that are owned at the moment by this player name. + -- @param #SET_DYNAMICCARGO self + -- @param #string PlayerName + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCurrentOwner(PlayerName) + self:FilterFunction( + function(cargo) + if cargo and cargo.Owner and string.find(cargo.Owner,PlayerName,1,true) then + return true + else + return false + end + end + ) + return self + end + + --- Builds a set of dynamic cargo in zones. + -- @param #SET_DYNAMICCARGO self + -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterZones( Zones ) + if not self.Filter.Zones then + self.Filter.Zones = {} + end + local zones = {} + if Zones.ClassName and Zones.ClassName == "SET_ZONE" then + zones = Zones.Set + elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then + self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") + return self + else + zones = Zones + end + for _,Zone in pairs( zones ) do + local zonename = Zone:GetName() + self.Filter.Zones[zonename] = Zone + end + return self + end + + --- Starts the filtering. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterStart() + if _DATABASE then + self:HandleEvent( EVENTS.NewDynamicCargo, self._EventHandlerDCAdd ) + self:HandleEvent( EVENTS.DynamicCargoRemoved, self._EventHandlerDCRemove ) + if self.Filter.Zones then + self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) + local timing = self.ZoneTimerInterval or 30 + self.ZoneTimer:Start(timing,timing) + end + self:_FilterStart() + end + + return self + end + + --- Stops the filtering. + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterStop() + if _DATABASE then + self:UnHandleEvent( EVENTS.NewDynamicCargo) + self:UnHandleEvent( EVENTS.DynamicCargoRemoved ) + if self.ZoneTimer and self.ZoneTimer:IsRunning() then + self.ZoneTimer:Stop() + end + end + + return self + end + + --- [Internal] Private function for use of continous zone filter + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:_ContinousZoneFilter() + local Database = _DATABASE.DYNAMICCARGO + for ObjectName, Object in pairs( Database ) do + if self:IsIncludeObject( Object ) and self:IsNotInSet(Object) then + self:Add( ObjectName, Object ) + elseif (not self:IsIncludeObject( Object )) and self:IsInSet(Object) then + self:Remove(ObjectName) + end + end + + return self + end + + --- Handles the events for the Set. + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + function SET_DYNAMICCARGO:_EventHandlerDCAdd( Event ) + if Event.IniDynamicCargo and Event.IniDynamicCargoName then + if not _DATABASE.DYNAMICCARGO[Event.IniDynamicCargoName] then + _DATABASE:AddDynamicCargo( Event.IniDynamicCargoName ) + end + local ObjectName, Object = self:FindInDatabase( Event ) + if Object and self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + end + end + + return self + end + + --- Handles the remove event for dynamic cargo set. + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + function SET_DYNAMICCARGO:_EventHandlerDCRemove( Event ) + if Event.IniDCSUnitName then + local ObjectName, Object = self:FindInDatabase( Event ) + if ObjectName then + self:Remove( ObjectName ) + end + end + + return self + 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_DYNAMICCARGO event or vise versa! + -- @param #SET_DYNAMICCARGO self + -- @param Core.Event#EVENTDATA Event + -- @return #string The name of the DYNAMICCARGO + -- @return Wrapper.DynamicCargo#DYNAMICCARGO The DYNAMICCARGO object + function SET_DYNAMICCARGO:FindInDatabase( Event ) + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] + end + + --- Set filter timer interval for FilterZones if using active filtering with FilterStart(). + -- @param #SET_DYNAMICCARGO self + -- @param #number Seconds Seconds between check intervals, defaults to 30. **Caution** - do not be too agressive with timing! Objects are usually not moving fast enough + -- to warrant a check of below 10 seconds. + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterZoneTimer(Seconds) + self.ZoneTimerInterval = Seconds or 30 + return self + end + + --- This filter is N/A for SET_DYNAMICCARGO + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterDeads() + return self + end + + --- This filter is N/A for SET_DYNAMICCARGO + -- @param #SET_DYNAMICCARGO self + -- @return #SET_DYNAMICCARGO self + function SET_DYNAMICCARGO:FilterCrashes() + return self + end + + --- Returns a list of current owners (playernames) indexed by playername from the SET. + -- @param #SET_DYNAMICCARGO self + -- @return #list<#string> Ownerlist + function SET_DYNAMICCARGO:GetOwnerNames() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.Owner then + table.insert(owners, cargo.Owner, cargo.Owner) + end + end + ) + return owners + end + + --- Returns a list of @{Wrapper.Storage#STORAGE} objects from the SET indexed by cargo name. + -- @param #SET_DYNAMICCARGO self + -- @return #list Storagelist + function SET_DYNAMICCARGO:GetStorageObjects() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.warehouse then + table.insert(owners, cargo.StaticName, cargo.warehouse) + end + end + ) + return owners + end + + --- Returns a list of current owners (Wrapper.Client#CLIENT objects) indexed by playername from the SET. + -- @param #SET_DYNAMICCARGO self + -- @return #list<#string> Ownerlist + function SET_DYNAMICCARGO:GetOwnerClientObjects() + local owners = {} + self:ForEach( + function(cargo) + if cargo and cargo.Owner then + local client = CLIENT:FindByPlayerName(cargo.Owner) + if client then + table.insert(owners, cargo.Owner, client) + end + end + end + ) + return owners + end + +end diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 017bfda9b..066fd1fd6 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -29,7 +29,9 @@ -- @module Core.Settings -- @image Core_Settings.JPG ---- @type SETTINGS + +--- +-- @type SETTINGS -- @extends Core.Base#BASE --- Takes care of various settings that influence the behavior of certain functionalities and classes within the MOOSE framework. @@ -218,7 +220,8 @@ SETTINGS = { SETTINGS.__Enum = {} ---- @type SETTINGS.__Enum.Era +--- +-- @type SETTINGS.__Enum.Era -- @field #number WWII -- @field #number Korea -- @field #number Cold @@ -737,8 +740,8 @@ do -- SETTINGS if _SETTINGS.ShowPlayerMenu == true then local PlayerGroup = PlayerUnit:GetGroup() - local PlayerName = PlayerUnit:GetPlayerName() - local PlayerNames = PlayerGroup:GetPlayerNames() + local PlayerName = PlayerUnit:GetPlayerName() or "None" + --local PlayerNames = PlayerGroup:GetPlayerNames() local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' ) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 75664cee9..c81aea59d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -199,6 +199,7 @@ -- -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. +-- * @{#SPAWN.StopRepeat}(): This method is used to stop the repeater. -- -- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only) -- @@ -317,7 +318,7 @@ SPAWN.Takeoff = { -- @usage local Plane = SPAWN:New( "Plane" ) -- Creates a new local variable that can initiate new planes with the name "Plane#ddd" using the template "Plane" as defined within the ME. function SPAWN:New( SpawnTemplatePrefix ) local self = BASE:Inherit( self, BASE:New() ) -- #SPAWN - self:F( { SpawnTemplatePrefix } ) + --self:F( { SpawnTemplatePrefix } ) local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) if TemplateGroup then @@ -373,7 +374,7 @@ end -- @usage local PlaneWithAlias = SPAWN:NewWithAlias( "Plane", "Bomber" ) -- Creates a new local variable that can instantiate new planes with the name "Bomber#ddd" using the template "Plane" as defined within the ME. function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) + --self:F( { SpawnTemplatePrefix, SpawnAliasPrefix } ) local TemplateGroup = GROUP:FindByName( SpawnTemplatePrefix ) if TemplateGroup then @@ -526,7 +527,7 @@ end -- function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix, NoMooseNamingPostfix ) local self = BASE:Inherit( self, BASE:New() ) - self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) + --self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) --if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then --BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" ) --return nil @@ -602,7 +603,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitLimit( 2, 24 ) -- function SPAWN:InitLimit( SpawnMaxUnitsAlive, SpawnMaxGroups ) - self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) + --self:F( { self.SpawnTemplatePrefix, SpawnMaxUnitsAlive, SpawnMaxGroups } ) self.SpawnInitLimit = true self.SpawnMaxUnitsAlive = SpawnMaxUnitsAlive -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time. @@ -623,7 +624,7 @@ end -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names. -- @return #SPAWN self function SPAWN:InitKeepUnitNames( KeepUnitNames ) - self:F() + --self:F() self.SpawnInitKeepUnitNames = false @@ -637,7 +638,7 @@ end -- @param #boolean LateActivated (optional) If true, the spawned groups are late activated. -- @return #SPAWN self function SPAWN:InitLateActivated( LateActivated ) - self:F() + --self:F() self.LateActivated = LateActivated or true @@ -651,7 +652,7 @@ end -- @param #number TerminalType (Optional) The terminal type. -- @return #SPAWN self function SPAWN:InitAirbase( AirbaseName, Takeoff, TerminalType ) - self:F() + --self:F() self.SpawnInitAirbase = AIRBASE:FindByName( AirbaseName ) @@ -679,7 +680,7 @@ end -- Spawn:InitHeading( 100, 150 ) -- function SPAWN:InitHeading( HeadingMin, HeadingMax ) - self:F() + --self:F() self.SpawnInitHeadingMin = HeadingMin self.SpawnInitHeadingMax = HeadingMax @@ -747,7 +748,7 @@ end -- -- @return #SPAWN self function SPAWN:InitCountry( Country ) - self:F() + --self:F() self.SpawnInitCountry = Country @@ -759,7 +760,7 @@ end -- @param #number Category Category id. -- @return #SPAWN self function SPAWN:InitCategory( Category ) - self:F() + --self:F() self.SpawnInitCategory = Category @@ -771,7 +772,7 @@ end -- @param #string Livery Livery name. Note that this is not necessarily the same name as displayed in the mission editor. -- @return #SPAWN self function SPAWN:InitLivery( Livery ) - self:F( { livery = Livery } ) + --self:F( { livery = Livery } ) self.SpawnInitLivery = Livery @@ -783,7 +784,7 @@ end -- @param #string Skill Skill, possible values "Average", "Good", "High", "Excellent" or "Random". -- @return #SPAWN self function SPAWN:InitSkill( Skill ) - self:F( { skill = Skill } ) + --self:F( { skill = Skill } ) if Skill:lower() == "average" then self.SpawnInitSkill = "Average" elseif Skill:lower() == "good" then @@ -804,7 +805,7 @@ end -- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique! -- @return #SPAWN self function SPAWN:InitSTN(Octal) - self:F( { Octal = Octal } ) + --self:F( { Octal = Octal } ) self.SpawnInitSTN = Octal or 77777 local num = UTILS.OctalToDecimal(Octal) if num == nil or num < 1 then @@ -822,7 +823,7 @@ end -- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique! -- @return #SPAWN self function SPAWN:InitSADL(Octal) - self:F( { Octal = Octal } ) + --self:F( { Octal = Octal } ) self.SpawnInitSADL = Octal or 7777 local num = UTILS.OctalToDecimal(Octal) if num == nil or num < 1 then @@ -840,7 +841,7 @@ end -- @param #number MPS The speed in MPS to use. -- @return #SPAWN self function SPAWN:InitSpeedMps(MPS) -self:F( { MPS = MPS } ) +--self:F( { MPS = MPS } ) if MPS == nil or tonumber(MPS)<0 then MPS=125 end @@ -853,7 +854,7 @@ end -- @param #number Knots The speed in knots to use. -- @return #SPAWN self function SPAWN:InitSpeedKnots(Knots) -self:F( { Knots = Knots } ) +--self:F( { Knots = Knots } ) if Knots == nil or tonumber(Knots)<0 then Knots=300 end @@ -866,7 +867,7 @@ end -- @param #number KPH The speed in KPH to use. -- @return #SPAWN self function SPAWN:InitSpeedKph(KPH) - self:F( { KPH = KPH } ) + --self:F( { KPH = KPH } ) if KPH == nil or tonumber(KPH)<0 then KPH=UTILS.KnotsToKmph(300) end @@ -880,7 +881,7 @@ end -- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @return #SPAWN self function SPAWN:InitRadioCommsOnOff( switch ) - self:F( { switch = switch } ) + --self:F( { switch = switch } ) self.SpawnInitRadio = switch or true return self end @@ -890,7 +891,7 @@ end -- @param #number frequency The frequency in MHz. -- @return #SPAWN self function SPAWN:InitRadioFrequency( frequency ) - self:F( { frequency = frequency } ) + --self:F( { frequency = frequency } ) self.SpawnInitFreq = frequency @@ -902,7 +903,7 @@ end -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #SPAWN self function SPAWN:InitRadioModulation( modulation ) - self:F( { modulation = modulation } ) + --self:F( { modulation = modulation } ) if modulation and modulation:lower() == "fm" then self.SpawnInitModu = radio.modulation.FM else @@ -947,7 +948,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 ) -- function SPAWN:InitRandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight ) - self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) + --self:F( { self.SpawnTemplatePrefix, SpawnStartPoint, SpawnEndPoint, SpawnRadius, SpawnHeight } ) self.SpawnRandomizeRoute = true self.SpawnRandomizeRouteStartPoint = SpawnStartPoint @@ -969,7 +970,7 @@ end -- @param DCS#Distance InnerRadius (optional) The inner radius in meters where the new group will NOT be spawned. -- @return #SPAWN function SPAWN:InitRandomizePosition( RandomizePosition, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) + --self:F( { self.SpawnTemplatePrefix, RandomizePosition, OuterRadius, InnerRadius } ) self.SpawnRandomizePosition = RandomizePosition or false self.SpawnRandomizePositionOuterRadius = OuterRadius or 0 @@ -995,7 +996,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeUnits( true, 500, 50 ) -- function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius ) - self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) + --self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } ) self.SpawnRandomizeUnits = RandomizeUnits or false self.SpawnOuterRadius = OuterRadius or 0 @@ -1020,7 +1021,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitRelativePositions(Positions) -- function SPAWN:InitSetUnitRelativePositions(Positions) - self:F({self.SpawnTemplatePrefix, Positions}) + --self:F({self.SpawnTemplatePrefix, Positions}) self.SpawnUnitsWithRelativePositions = true self.UnitsRelativePositions = Positions @@ -1040,7 +1041,7 @@ end -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitAbsolutePositions(Positions) -- function SPAWN:InitSetUnitAbsolutePositions(Positions) - self:F({self.SpawnTemplatePrefix, Positions}) + --self:F({self.SpawnTemplatePrefix, Positions}) self.SpawnUnitsWithAbsolutePositions = true self.UnitsAbsolutePositions = Positions @@ -1053,7 +1054,7 @@ end -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param #string SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be chosen when a new group will be spawned. +-- @param #list<#string> SpawnTemplatePrefixTable A table with the names of the groups defined within the mission editor, from which one will be chosen when a new group will be spawned. -- @return #SPAWN -- @usage -- @@ -1069,7 +1070,7 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 ) function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) - self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) + --self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } ) local temptable = {} for _,_temp in pairs(SpawnTemplatePrefixTable) do @@ -1080,7 +1081,7 @@ function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable ) self.SpawnRandomizeTemplate = true for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeTemplate( SpawnGroupID ) + self:_RandomizeTemplate( SpawnGroupID, RandomizePositionInZone ) end return self @@ -1092,6 +1093,7 @@ end -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self -- @param Core.Set#SET_GROUP SpawnTemplateSet A SET_GROUP object set, that contains the groups that are possible unit representatives of the group to be spawned. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN -- @usage -- @@ -1110,11 +1112,11 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 ) -- -function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) - self:F( { self.SpawnTemplatePrefix } ) +function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet,RandomizePositionInZone ) + --self:F( { self.SpawnTemplatePrefix } ) local setnames = SpawnTemplateSet:GetSetNames() - self:InitRandomizeTemplate(setnames) + self:InitRandomizeTemplate(setnames,RandomizePositionInZone) return self end @@ -1124,7 +1126,8 @@ end -- but they will all follow the same Template route and have the same prefix name. -- In other words, this method randomizes between a defined set of groups the template to be used for each new spawn of a group. -- @param #SPAWN self --- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. +-- @param #string SpawnTemplatePrefixes A string or a list of string that contains the prefixes of the groups that are possible unit representatives of the group to be spawned. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN -- @usage -- @@ -1140,12 +1143,12 @@ end -- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplatePrefixes( "US Tank Platoon Templates" ):InitRandomizeRoute( 3, 3, 2000 ) -- -function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes ) -- R2.3 - self:F( { self.SpawnTemplatePrefix } ) +function SPAWN:InitRandomizeTemplatePrefixes( SpawnTemplatePrefixes, RandomizePositionInZone ) -- R2.3 + --self:F( { self.SpawnTemplatePrefix } ) local SpawnTemplateSet = SET_GROUP:New():FilterPrefixes( SpawnTemplatePrefixes ):FilterOnce() - self:InitRandomizeTemplateSet( SpawnTemplateSet ) + self:InitRandomizeTemplateSet( SpawnTemplateSet, RandomizePositionInZone ) return self end @@ -1155,7 +1158,7 @@ end -- @param #number Grouping Indicates the maximum amount of units in the group. -- @return #SPAWN function SPAWN:InitGrouping( Grouping ) -- R2.2 - self:F( { self.SpawnTemplatePrefix, Grouping } ) + --self:F( { self.SpawnTemplatePrefix, Grouping } ) self.SpawnGrouping = Grouping @@ -1165,6 +1168,7 @@ end --- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types. -- @param #SPAWN self -- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects. +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN self -- @usage -- @@ -1177,8 +1181,8 @@ end -- :InitRandomizeZones( ZoneTable ) -- :SpawnScheduled( 5, .5 ) -- -function SPAWN:InitRandomizeZones( SpawnZoneTable ) - self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) +function SPAWN:InitRandomizeZones( SpawnZoneTable, RandomizePositionInZone ) + --self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } ) local temptable = {} for _,_temp in pairs(SpawnZoneTable) do @@ -1189,7 +1193,7 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable ) self.SpawnRandomizeZones = true for SpawnGroupID = 1, self.SpawnMaxGroups do - self:_RandomizeZones( SpawnGroupID ) + self:_RandomizeZones( SpawnGroupID, RandomizePositionInZone ) end return self @@ -1225,7 +1229,7 @@ end -- @param Core.Point#COORDINATE Coordinate The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionCoordinate(Coordinate) - self:T( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) + --self:T2( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} ) self:InitPositionVec2(Coordinate:GetVec2()) return self end @@ -1235,10 +1239,10 @@ end -- @param DCS#Vec2 Vec2 The position to spawn from -- @return #SPAWN self function SPAWN:InitPositionVec2(Vec2) - self:T( { self.SpawnTemplatePrefix, Vec2} ) + --self:T2( { self.SpawnTemplatePrefix, Vec2} ) self.SpawnInitPosition = Vec2 self.SpawnFromNewPosition = true - self:I("MaxGroups:"..self.SpawnMaxGroups) + --self:T2("MaxGroups:"..self.SpawnMaxGroups) for SpawnGroupID = 1, self.SpawnMaxGroups do self:_SetInitialPosition( SpawnGroupID ) end @@ -1263,7 +1267,7 @@ end -- :InitRepeatOnEngineShutDown() -- function SPAWN:InitRepeat() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) self.Repeat = true self.RepeatOnEngineShutDown = false @@ -1274,6 +1278,7 @@ end --- Respawn group after landing. -- @param #SPAWN self +-- @param #number WaitingTime Wait this many seconds before despawning the alive group after landing. Defaults to 3 . -- @return #SPAWN self -- @usage -- @@ -1281,15 +1286,16 @@ end -- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically. -- SpawnRU_SU34 = SPAWN:New( 'Su-34' ) -- :InitRandomizeRoute( 1, 1, 3000 ) --- :InitRepeatOnLanding() +-- :InitRepeatOnLanding(20) -- :Spawn() -- -function SPAWN:InitRepeatOnLanding() - self:F( { self.SpawnTemplatePrefix } ) +function SPAWN:InitRepeatOnLanding(WaitingTime) + --self:F( { self.SpawnTemplatePrefix } ) self:InitRepeat() self.RepeatOnEngineShutDown = false self.RepeatOnLanding = true + self.RepeatOnLandingTime = (WaitingTime and WaitingTime > 3) and WaitingTime or 3 return self end @@ -1307,7 +1313,7 @@ end -- :InitRepeatOnEngineShutDown() -- :Spawn() function SPAWN:InitRepeatOnEngineShutDown() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self:InitRepeat() self.RepeatOnEngineShutDown = true @@ -1327,13 +1333,13 @@ end -- Spawn_Helicopter:InitCleanUp( 20 ) -- CleanUp the spawning of the helicopters every 20 seconds when they become inactive. -- function SPAWN:InitCleanUp( SpawnCleanUpInterval ) - self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) + --self:F( { self.SpawnTemplatePrefix, SpawnCleanUpInterval } ) self.SpawnCleanUpInterval = SpawnCleanUpInterval self.SpawnCleanUpTimeStamps = {} local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup } ) self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 ) return self @@ -1356,7 +1362,7 @@ end -- :InitArray( 90, 10, 100, 50 ) -- function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) - self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) + --self:F( { self.SpawnTemplatePrefix, SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY } ) self.SpawnVisible = true -- When the first Spawn executes, all the Groups need to be made visible before start. @@ -1366,7 +1372,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) local SpawnYIndex = 0 for SpawnGroupID = 1, self.SpawnMaxGroups do - self:T( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) + --self:T2( { SpawnX, SpawnY, SpawnXIndex, SpawnYIndex } ) self.SpawnGroups[SpawnGroupID].Visible = true self.SpawnGroups[SpawnGroupID].Spawned = false @@ -1393,6 +1399,7 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._OnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._OnDeadOrCrash ) if self.Repeat then self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff ) self:HandleEvent( EVENTS.Land, self._OnLand ) @@ -1410,6 +1417,30 @@ function SPAWN:InitArray( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY ) return self end +--- Stop the SPAWN InitRepeat function (EVENT handler for takeoff, land and engine shutdown) +-- @param #SPAWN self +-- @return #SPAWN self +-- @usage +-- local spawn = SPAWN:New("Template Group") +-- :InitRepeatOnEngineShutDown() +-- local plane = spawn:Spawn() -- it is important that we keep the SPAWN object and do not overwrite it with the resulting GROUP object by just calling :Spawn() +-- +-- -- later on +-- spawn:StopRepeat() +function SPAWN:StopRepeat() + if self.Repeat then + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + end + if self.RepeatOnEngineShutDown then + self:UnHandleEvent(EVENTS.EngineShutdown) + end + self.Repeat = false + self.RepeatOnEngineShutDown = false + self.RepeatOnLanding = false + return self +end + do -- AI methods --- Turns the AI On or Off for the @{Wrapper.Group} when spawning. @@ -1473,9 +1504,10 @@ end -- Delay methods --- Hide the group on the map view (visible to game master slots!). -- @param #SPAWN self +-- @param #boolean OnOff Defaults to true -- @return #SPAWN The SPAWN object -function SPAWN:InitHiddenOnMap() - self.SpawnHiddenOnMap = true +function SPAWN:InitHiddenOnMap(OnOff) + self.SpawnHiddenOnMap = OnOff == false and false or true return self end @@ -1500,7 +1532,7 @@ end -- @param #SPAWN self -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:Spawn() - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, self.AliveUnits } ) if self.SpawnInitAirbase then return self:SpawnAtAirbase( self.SpawnInitAirbase, self.SpawnInitTakeoff, nil, self.SpawnInitTerminalType ) @@ -1516,7 +1548,7 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:ReSpawn( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) if not SpawnIndex then SpawnIndex = 1 @@ -1539,11 +1571,11 @@ function SPAWN:ReSpawn( SpawnIndex ) SpawnGroup:WayPointExecute( 1, 5 ) end - if SpawnGroup.ReSpawnFunction then + if SpawnGroup and SpawnGroup.ReSpawnFunction then SpawnGroup:ReSpawnFunction() end - SpawnGroup:ResetEvents() + if SpawnGroup then SpawnGroup:ResetEvents() end return SpawnGroup end @@ -1564,8 +1596,24 @@ end -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) - self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) - + --[[ + local set = SET_GROUP:New():FilterAlive():FilterPrefixes({self.SpawnTemplatePrefix, self.SpawnAliasPrefix}):FilterOnce() + local aliveunits = 0 + set:ForEachGroupAlive( + function(grp) + aliveunits = aliveunits + grp:CountAliveUnits() + end + ) + + if aliveunits ~= self.AliveUnits then + self.AliveUnits = aliveunits + --self:T2("***** self.AliveUnits accounting failure! Corrected! *****") + end + + set= nil + --]] + --self:T2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) + if self:_GetSpawnIndex( SpawnIndex ) then if self.SpawnFromNewPosition then @@ -1579,12 +1627,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate local SpawnZone = self.SpawnGroups[self.SpawnIndex].SpawnZone - self:T( SpawnTemplate.name ) + --self:T2( SpawnTemplate.name ) if SpawnTemplate then - local PointVec3 = POINT_VEC3:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) - self:T( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) + local PointVec3 = COORDINATE:New( SpawnTemplate.route.points[1].x, SpawnTemplate.route.points[1].alt, SpawnTemplate.route.points[1].y ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, PointVec3 } ) -- If RandomizePosition, then Randomize the formation in the zone band, keeping the template. if self.SpawnRandomizePosition then @@ -1596,7 +1644,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].x = SpawnTemplate.units[UnitID].x + (RandomVec2.x - CurrentX) SpawnTemplate.units[UnitID].y = SpawnTemplate.units[UnitID].y + (RandomVec2.y - CurrentY) - self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -1612,18 +1660,18 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) numTries = numTries + 1 inZone = SpawnZone:IsVec2InZone(RandomVec2) - --self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") - --self:I(SpawnZone) + --self:T2("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") + --self:T2(SpawnZone) end end if (not inZone) then - self:I("Could not place unit within zone and within radius!") + --self:T2("Could not place unit within zone and within radius!") RandomVec2 = SpawnZone:GetRandomVec2() end end SpawnTemplate.units[UnitID].x = RandomVec2.x SpawnTemplate.units[UnitID].y = RandomVec2.y - self:T( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -1779,7 +1827,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end if self.SpawnHiddenOnMap then - SpawnTemplate.hidden=true + SpawnTemplate.hidden=self.SpawnHiddenOnMap end -- Set country, coalition and category. @@ -1797,8 +1845,9 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) if not NoBirth then self:HandleEvent( EVENTS.Birth, self._OnBirth ) end - self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) + --self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash ) + self:HandleEvent( EVENTS.UnitLost, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._OnDeadOrCrash ) if self.Repeat then self:HandleEvent( EVENTS.Takeoff, self._OnTakeOff ) @@ -1843,6 +1892,7 @@ end --- Spawns new groups at varying time intervals. -- This is useful if you want to have continuity within your missions of certain (AI) groups to be present (alive) within your missions. +-- **WARNING** - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every split second! Be reasonable and stay at 15 seconds and above! -- @param #SPAWN self -- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups. -- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn. @@ -1860,11 +1910,19 @@ end -- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters. -- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 ) function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation, WithDelay ) - self:F( { SpawnTime, SpawnTimeVariation } ) + --self:F( { SpawnTime, SpawnTimeVariation } ) local SpawnTime = SpawnTime or 60 local SpawnTimeVariation = SpawnTimeVariation or 0.5 + -- Noob catch + if SpawnTime < 15 then + self:E("****SPAWN SCHEDULED****\nWARNING - Setting a very low SpawnTime heavily impacts your mission performance and CPU time, it is NOT useful to check the alive state of an object every "..tostring(SpawnTime).." seconds.\nSetting to 15 second intervals.\n*****") + SpawnTime = 15 + end + + if SpawnTimeVariation > 1 or SpawnTimeVariation < 0 then SpawnTimeVariation = 0.5 end + if SpawnTime ~= nil and SpawnTimeVariation ~= nil then local InitialDelay = 0 if WithDelay or self.DelayOnOff == true then @@ -1881,7 +1939,7 @@ end -- @param #SPAWN self -- @return #SPAWN function SPAWN:SpawnScheduleStart() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self.SpawnScheduler:Start() return self @@ -1891,7 +1949,7 @@ end -- @param #SPAWN self -- @return #SPAWN function SPAWN:SpawnScheduleStop() - self:F( { self.SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix } ) self.SpawnScheduler:Stop() return self @@ -1916,7 +1974,7 @@ end -- :SpawnScheduled( 300, 0.3 ) -- function SPAWN:OnSpawnGroup( SpawnCallBackFunction, ... ) - self:F( "OnSpawnGroup" ) + --self:F( "OnSpawnGroup" ) self.SpawnFunctionHook = SpawnCallBackFunction self.SpawnFunctionArguments = {} @@ -1975,12 +2033,10 @@ end -- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- -function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 - self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) +function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2( PointVec3 ) -- Set take off type. Default is hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot @@ -1990,39 +2046,24 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT EmergencyAirSpawn = true end - self:F( { SpawnIndex = self.SpawnIndex } ) - if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate - self:F( { SpawnTemplate = SpawnTemplate } ) - if SpawnTemplate then - -- Check if the aircraft with the specified SpawnIndex is already spawned. - -- If yes, ensure that the aircraft is spawned at the same aircraft spot. - - local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) - - self:F( { GroupAlive = GroupAlive } ) - - -- Debug output - self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) - -- Template group, unit and its attributes. - local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) - local TemplateUnit = TemplateGroup:GetUnit( 1 ) + local group = GROUP:FindByName( self.SpawnTemplatePrefix ) + local unit = group:GetUnit( 1 ) -- General category of spawned group. - local group = TemplateGroup local istransport = group:HasAttribute( "Transports" ) and group:HasAttribute( "Planes" ) local isawacs = group:HasAttribute( "AWACS" ) local isfighter = group:HasAttribute( "Fighters" ) or group:HasAttribute( "Interceptors" ) or group:HasAttribute( "Multirole fighters" ) or (group:HasAttribute( "Bombers" ) and not group:HasAttribute( "Strategic bombers" )) local isbomber = group:HasAttribute( "Strategic bombers" ) local istanker = group:HasAttribute( "Tankers" ) - local ishelo = TemplateUnit:HasAttribute( "Helicopters" ) + local ishelo = unit:HasAttribute( "Helicopters" ) -- Number of units in the group. With grouping this can actually differ from the template group size! local nunits = #SpawnTemplate.units @@ -2038,42 +2079,34 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Get airbase ID and category. local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() - self:F( { AirbaseCategory = AirbaseCategory } ) + --self:F( { AirbaseCategory = AirbaseCategory } ) - -- Set airdromeId. + -- Set airdrome ID. For helipads and ships we need to add the helipad ID and linked unit. + -- Note, it is important not to set the airdrome ID for at least ships, because spawn will happen at origin of the map if AirbaseCategory == Airbase.Category.SHIP then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID elseif AirbaseCategory == Airbase.Category.HELIPAD then SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID - elseif AirbaseCategory == Airbase.Category.AIRDROME then + else SpawnPoint.airdromeId = AirbaseID end -- Set waypoint type/action. - SpawnPoint.alt = 0 - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. - local spawnonship = false - local spawnonfarp = false - local spawnonrunway = false - local spawnonairport = false - if spawnonground then - if AirbaseCategory == Airbase.Category.SHIP then - spawnonship = true - elseif AirbaseCategory == Airbase.Category.HELIPAD then - spawnonfarp = true - elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnonairport = true - end - spawnonrunway = Takeoff == SPAWN.Takeoff.Runway + local autoparking=false + if SpawnAirbase.isAirdrome then + autoparking=false + else + autoparking=true end -- Array with parking spots coordinates. @@ -2089,8 +2122,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Set terminal type. local termtype = TerminalType - if spawnonrunway then - if spawnonship then + if Takeoff==SPAWN.Takeoff.Runway then + if SpawnAirbase.isShip then -- Looks like there are no runway spawn spots on the stennis! if ishelo then termtype = AIRBASE.TerminalType.HelicopterUsable @@ -2110,34 +2143,31 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local verysafe = false -- Number of free parking spots at the airbase. - if spawnonship or spawnonfarp or spawnonrunway then + if autoparking then -- These places work procedural and have some kind of build in queue ==> Less effort. - 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. + -- Parking data explicitly set by user as input parameter. (This was commented out for some unknown reason. But I need it this way.) 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, Parkingdata ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else @@ -2145,45 +2175,34 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if termtype == nil then if isbomber or istransport or istanker or isawacs then -- First we fill the potentially bigger spots. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) - spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) + --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + spots = SpawnAirbase:FindFreeParkingSpotForAircraft( group, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end end end - -- Debug: Get parking data. - --[[ - local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) - for _,_spot in pairs(parkingdata) do - self:T(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", - SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) - end - self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) - ]] - -- Set this to true if not enough spots are available for emergency air start. local _notenough = false -- Need to differentiate some cases again. - if spawnonship or spawnonfarp or spawnonrunway then + if autoparking then -- On free spot required in these cases. if nfree >= 1 then @@ -2201,7 +2220,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT _notenough = true end - elseif spawnonairport then + else if nfree >= nunits then @@ -2223,13 +2242,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT self:E( string.format( "WARNING: Group %s has no parking spots at %s ==> air start!", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Not enough parking spots at the airport ==> Spawn in air. - spawnonground = false - spawnonship = false - spawnonfarp = false - spawnonrunway = false + autoparking=false -- Set waypoint type/action to turning point. - SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point + SpawnPoint.type = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][1] -- type = Turning Point SpawnPoint.action = GROUPTEMPLATE.Takeoff[GROUP.Takeoff.Air][2] -- action = Turning Point -- Adjust altitude to be 500-1000 m above the airbase. @@ -2271,7 +2287,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.parked = true for UnitID = 1, nunits do - self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -2287,9 +2302,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if spawnonground then -- Ships and FARPS seem to have a build in queue. - if spawnonship or spawnonfarp or spawnonrunway then - - self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + if autoparking then -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2298,20 +2311,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) - -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - -- parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end else - self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) - -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX SpawnTemplate.units[UnitID].y = TY @@ -2325,11 +2333,6 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT if parkingindex[UnitID] then UnitTemplate.parking = parkingindex[UnitID] end - - -- Debug output. - self:T( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - self:T( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2349,14 +2352,15 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- When spawned in the air, we need to generate a Takeoff Event. if Takeoff == GROUP.Takeoff.Air then for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) + --SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() }, 5 ) --No need to create a new SCHEDULER instance every time! + self:ScheduleOnce(5, BASE.CreateEventTakeoff, {GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject()}) end end -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) - end + --if Takeoff ~= SPAWN.Takeoff.Runway and Takeoff ~= SPAWN.Takeoff.Air and spawnonairport then + -- SCHEDULER:New( nil, AIRBASE.CheckOnRunWay, { SpawnAirbase, GroupSpawned, 75, true }, 1.0 ) + --end return GroupSpawned end @@ -2372,7 +2376,7 @@ end -- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. -- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) - self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) + --self:F( { Airbase = Airbase, Spots = Spots, Takeoff = Takeoff } ) -- Ensure that Spots parameter is a table. if type( Spots ) ~= "table" then @@ -2407,10 +2411,10 @@ function SPAWN:SpawnAtParkingSpot( Airbase, Spots, Takeoff ) -- Get parking spot data. local spot = Airbase:GetParkingSpotData( TerminalID ) - self:T2( { spot = spot } ) + --self:T2( { spot = spot } ) if spot and spot.Free then - self:T( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) + --self:T2( string.format( "Adding parking spot ID=%d TermType=%d", spot.TerminalID, spot.TerminalType ) ) table.insert( Parkingdata, spot ) end @@ -2438,11 +2442,11 @@ end -- @return #nil Nothing is returned! function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) - self:F( { SpawnIndex = SpawnIndex, SpawnMaxGroups = self.SpawnMaxGroups } ) + --self:F( { SpawnIndex = SpawnIndex, SpawnMaxGroups = self.SpawnMaxGroups } ) -- Get position of airbase. local PointVec3 = SpawnAirbase:GetCoordinate() - self:T2( PointVec3 ) + --self:T2( PointVec3 ) -- Set take off type. Default is hot. local Takeoff = SPAWN.Takeoff.Cold @@ -2458,7 +2462,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) -- Debug output - self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) -- Template group, unit and its attributes. local TemplateGroup = GROUP:FindByName( self.SpawnTemplatePrefix ) @@ -2482,7 +2486,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Get airbase ID and category. local AirbaseID = SpawnAirbase:GetID() local AirbaseCategory = SpawnAirbase:GetAirbaseCategory() - self:F( { AirbaseCategory = AirbaseCategory } ) + --self:F( { AirbaseCategory = AirbaseCategory } ) -- Set airdromeId. if AirbaseCategory == Airbase.Category.SHIP then @@ -2502,7 +2506,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Check if we spawn on ground. local spawnonground = not (Takeoff == SPAWN.Takeoff.Air) - self:T( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) + --self:T2( { spawnonground = spawnonground, TOtype = Takeoff, TOair = Takeoff == SPAWN.Takeoff.Air } ) -- Check where we actually spawn if we spawn on ground. local spawnonship = false @@ -2544,7 +2548,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Number of free parking spots at the airbase. if spawnonship or spawnonfarp or spawnonrunway then -- These places work procedural and have some kind of build in queue ==> Less effort. - self:T( string.format( "Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( 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 ) --[[ @@ -2557,18 +2561,18 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex 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 ) ) + --self:T2( 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, Parkingdata ) nfree = #spots if nfree < nunits then -- Not enough helo ports. Let's try also other terminal types. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterUsable ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.HelicopterUsable, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- No terminal type specified. We try all spots except shelters. - self:T( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) + --self:T2( string.format( "Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), termtype ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2580,23 +2584,23 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- TODO: Some attributes are "Helicopters", "Bombers", "Transports", "Battleplanes". Need to check it out. if isbomber or istransport then -- First we fill the potentially bigger spots. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots if nfree < nunits then -- Now we try the smaller ones. - self:T( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) + --self:T2( string.format( "Transport/bomber group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.OpenMedOrBig ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.OpenMedOrBig, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else - self:T( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) + --self:T2( string.format( "Fighter group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.FighterAircraft ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, AIRBASE.TerminalType.FighterAircraft, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end else -- Terminal type explicitly given. - self:T( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) + --self:T2( string.format( "Plane group %s is at %s using terminal type %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), tostring( termtype ) ) ) spots = SpawnAirbase:FindFreeParkingSpotForAircraft( TemplateGroup, termtype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits, Parkingdata ) nfree = #spots end @@ -2606,12 +2610,12 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Debug: Get parking data. --[[ local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + --self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) for _,_spot in pairs(parkingdata) do - self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + --self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end - self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) + --self:T2(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) ]] -- Set this to true if not enough spots are available for emergency air start. @@ -2671,7 +2675,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex SpawnTemplate.parked = true for UnitID = 1, nunits do - self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:F( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -2689,7 +2693,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex -- Ships and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway then - self:T( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn on ship. We take only the position of the ship. SpawnTemplate.units[UnitID].x = PointVec3.x -- TX @@ -2698,7 +2702,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) + --self:T2( string.format( "Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID] ) ) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x @@ -2710,7 +2714,7 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex else - self:T( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) + --self:T2( string.format( "Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName() ) ) -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. SpawnTemplate.units[UnitID].x = TX @@ -2727,9 +2731,9 @@ function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex end -- Debug output. - self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) - self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) - self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( string.format( "Group %s unit number %d: Parking = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking ) ) ) + --self:T2( string.format( "Group %s unit number %d: Parking ID = %s", self.SpawnTemplatePrefix, UnitID, tostring( UnitTemplate.parking_id ) ) ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end end @@ -2801,7 +2805,7 @@ end -- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) -- function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 - self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) + --self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, 1 ) @@ -2824,10 +2828,10 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) - self:T2( PointVec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) + --self:T2( PointVec3 ) if SpawnIndex then else @@ -2840,7 +2844,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate then - self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) + --self:T2( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } ) local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil @@ -2852,7 +2856,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) -- Translate the position of the Group Template to the Vec3. for UnitID = 1, #SpawnTemplate.units do - -- self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + -- self:T2( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) local UnitTemplate = SpawnTemplate.units[UnitID] local SX = UnitTemplate.x or 0 local SY = UnitTemplate.y or 0 @@ -2865,7 +2869,7 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex ) if SpawnTemplate.CategoryID ~= Group.Category.SHIP then SpawnTemplate.units[UnitID].alt = Vec3.y or TemplateHeight end - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. SpawnTemplate.units[UnitID].y ) end SpawnTemplate.route.points[1].x = Vec3.x SpawnTemplate.route.points[1].y = Vec3.z @@ -2892,7 +2896,7 @@ end -- @param #number SpawnIndex (optional) The index which group to spawn within the given zone. -- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned. function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex ) end @@ -2902,7 +2906,7 @@ end -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec3 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 or #nil if nothing was spawned. -- @usage @@ -2913,7 +2917,7 @@ end -- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 ) -- function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex ) end @@ -2939,7 +2943,7 @@ end -- SpawnAirplanes:SpawnFromVec2( SpawnVec2, 2000, 4000 ) -- function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex, Vec2, MinHeight, MaxHeight, SpawnIndex } ) local Height = nil @@ -2950,12 +2954,12 @@ function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex ) return self:SpawnFromVec3( { x = Vec2.x, y = Height, z = Vec2.y }, SpawnIndex ) -- y can be nil. In this case, spawn on the ground for vehicles, and in the template altitude for air. end ---- Will spawn a group from a POINT_VEC2 in 3D space. +--- Will spawn a group from a COORDINATE in 3D space. -- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles. -- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. -- You can use the returned group to further define the route to be followed. -- @param #SPAWN self --- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 coordinates where to spawn the group. +-- @param Core.Point#COORDINATE PointVec2 The coordinates where to spawn the group. -- @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. @@ -2971,7 +2975,7 @@ end -- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 ) -- function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } ) return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) end @@ -2997,7 +3001,7 @@ end -- SpawnAirplanes:SpawnFromUnit( SpawnStatic, 2000, 4000 ) -- function SPAWN:SpawnFromUnit( HostUnit, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, HostUnit, MinHeight, MaxHeight, SpawnIndex } ) if HostUnit and HostUnit:IsAlive() ~= nil then -- and HostUnit:getUnit(1):inAir() == false then return self:SpawnFromVec2( HostUnit:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -3025,7 +3029,7 @@ end -- SpawnAirplanes:SpawnFromStatic( SpawnStatic, 2000, 4000 ) -- function SPAWN:SpawnFromStatic( HostStatic, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, HostStatic, MinHeight, MaxHeight, SpawnIndex } ) if HostStatic and HostStatic:IsAlive() then return self:SpawnFromVec2( HostStatic:GetVec2(), MinHeight, MaxHeight, SpawnIndex ) @@ -3065,7 +3069,7 @@ end -- SpawnAirplanes:SpawnInZone( SpawnZone, nil, 2000, 4000 ) -- function SPAWN:SpawnInZone( Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, Zone, RandomizeGroup, MinHeight, MaxHeight, SpawnIndex } ) if Zone then if RandomizeGroup then @@ -3115,7 +3119,7 @@ end -- @param #number SpawnIndex Is the number of the Group that is to be spawned. -- @return #string SpawnGroupName function SPAWN:SpawnGroupName( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex } ) local SpawnPrefix = self.SpawnTemplatePrefix if self.SpawnAliasPrefix then @@ -3124,10 +3128,10 @@ function SPAWN:SpawnGroupName( SpawnIndex ) if SpawnIndex then local SpawnName = string.format( '%s#%03d', SpawnPrefix, SpawnIndex ) - self:T( SpawnName ) + --self:T2( SpawnName ) return SpawnName else - self:T( SpawnPrefix ) + --self:T2( SpawnPrefix ) return SpawnPrefix end @@ -3147,7 +3151,7 @@ end -- end -- function SPAWN:GetFirstAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = 1, self.SpawnCount do local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -3174,7 +3178,7 @@ end -- end -- function SPAWN:GetNextAliveGroup( SpawnIndexStart ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndexStart } ) SpawnIndexStart = SpawnIndexStart + 1 for SpawnIndex = SpawnIndexStart, self.SpawnCount do @@ -3200,7 +3204,7 @@ end -- end -- function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) for SpawnIndex = self.SpawnCount, 1, -1 do -- Added local SpawnGroup = self:GetGroupFromIndex( SpawnIndex ) @@ -3221,7 +3225,7 @@ end -- @param #number SpawnIndex The index of the group to return. -- @return Wrapper.Group#GROUP self function SPAWN:GetGroupFromIndex( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) if not SpawnIndex then SpawnIndex = 1 @@ -3279,7 +3283,7 @@ end --- Get the index from a given group. -- The function will search the name of the group for a #, and will return the number behind the #-mark. function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) local Index = tonumber( IndexString ) @@ -3291,7 +3295,7 @@ end --- Return the last maximum index that can be used. function SPAWN:_GetLastIndex() - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) return self.SpawnMaxGroups end @@ -3299,7 +3303,7 @@ end --- Initalize the SpawnGroups collection. -- @param #SPAWN self function SPAWN:_InitializeSpawnGroups( SpawnIndex ) - self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) + --self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnIndex } ) if not self.SpawnGroups[SpawnIndex] then self.SpawnGroups[SpawnIndex] = {} @@ -3343,7 +3347,7 @@ end --- Gets the CountryID of the Group with the given SpawnPrefix function SPAWN:_GetGroupCountryID( SpawnPrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnPrefix } ) local TemplateGroup = Group.getByName( SpawnPrefix ) @@ -3361,7 +3365,7 @@ end -- @param #string SpawnTemplatePrefix -- @return @SPAWN self function SPAWN:_GetTemplate( SpawnTemplatePrefix ) - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } ) local SpawnTemplate = nil @@ -3370,7 +3374,7 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) end local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template - self:F( { Template = Template } ) + --self:F( { Template = Template } ) SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) @@ -3392,7 +3396,7 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 - self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) + --self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) -- if not self.SpawnTemplate then -- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix ) @@ -3423,7 +3427,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if self.SpawnGrouping then local UnitAmount = #SpawnTemplate.units - self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) + --self:F( { UnitAmount = UnitAmount, SpawnGrouping = self.SpawnGrouping } ) if UnitAmount > self.SpawnGrouping then for UnitID = self.SpawnGrouping + 1, UnitAmount do SpawnTemplate.units[UnitID] = nil @@ -3455,7 +3459,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if SpawnInitKeepUnitIFF == false then UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID ) - self:T( { UnitPrefix, Rest } ) + --self:T2( { UnitPrefix, Rest } ) --else --UnitPrefix=SpawnTemplate.units[UnitID].name end @@ -3649,7 +3653,7 @@ end -- @param #number SpawnIndex The index of the group to be spawned. -- @return #SPAWN function SPAWN:_RandomizeRoute( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeRoute, self.SpawnRandomizeRouteStartPoint, self.SpawnRandomizeRouteEndPoint, self.SpawnRandomizeRouteRadius } ) if self.SpawnRandomizeRoute then local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate @@ -3669,7 +3673,7 @@ function SPAWN:_RandomizeRoute( SpawnIndex ) SpawnTemplate.route.points[t].alt = nil end - self:T( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) + --self:T2( 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y ) end end @@ -3683,7 +3687,7 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_RandomizeTemplate( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeTemplate } ) if self.SpawnRandomizeTemplate then self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[math.random( 1, #self.SpawnTemplatePrefixTable )] @@ -3712,15 +3716,15 @@ end -- @param #number SpawnIndex -- @return #SPAWN self function SPAWN:_SetInitialPosition( SpawnIndex ) - self:T( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) + --self:T2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnFromNewPosition then - self:T( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) + --self:T2( "Preparing Spawn at Vec2 ", self.SpawnInitPosition ) local SpawnVec2 = self.SpawnInitPosition - self:T( { SpawnVec2 = SpawnVec2 } ) + --self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate @@ -3730,11 +3734,11 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0 SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0 - self:T( { Route = SpawnTemplate.route } ) + --self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3745,7 +3749,7 @@ function SPAWN:_SetInitialPosition( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.route.points[1].x = SpawnVec2.x @@ -3761,33 +3765,38 @@ end --- Private method that randomizes the @{Core.Zone}s where the Group will be spawned. -- @param #SPAWN self -- @param #number SpawnIndex +-- @param #boolean RandomizePositionInZone If nil or true, also the position inside the selected random zone will be randomized. Set to false to use the center of the zone. -- @return #SPAWN self -function SPAWN:_RandomizeZones( SpawnIndex ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) +function SPAWN:_RandomizeZones( SpawnIndex, RandomizePositionInZone) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } ) if self.SpawnRandomizeZones then local SpawnZone = nil -- Core.Zone#ZONE_BASE while not SpawnZone do - self:T( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) + --self:T2( { SpawnZoneTableCount = #self.SpawnZoneTable, self.SpawnZoneTable } ) local ZoneID = math.random( #self.SpawnZoneTable ) - self:T( ZoneID ) + --self:T2( ZoneID ) SpawnZone = self.SpawnZoneTable[ZoneID]:GetZoneMaybe() end - self:T( "Preparing Spawn in Zone", SpawnZone:GetName() ) + --self:T2( "Preparing Spawn in Zone", SpawnZone:GetName() ) - local SpawnVec2 = SpawnZone:GetRandomVec2() + local SpawnVec2 = SpawnZone:GetVec2() - self:T( { SpawnVec2 = SpawnVec2 } ) + if RandomizePositionInZone ~= false then + SpawnVec2 = SpawnZone:GetRandomVec2() + end + + --self:T2( { SpawnVec2 = SpawnVec2 } ) local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate self.SpawnGroups[SpawnIndex].SpawnZone = SpawnZone - self:T( { Route = SpawnTemplate.route } ) + --self:T2( { Route = SpawnTemplate.route } ) for UnitID = 1, #SpawnTemplate.units do local UnitTemplate = SpawnTemplate.units[UnitID] - self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) local SX = UnitTemplate.x local SY = UnitTemplate.y local BX = SpawnTemplate.route.points[1].x @@ -3798,7 +3807,7 @@ function SPAWN:_RandomizeZones( SpawnIndex ) UnitTemplate.y = TY -- TODO: Manage altitude based on landheight... -- SpawnTemplate.units[UnitID].alt = SpawnVec2: - self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) + --self:T2( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y ) end SpawnTemplate.x = SpawnVec2.x SpawnTemplate.y = SpawnVec2.y @@ -3811,7 +3820,7 @@ function SPAWN:_RandomizeZones( SpawnIndex ) end function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle ) - self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) + --self:F( { self.SpawnTemplatePrefix, SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, SpawnY, SpawnAngle } ) -- Translate local TranslatedX = SpawnX @@ -3853,11 +3862,11 @@ end -- @param #number SpawnIndex Spawn index. -- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) - self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) + --self:T2( { template=self.SpawnTemplatePrefix, SpawnIndex=SpawnIndex, SpawnMaxGroups=self.SpawnMaxGroups, SpawnMaxUnitsAlive=self.SpawnMaxUnitsAlive, AliveUnits=self.AliveUnits, TemplateUnits=#self.SpawnTemplate.units } ) if (self.SpawnMaxGroups == 0) or (SpawnIndex <= self.SpawnMaxGroups) then if (self.SpawnMaxUnitsAlive == 0) or (self.AliveUnits + #self.SpawnTemplate.units <= self.SpawnMaxUnitsAlive) or self.UnControlled == true then - self:F( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) + --self:T2( { SpawnCount = self.SpawnCount, SpawnIndex = SpawnIndex } ) if SpawnIndex and SpawnIndex >= self.SpawnCount + 1 then self.SpawnCount = self.SpawnCount + 1 SpawnIndex = self.SpawnCount @@ -3881,44 +3890,83 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnBirth( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) + --self:T2( { "Birth Event:", EventPrefix, self.SpawnTemplatePrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then self.AliveUnits = self.AliveUnits + 1 - self:T( "Alive Units: " .. self.AliveUnits ) + --self:T2( "Alive Units: " .. self.AliveUnits ) end end end end +--- +-- @param #SPAWN self +-- @return #number count +function SPAWN:_CountAliveUnits() + local count = 0 + --self:I("self.SpawnAliasPrefix="..tostring(self.SpawnAliasPrefix).." | self.SpawnTemplatePrefix="..tostring(self.SpawnTemplatePrefix)) + if self.SpawnAliasPrefix then + if not self.SpawnAliasPrefixEscaped then self.SpawnAliasPrefixEscaped = string.gsub(self.SpawnAliasPrefix,"[%p%s]",".") end + --self:I("self.SpawnAliasPrefixEscaped="..tostring(self.SpawnAliasPrefixEscaped)) + local SpawnAliasPrefix = self.SpawnAliasPrefixEscaped + local agroups = GROUP:FindAllByMatching(SpawnAliasPrefix) + for _,_grp in pairs(agroups) do + --self:I("Group Name = " .. _grp:GetName()) + local game = self:_GetPrefixFromGroupName(_grp.GroupName) + --self:I("Game = "..game) + --self:I("Count = ".._grp:CountAliveUnits()) + if game and game == self.SpawnAliasPrefix then + count = count + _grp:CountAliveUnits() + end + end + else + if not self.SpawnTemplatePrefixEscaped then self.SpawnTemplatePrefixEscaped = string.gsub(self.SpawnTemplatePrefix,"[%p%s]",".") end + local SpawnTemplatePrefix = self.SpawnTemplatePrefixEscaped + local groups = GROUP:FindAllByMatching(SpawnTemplatePrefix) + for _,_grp in pairs(groups) do + local game = self:_GetPrefixFromGroupName(_grp.GroupName) + if game and game == self.SpawnTemplatePrefix then + count = count + _grp:CountAliveUnits() + end + end + end + self.AliveUnits = count + return self +end + +--- -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnDeadOrCrash( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:I( "Dead or crash event ID "..tostring(EventData.id or 0)) + --self:I( "Dead or crash event for "..tostring(EventData.IniUnitName or "none") ) + + --if EventData.id == EVENTS.Dead then return end local unit=UNIT:FindByName(EventData.IniUnitName) + --local group=GROUP:FindByName(EventData.IniGroupName) if unit then local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Dead event: " .. EventPrefix } ) - - if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then - - self.AliveUnits = self.AliveUnits - 1 - - self:T( "Alive Units: " .. self.AliveUnits ) + --self:I(string.format("EventPrefix = %s | SpawnAliasPrefix = %s | Old AliveUnits = %d",EventPrefix or "",self.SpawnAliasPrefix or "",self.AliveUnits or 0)) + if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) and self.AliveUnits > 0 then + --self:I( { "Dead event: " .. EventPrefix } ) + --self.AliveUnits = self.AliveUnits - 1 + self:ScheduleOnce(1,self._CountAliveUnits,self) + --self.AliveUnits = self:_CountAliveUnits() + --self:I( "New Alive Units: " .. self.AliveUnits ) end - end end end @@ -3928,15 +3976,15 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnTakeOff( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "TakeOff event: " .. EventPrefix } ) + --self:T2( { "TakeOff event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then - self:T( "self.Landed = false" ) + --self:T2( "self.Landed = false" ) SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", false ) end end @@ -3948,23 +3996,23 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnLand( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "Land event: " .. EventPrefix } ) + --self:T2( { "Land event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- TODO: Check if this is the last unit of the group that lands. SpawnGroup:SetState( SpawnGroup, "Spawn_Landed", true ) if self.RepeatOnLanding then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + --self:T2( { "Landed:", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4.26368 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 -- Bug was initially only for engine shutdown event but after ED "fixed" it, it now happens on landing events. - SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, self.RepeatOnLandingTime or 3 ) end end end @@ -3977,19 +4025,19 @@ end -- @param #SPAWN self -- @param Core.Event#EVENTDATA EventData function SPAWN:_OnEngineShutDown( EventData ) - self:F( self.SpawnTemplatePrefix ) + --self:F( self.SpawnTemplatePrefix ) local SpawnGroup = EventData.IniGroup if SpawnGroup then local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup ) if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group! - self:T( { "EngineShutdown event: " .. EventPrefix } ) + --self:T2( { "EngineShutdown event: " .. EventPrefix } ) if EventPrefix == self.SpawnTemplatePrefix or (self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix) then -- todo: test if on the runway local Landed = SpawnGroup:GetState( SpawnGroup, "Spawn_Landed" ) if Landed and self.RepeatOnEngineShutDown then local SpawnGroupIndex = self:GetSpawnIndexFromGroup( SpawnGroup ) - self:T( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) + --self:T2( { "EngineShutDown: ", "ReSpawn:", SpawnGroup:GetName(), SpawnGroupIndex } ) -- self:ReSpawn( SpawnGroupIndex ) -- Delay respawn by three seconds due to DCS 2.5.4 OB bug https://github.com/FlightControl-Master/MOOSE/issues/1076 SCHEDULER:New( nil, self.ReSpawn, { self, SpawnGroupIndex }, 3 ) @@ -4015,10 +4063,10 @@ end -- @param #SPAWN self -- @return #boolean True = Continue Scheduler function SPAWN:_SpawnCleanUpScheduler() - self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) + --self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } ) local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup() - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) local IsHelo = false @@ -4035,7 +4083,7 @@ function SPAWN:_SpawnCleanUpScheduler() self.SpawnCleanUpTimeStamps[SpawnUnitName] = self.SpawnCleanUpTimeStamps[SpawnUnitName] or {} local Stamp = self.SpawnCleanUpTimeStamps[SpawnUnitName] - self:T( { SpawnUnitName, Stamp } ) + --self:T2( { SpawnUnitName, Stamp } ) if Stamp.Vec2 then if (SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1) or IsHelo then @@ -4043,8 +4091,9 @@ function SPAWN:_SpawnCleanUpScheduler() 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 ) + --self:T2( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) + --self:ReSpawn( SpawnCursor ) + SCHEDULER:New( nil, self.ReSpawn, { self, SpawnCursor }, 3 ) Stamp.Vec2 = nil Stamp.Time = nil end @@ -4071,7 +4120,7 @@ function SPAWN:_SpawnCleanUpScheduler() SpawnGroup, SpawnCursor = self:GetNextAliveGroup( SpawnCursor ) - self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) + --self:T2( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } ) end diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index f6a082ddf..f603450d7 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -105,7 +105,7 @@ -- -- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! --- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! +-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a COORDINATE coordinate. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! -- -- @field #SPAWNSTATIC SPAWNSTATIC @@ -189,6 +189,7 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) self.InitStaticCategory=StaticCategory self.CountryID=CountryID or country.id.USA self.SpawnTemplatePrefix=self.InitStaticType + self.TemplateStaticUnit = {} self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) self.InitStaticHeading=0 @@ -196,6 +197,61 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) return self end +--- (Internal/Cargo) Init the resource table for STATIC object that should be spawned containing storage objects. +-- NOTE that you have to init many other parameters as the resources. +-- @param #SPAWNSTATIC self +-- @param #number CombinedWeight The weight this cargo object should have (some have fixed weights!), defaults to 1kg. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:_InitResourceTable(CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self.TemplateStaticUnit.resourcePayload = { + ["weapons"] = {}, + ["aircrafts"] = {}, + ["gasoline"] = 0, + ["diesel"] = 0, + ["methanol_mixture"] = 0, + ["jet_fuel"] = 0, + } + end + self:InitCargo(true) + self:InitCargoMass(CombinedWeight or 1) + return self +end + +--- (User/Cargo) Add to resource table for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- @param #SPAWNSTATIC self +-- @param #string Type Type of cargo. Known types are: STORAGE.Type.WEAPONS, STORAGE.Type.LIQUIDS, STORAGE.Type.AIRCRAFT. Liquids are fuel. +-- @param #string Name Name of the cargo type. Liquids can be STORAGE.LiquidName.JETFUEL, STORAGE.LiquidName.GASOLINE, STORAGE.LiquidName.MW50 and STORAGE.LiquidName.DIESEL. The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. Aircraft go by their typename. +-- @param #number Amount of tons (liquids) or number (everything else) to add. +-- @param #number CombinedWeight Combined weight to be set to this static cargo object. NOTE - some static cargo objects have fixed weights! +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:AddCargoResource(Type,Name,Amount,CombinedWeight) + if not self.TemplateStaticUnit.resourcePayload then + self:_InitResourceTable(CombinedWeight) + end + if Type == STORAGE.Type.LIQUIDS and type(Name) == "string" then + self.TemplateStaticUnit.resourcePayload[Name] = Amount + else + self.TemplateStaticUnit.resourcePayload[Type] = { + [Name] = { + ["amount"] = Amount, + } + } + end + UTILS.PrintTableToLog(self.TemplateStaticUnit) + return self +end + +--- (User/Cargo) Resets resource table to zero for STATIC object that should be spawned containing storage objects. Inits the object table if necessary and sets it to be cargo for helicopters. +-- Handy if you spawn from cargo statics which have resources already set. +-- @param #SPAWNSTATIC self +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:ResetCargoResources() + self.TemplateStaticUnit.resourcePayload = nil + self:_InitResourceTable() + return self +end + --- Initialize heading of the spawned static. -- @param #SPAWNSTATIC self -- @param Core.Point#COORDINATE Coordinate Position where the static is spawned. @@ -317,6 +373,25 @@ function SPAWNSTATIC:InitLinkToUnit(Unit, OffsetX, OffsetY, OffsetAngle) return self end +--- Allows to place a CallFunction hook when a new static spawns. +-- The provided method will be called when a new group is spawned, including its given parameters. +-- The first parameter of the SpawnFunction is the @{Wrapper.Static#STATIC} that was spawned. +-- @param #SPAWNSTATIC self +-- @param #function SpawnCallBackFunction The function to be called when a group spawns. +-- @param SpawnFunctionArguments A random amount of arguments to be provided to the function when the group spawns. +-- @return #SPAWNSTATIC self +function SPAWNSTATIC:OnSpawnStatic( SpawnCallBackFunction, ... ) + self:F( "OnSpawnStatic" ) + + self.SpawnFunctionHook = SpawnCallBackFunction + self.SpawnFunctionArguments = {} + if arg then + self.SpawnFunctionArguments = arg + end + + return self +end + --- Spawn a new STATIC object. -- @param #SPAWNSTATIC self -- @param #number Heading (Optional) The heading of the static, which is a number in degrees from 0 to 360. Default is the heading of the template. @@ -336,9 +411,9 @@ function SPAWNSTATIC:Spawn(Heading, NewName) end ---- Creates a new @{Wrapper.Static} from a POINT_VEC2. +--- Creates a new @{Wrapper.Static} from a COORDINATE. -- @param #SPAWNSTATIC self --- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. +-- @param Core.Point#COORDINATE PointVec2 The 2D coordinate where to spawn the static. -- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. -- @param #string NewName (Optional) The name of the new static. -- @return Wrapper.Static#STATIC The static spawned. @@ -460,12 +535,6 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- Name of the spawned static. Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) - -- Add and register the new static. - local mystatic=_DATABASE:AddStatic(Template.name) - - -- Debug output. - self:T(Template) - -- Add static to the game. local Static=nil --DCS#StaticObject @@ -488,7 +557,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) - -- Currently DCS 2.8 does not trigger birth events if FAPRS are spawned! + -- Currently DCS 2.8 does not trigger birth events if FARPS are spawned! -- We create such an event. The airbase is registered in Core.Event local Event = { id = EVENTS.Birth, @@ -502,6 +571,28 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) self:T("Spawning Static") self:T2({Template=Template}) Static=coalition.addStaticObject(CountryID, Template) + + if Static then + self:T(string.format("Succesfully spawned static object \"%s\" ID=%d", Static:getName(), Static:getID())) + --[[ + local static=StaticObject.getByName(Static:getName()) + if static then + env.info(string.format("FF got static from StaticObject.getByName")) + else + env.error(string.format("FF error did NOT get static from StaticObject.getByName")) + end ]] + else + self:E(string.format("ERROR: DCS static object \"%s\" is nil!", tostring(Template.name))) + end + end + + -- Add and register the new static. + local mystatic=_DATABASE:AddStatic(Template.name) + + -- If there is a SpawnFunction hook defined, call it. + if self.SpawnFunctionHook then + -- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. + self:ScheduleOnce(0.3, self.SpawnFunctionHook, mystatic, unpack(self.SpawnFunctionArguments)) end return mystatic diff --git a/Moose Development/Moose/Core/UserFlag.lua b/Moose Development/Moose/Core/UserFlag.lua index 1c47e787f..94bff742a 100644 --- a/Moose Development/Moose/Core/UserFlag.lua +++ b/Moose Development/Moose/Core/UserFlag.lua @@ -18,7 +18,7 @@ do -- UserFlag - --- @type USERFLAG + -- @type USERFLAG -- @field #string ClassName Name of the class -- @field #string UserFlagName Name of the flag. -- @extends Core.Base#BASE diff --git a/Moose Development/Moose/Core/Velocity.lua b/Moose Development/Moose/Core/Velocity.lua index e71282208..1181271c7 100644 --- a/Moose Development/Moose/Core/Velocity.lua +++ b/Moose Development/Moose/Core/Velocity.lua @@ -20,7 +20,7 @@ do -- Velocity - --- @type VELOCITY + -- @type VELOCITY -- @extends Core.Base#BASE @@ -127,7 +127,7 @@ end do -- VELOCITY_POSITIONABLE - --- @type VELOCITY_POSITIONABLE + -- @type VELOCITY_POSITIONABLE -- @extends Core.Base#BASE diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index a809efe97..f4977e284 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -144,7 +144,7 @@ ZONE_BASE = { -- @return #ZONE_BASE self function ZONE_BASE:New( ZoneName ) local self = BASE:Inherit( self, FSM:New() ) - self:F( ZoneName ) + --self:F( ZoneName ) self.ZoneName = ZoneName @@ -157,7 +157,7 @@ end -- @param #ZONE_BASE self -- @return #string The name of the zone. function ZONE_BASE:GetName() - self:F2() + --self:F2() return self.ZoneName end @@ -167,7 +167,7 @@ end -- @param #string ZoneName The name of the zone. -- @return #ZONE_BASE function ZONE_BASE:SetName( ZoneName ) - self:F2() + --self:F2() self.ZoneName = ZoneName end @@ -177,7 +177,7 @@ end -- @param DCS#Vec2 Vec2 The Vec2 to test. -- @return #boolean true if the Vec2 is within the zone. function ZONE_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) return false end @@ -213,7 +213,7 @@ end --- Returns if a PointVec3 is within the zone. -- @param #ZONE_BASE self --- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test. +-- @param Core.Point#COORDINATE PointVec3 The PointVec3 to test. -- @return #boolean true if the PointVec3 is within the zone. function ZONE_BASE:IsPointVec3InZone( PointVec3 ) local InZone = self:IsPointVec2InZone( PointVec3 ) @@ -227,18 +227,18 @@ function ZONE_BASE:GetVec2() return nil end ---- Returns a @{Core.Point#POINT_VEC2} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. +-- @return Core.Point#COORDINATE The COORDINATE of the zone. function ZONE_BASE:GetPointVec2() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) local Vec2 = self:GetVec2() - local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) + local PointVec2 = COORDINATE:NewFromVec2( Vec2 ) - self:T2( { PointVec2 } ) + --self:T2( { PointVec2 } ) return PointVec2 end @@ -248,7 +248,7 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) Height = Height or 0 @@ -256,23 +256,23 @@ function ZONE_BASE:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y } - self:T2( { Vec3 } ) + --self:T2( { Vec3 } ) return Vec3 end ---- Returns a @{Core.Point#POINT_VEC3} of the zone. +--- Returns a @{Core.Point#COORDINATE} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. --- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. +-- @return Core.Point#COORDINATE The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) local Vec3 = self:GetVec3( Height ) - local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) + local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) - self:T2( { PointVec3 } ) + --self:T2( { PointVec3 } ) return PointVec3 end @@ -282,7 +282,7 @@ 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 ) @@ -344,16 +344,16 @@ function ZONE_BASE:GetRandomVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC3} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_BASE self --- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_BASE:GetRandomPointVec3() return nil end @@ -377,7 +377,7 @@ end --- Bound the zone boundaries with a tires. -- @param #ZONE_BASE self function ZONE_BASE:BoundZone() - self:F2() + --self:F2() end --- Set draw coalition of zone. @@ -524,7 +524,7 @@ end -- @param #ZONE_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) end @@ -533,7 +533,7 @@ end -- @param #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. -- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) - self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) + --self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) self.ZoneProbability = ZoneProbability or 1 return self @@ -543,7 +543,7 @@ end -- @param #ZONE_BASE self -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() - self:F2() + --self:F2() return self.ZoneProbability end @@ -574,7 +574,7 @@ end -- -- The result should be that Zone1 would be more probable selected than Zone2. -- function ZONE_BASE:GetZoneMaybe() - self:F2() + --self:F2() local Randomization = math.random() if Randomization <= self.ZoneProbability then @@ -619,10 +619,13 @@ function ZONE_BASE:Trigger(Objects) self:AddTransition("TriggerStopped","TriggerStart","TriggerRunning") self:AddTransition("*","EnteredZone","*") self:AddTransition("*","LeftZone","*") + self:AddTransition("*","ZoneEmpty","*") + self:AddTransition("*","ObjectDead","*") self:AddTransition("*","TriggerRunCheck","*") self:AddTransition("*","TriggerStop","TriggerStopped") self:TriggerStart() self.checkobjects = Objects + self.ObjectsInZone = false if UTILS.IsInstanceOf(Objects,"SET_BASE") then self.objectset = Objects.Set else @@ -660,6 +663,22 @@ function ZONE_BASE:Trigger(Objects) -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable leaving the zone. + + --- On After "ObjectDead" event. An observed object has left the zone. + -- @function [parent=#ZONE_BASE] OnAfterObjectDead + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable which died. Might be nil. + + --- On After "ZoneEmpty" event. All observed objects have left the zone or are dead. + -- @function [parent=#ZONE_BASE] OnAfterZoneEmpty + -- @param #ZONE_BASE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + end --- (Internal) Check the assigned objects for being in/out of the zone @@ -673,9 +692,13 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- just earmark everyone in/out for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE - if not obj.TriggerInZone then obj.TriggerInZone = {} end + if not obj.TriggerInZone then + obj.TriggerInZone = {} + obj.TriggerZoneDeadNotification = false + end if obj and obj:IsAlive() and self:IsCoordinateInZone(obj:GetCoordinate()) then obj.TriggerInZone[self.ZoneName] = true + self.ObjectsInZone = true else obj.TriggerInZone[self.ZoneName] = false end @@ -683,6 +706,7 @@ function ZONE_BASE:_TriggerCheck(fromstart) end else -- Check for changes + local objcount = 0 for _,_object in pairs(objectset) do local obj = _object -- Wrapper.Controllable#CONTROLLABLE if obj and obj:IsAlive() then @@ -697,11 +721,20 @@ function ZONE_BASE:_TriggerCheck(fromstart) -- is obj in zone? local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) --self:I("Object "..obj:GetName().." is in zone: "..tostring(inzone)) + if inzone and obj.TriggerInZone[self.ZoneName] then + -- just count + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false + end if inzone and not obj.TriggerInZone[self.ZoneName] then -- wasn't in zone before --self:I("Newly entered") self:__EnteredZone(0.5,obj) obj.TriggerInZone[self.ZoneName] = true + objcount = objcount + 1 + self.ObjectsInZone = true + obj.TriggerZoneDeadNotification = false elseif (not inzone) and obj.TriggerInZone[self.ZoneName] then -- has left the zone --self:I("Newly left") @@ -710,8 +743,21 @@ function ZONE_BASE:_TriggerCheck(fromstart) else --self:I("Not left or not entered, or something went wrong!") end + else + -- object dead + if not obj.TriggerZoneDeadNotification == true then + obj.TriggerInZone = nil + self:__ObjectDead(0.5,obj) + obj.TriggerZoneDeadNotification = true + end end end + -- zone empty? + if objcount == 0 and self.ObjectsInZone == true then + -- zone was not but is now empty + self.ObjectsInZone = false + self:__ZoneEmpty(0.5) + end end return self end @@ -782,8 +828,8 @@ end -- 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. +-- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#COORDINATE} object representing a random 2D point in the zone. +-- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#COORDINATE} object representing a random 3D point in the zone. Note that the height of the point is at landheight. -- -- ## Draw zone -- @@ -805,7 +851,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone ) -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS - self:F( { ZoneName, Vec2, Radius } ) + --self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 @@ -961,7 +1007,7 @@ end -- @param #number AddOffSet (optional) The angle to be added for the smoking start position. -- @return #ZONE_RADIUS self function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) local Point = {} local Vec2 = self:GetVec2() @@ -978,7 +1024,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Smoke( SmokeColor ) end return self @@ -992,7 +1038,7 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_RADIUS self function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) - self:F2( { FlareColor, Azimuth } ) + --self:F2( { FlareColor, Azimuth } ) local Point = {} local Vec2 = self:GetVec2() @@ -1008,7 +1054,7 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self @@ -1018,9 +1064,9 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:GetRadius() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) - self:T2( { self.Radius } ) + --self:T2( { self.Radius } ) return self.Radius end @@ -1030,10 +1076,10 @@ end -- @param DCS#Distance Radius The radius of the zone. -- @return DCS#Distance The radius of the zone. function ZONE_RADIUS:SetRadius( Radius ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) self.Radius = Radius - self:T2( { self.Radius } ) + --self:T2( { self.Radius } ) return self.Radius end @@ -1042,9 +1088,9 @@ end -- @param #ZONE_RADIUS self -- @return DCS#Vec2 The location of the zone. function ZONE_RADIUS:GetVec2() - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) - self:T2( { self.Vec2 } ) + --self:T2( { self.Vec2 } ) return self.Vec2 end @@ -1054,11 +1100,11 @@ end -- @param DCS#Vec2 Vec2 The new location of the zone. -- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) self.Vec2 = Vec2 - self:T2( { self.Vec2 } ) + --self:T2( { self.Vec2 } ) return self.Vec2 end @@ -1068,14 +1114,14 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The point of the zone. function ZONE_RADIUS:GetVec3( Height ) - self:F2( { self.ZoneName, Height } ) + --self:F2( { self.ZoneName, Height } ) 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 } ) + --self:T2( { Vec3 } ) return Vec3 end @@ -1152,7 +1198,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Units[ZoneObject] = ZoneObject - self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) + --self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end @@ -1163,7 +1209,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( tostring(SceneryName), ZoneObject) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName] ) - self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -1217,7 +1263,7 @@ function ZONE_RADIUS:GetScannedSetUnit() if FoundUnit then SetUnit:AddUnit( FoundUnit ) else - local FoundStatic = STATIC:FindByName( UnitObject:getName() ) + local FoundStatic = STATIC:FindByName( UnitObject:getName(), false ) if FoundStatic then SetUnit:AddUnit( FoundStatic ) end @@ -1423,13 +1469,13 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) + --self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { id = world.VolumeType.SPHERE, params = { point = ZoneCoord:GetVec3(), - radius = ZoneRadius / 2, + radius = ZoneRadius, } } @@ -1450,7 +1496,7 @@ end -- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) if not Vec2 then return false end @@ -1470,7 +1516,7 @@ end -- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) + --self:F2( Vec3 ) if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) @@ -1529,17 +1575,17 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) return point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @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#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end @@ -1550,27 +1596,27 @@ end -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @return DCS#Vec3 The random location within the zone. function ZONE_RADIUS:GetRandomVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) local Vec2 = self:GetRandomVec2( inner, outer ) - self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) + --self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) return { x = Vec2.x, y = self.y, z = Vec2.y } end ---- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @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#POINT_VEC3 The @{Core.Point#POINT_VEC3} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2( inner, outer ) ) - self:T3( { PointVec3 } ) + --self:T3( { PointVec3 } ) return PointVec3 end @@ -1699,7 +1745,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma T1=timer.getTime() - self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) + --self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) if found then return rcoord else return nil end @@ -1768,7 +1814,7 @@ 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, true)) - self:F(ZoneName) + --self:F(ZoneName) -- Color of zone. self.Color={1, 0, 0, 0.15} @@ -1838,7 +1884,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) self.relative_to_unit = Offset.relative_to_unit or false end - self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) + --self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() @@ -1854,7 +1900,7 @@ end -- @param #ZONE_UNIT self -- @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 ) + --self:F2( self.ZoneName ) local ZoneVec2 = self.ZoneUNIT:GetVec2() if ZoneVec2 then @@ -1887,7 +1933,7 @@ function ZONE_UNIT:GetVec2() return self.LastVec2 end - self:T2( { ZoneVec2 } ) + --self:T2( { ZoneVec2 } ) return nil end @@ -1896,7 +1942,7 @@ end -- @param #ZONE_UNIT self -- @return DCS#Vec2 The random location within the zone. function ZONE_UNIT:GetRandomVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local RandomVec2 = {} --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! @@ -1910,7 +1956,7 @@ function ZONE_UNIT:GetRandomVec2() 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 } ) + --self:T( { RandomVec2 } ) return RandomVec2 end @@ -1920,7 +1966,7 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) - self:F2( self.ZoneName ) + --self:F2( self.ZoneName ) Height = Height or 0 @@ -1928,7 +1974,7 @@ function ZONE_UNIT:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } - self:T2( { Vec3 } ) + --self:T2( { Vec3 } ) return Vec3 end @@ -1954,7 +2000,7 @@ ZONE_GROUP = { -- @return #ZONE_GROUP self function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius, true ) ) - self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) + --self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } ) self._.ZoneGROUP = ZoneGROUP self._.ZoneVec2Cache = self._.ZoneGROUP:GetVec2() @@ -1970,7 +2016,7 @@ end -- @param #ZONE_GROUP self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local ZoneVec2 = nil @@ -1981,7 +2027,7 @@ function ZONE_GROUP:GetVec2() ZoneVec2 = self._.ZoneVec2Cache end - self:T( { ZoneVec2 } ) + --self:T( { ZoneVec2 } ) return ZoneVec2 end @@ -1990,7 +2036,7 @@ end -- @param #ZONE_GROUP self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetRandomVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local Point = {} local Vec2 = self._.ZoneGROUP:GetVec2() @@ -1999,22 +2045,22 @@ function ZONE_GROUP:GetRandomVec2() 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 } ) + --self:T( { Point } ) return Point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_GROUP 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#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. +-- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_GROUP:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end @@ -2060,7 +2106,7 @@ function _ZONE_TRIANGLE:New(p1, p2, p3) end self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 - + return self end @@ -2068,7 +2114,7 @@ end -- @param #_ZONE_TRIANGLE self -- @param #table pt The point to check -- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function _ZONE_TRIANGLE:ContainsPoint(pt, points) points = points or self.Points @@ -2160,8 +2206,8 @@ end -- 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. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#COORDINATE} object representing a random 2D point within the zone. +-- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone -- @@ -2197,7 +2243,7 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) - self:F( { ZoneName, PointsArray } ) + --self:F( { ZoneName, PointsArray } ) if PointsArray then @@ -2365,7 +2411,7 @@ end -- @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 ) + --self:F( self.ZoneName ) local Bounds = self:GetBoundingSquare() @@ -2448,9 +2494,9 @@ end -- @param #ZONE_POLYGON_BASE self -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:Flush() - self:F2() + --self:F2() - self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) + --self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) return self end @@ -2469,7 +2515,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) j = #self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --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 @@ -2577,7 +2623,7 @@ function ZONE_POLYGON_BASE:ReFill(Color,Alpha) self.FillTriangles = {} end -- refill - for _, triangle in pairs(self._Triangles) do + for _,triangle in pairs(self._Triangles) do local draw_ids = triangle:Fill(coalition,color,alpha,nil) self.FillTriangles = draw_ids table.combine(self.DrawID, draw_ids) @@ -2721,7 +2767,7 @@ end -- @param #number Segments (Optional) Number of segments within boundary line. Default 10. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) - self:F2( SmokeColor ) + --self:F2( SmokeColor ) Segments=Segments or 10 @@ -2729,7 +2775,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) local j=#self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --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 @@ -2737,7 +2783,7 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) 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 ) - POINT_VEC2:New( PointX, PointY ):Smoke( SmokeColor ) + COORDINATE:New( PointX, 0, PointY ):Smoke( SmokeColor ) end j = i i = i + 1 @@ -2754,7 +2800,7 @@ end -- @param #number AddHeight (optional) The height to be added for the smoke. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) - self:F2(FlareColor) + --self:F2(FlareColor) Segments=Segments or 10 @@ -2764,7 +2810,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) local j=#self._.Polygon while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --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 @@ -2772,7 +2818,7 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) 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 ) - POINT_VEC2:New( PointX, PointY, AddHeight ):Flare(FlareColor, Azimuth) + COORDINATE:New( PointX, AddHeight, PointY ):Flare(FlareColor, Azimuth) end j = i i = i + 1 @@ -2787,7 +2833,7 @@ end -- @param DCS#Vec2 Vec2 The location to test. -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) - self:F2( Vec2 ) + --self:F2( Vec2 ) if not Vec2 then return false end local Next local Prev @@ -2797,18 +2843,18 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) Prev = #self._.Polygon while Next <= #self._.Polygon do - self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) + --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 ) ) then InPolygon = not InPolygon end - self:T2( { InPolygon = InPolygon } ) + --self:T2( { InPolygon = InPolygon } ) Prev = Next Next = Next + 1 end - self:T( { InPolygon = InPolygon } ) + --self:T( { InPolygon = InPolygon } ) return InPolygon end @@ -2817,7 +2863,7 @@ end -- @param DCS#Vec3 Vec3 The point to test. -- @return #boolean true if the point is within the zone. function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) - self:F2( Vec3 ) + --self:F2( Vec3 ) if not Vec3 then return false end @@ -2848,28 +2894,28 @@ function ZONE_POLYGON_BASE:GetRandomVec2() end end ---- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC2} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec2() - self:F2() + --self:F2() - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T2( PointVec2 ) + --self:T2( PointVec2 ) return PointVec2 end ---- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Return a @{Core.Point#COORDINATE} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_POLYGON_BASE self --- @return @{Core.Point#POINT_VEC3} +-- @return @{Core.Point#COORDINATE} function ZONE_POLYGON_BASE:GetRandomPointVec3() - self:F2() + --self:F2() - local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) + local PointVec3 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T2( PointVec3 ) + --self:T2( PointVec3 ) return PointVec3 end @@ -2879,11 +2925,11 @@ end -- @param #ZONE_POLYGON_BASE self -- @return Core.Point#COORDINATE function ZONE_POLYGON_BASE:GetRandomCoordinate() - self:F2() + --self:F2() local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T2( Coordinate ) + --self:T2( Coordinate ) return Coordinate end @@ -2900,7 +2946,7 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() local y2 = self._.Polygon[1].y for i = 2, #self._.Polygon do - self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + --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 @@ -2923,7 +2969,7 @@ function ZONE_POLYGON_BASE:GetBoundingVec2() local y2 = self._.Polygon[1].y for i = 2, #self._.Polygon do - self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + --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 @@ -2962,7 +3008,7 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C Limit = #self._.Polygon end while i <= #self._.Polygon do - self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + --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 @@ -3033,7 +3079,7 @@ function ZONE_POLYGON:New( ZoneName, ZoneGroup ) local GroupPoints = ZoneGroup:GetTaskRoute() local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, GroupPoints ) ) - self:F( { ZoneName, ZoneGroup, self._.Polygon } ) + --self:F( { ZoneName, ZoneGroup, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3049,7 +3095,7 @@ end function ZONE_POLYGON:NewFromPointsArray( ZoneName, PointsArray ) local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) ) - self:F( { ZoneName, self._.Polygon } ) + --self:F( { ZoneName, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3069,7 +3115,7 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) local GroupPoints = ZoneGroup:GetTaskRoute() local self = BASE:Inherit( self, ZONE_POLYGON_BASE:New( GroupName, GroupPoints ) ) - self:F( { GroupName, ZoneGroup, self._.Polygon } ) + --self:F( { GroupName, ZoneGroup, self._.Polygon } ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) @@ -3235,7 +3281,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Units[ZoneObject] = ZoneObject - self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) + --self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end @@ -3246,7 +3292,7 @@ function ZONE_POLYGON:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {} self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) table.insert(self.ScanData.SceneryTable,self.ScanData.Scenery[SceneryType][SceneryName]) - self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) + --self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end end @@ -3550,7 +3596,37 @@ do -- ZONE_ELASTIC return self end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates). + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex2D(Vec2) + + local found = false + local findex = 0 + for _id,_vec2 in pairs(self.points) do + if _vec2.x == Vec2.x and _vec2.y == Vec2.y then + found = true + findex = _id + break + end + end + + if found == true and findex > 0 then + table.remove(self.points,findex) + end + return self + end + + --- Remove a vertex (point) from the polygon. + -- @param #ZONE_ELASTIC self + -- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used. + -- @return #ZONE_ELASTIC self + function ZONE_ELASTIC:RemoveVertex3D(Vec3) + return self:RemoveVertex2D({x=Vec3.x, y=Vec3.z}) + end --- Add a vertex (point) to the polygon. -- @param #ZONE_ELASTIC self @@ -3587,8 +3663,8 @@ do -- ZONE_ELASTIC function ZONE_ELASTIC:Update(Delay, Draw) -- Debug info. - self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) - + --self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName))) + -- Copy all points. local points=UTILS.DeepCopy(self.points or {}) @@ -3606,6 +3682,9 @@ do -- ZONE_ELASTIC -- Update polygon verticies from points. self._.Polygon=self:_ConvexHull(points) + + self._Triangles = self:_Triangulate() + self.SurfaceArea = self:_CalculateSurfaceArea() if Draw~=false then if self.DrawID or Draw==true then @@ -3655,7 +3734,7 @@ do -- ZONE_ELASTIC end - --- Create a convec hull. + --- Create a convex hull. -- @param #ZONE_ELASTIC self -- @param #table pl Points -- @return #table Points @@ -3804,7 +3883,7 @@ end --- Checks if a point is contained within the oval. -- @param #ZONE_OVAL self -- @param #table point The point to check --- @return #bool True if the point is contained, false otherwise +-- @return #boolean True if the point is contained, false otherwise function ZONE_OVAL:IsVec2InZone(vec2) local cos, sin = math.cos, math.sin local dx = vec2.x - self.CenterVec2.x @@ -3866,18 +3945,18 @@ function ZONE_OVAL:GetRandomVec2() return {x=rx, y=ry} end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec2() - return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) + return COORDINATE:NewFromVec2(self:GetRandomVec2()) end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. +--- Define a random @{Core.Point#COORDINATE} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. -- @param #ZONE_OVAL self --- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +-- @return Core.Point#COORDINATE The COORDINATE coordinates. function ZONE_OVAL:GetRandomPointVec3() - return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) + return COORDINATE:NewFromVec3(self:GetRandomVec2()) end --- Draw the zone on the F10 map. @@ -4001,7 +4080,7 @@ do -- ZONE_AIRBASE -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The location of the zone based on the AIRBASE location. function ZONE_AIRBASE:GetVec2() - self:F( self.ZoneName ) + --self:F( self.ZoneName ) local ZoneVec2 = nil @@ -4012,22 +4091,22 @@ do -- ZONE_AIRBASE ZoneVec2 = self._.ZoneVec2Cache end - self:T( { ZoneVec2 } ) + --self:T( { ZoneVec2 } ) return ZoneVec2 end - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. + --- Returns a @{Core.Point#COORDINATE} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. -- @param #ZONE_AIRBASE 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#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. + -- @return Core.Point#COORDINATE The @{Core.Point#COORDINATE} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) - self:F( self.ZoneName, inner, outer ) + --self:F( self.ZoneName, inner, outer ) - local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) + local PointVec2 = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - self:T3( { PointVec2 } ) + --self:T3( { PointVec2 } ) return PointVec2 end diff --git a/Moose Development/Moose/Core/Zone_Detection.lua b/Moose Development/Moose/Core/Zone_Detection.lua index bb5424a37..8c05919c9 100644 --- a/Moose Development/Moose/Core/Zone_Detection.lua +++ b/Moose Development/Moose/Core/Zone_Detection.lua @@ -2,7 +2,8 @@ -- @module Core.Zone_Detection -- @image MOOSE.JPG ---- @type ZONE_DETECTION +--- +-- @type ZONE_DETECTION -- @field DCS#Vec2 Vec2 The current location of the zone. -- @field DCS#Distance Radius The radius of the zone. -- @extends #ZONE_BASE @@ -106,7 +107,7 @@ function ZONE_DETECTION:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Smoke( SmokeColor ) + COORDINATE:New( Point.x, AddHeight, Point.y):Smoke( SmokeColor ) end return self @@ -137,7 +138,7 @@ function ZONE_DETECTION:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - POINT_VEC2:New( Point.x, Point.y, AddHeight ):Flare( FlareColor, Azimuth ) + COORDINATE:New( Point.x, AddHeight, Point.y ):Flare( FlareColor, Azimuth ) end return self @@ -201,4 +202,3 @@ function ZONE_DETECTION:IsVec3InZone( Vec3 ) return InZone end - diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 642d3edbc..8e01b8a73 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -1,9 +1,9 @@ --- **DCS API** Prototypes. --- +-- -- === --- +-- -- See the [Simulator Scripting Engine Documentation](https://wiki.hoggitworld.com/view/Simulator_Scripting_Engine_Documentation) on Hoggit for further explanation and examples. --- +-- -- @module DCS -- @image MOOSE.JPG @@ -14,50 +14,81 @@ do -- world -- @field #world.event event [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world) -- @field #world.BirthPlace BirthPlace The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events. -- @field #world.VolumeType VolumeType The volumeType enumerator defines the types of 3d geometery used within the [world.searchObjects](https://wiki.hoggitworld.com/view/DCS_func_searchObjects) function. + -- @field #world.weather weather Weather functions for fog etc. --- The world singleton contains functions centered around two different but extremely useful functions. -- * Events and event handlers are all governed within world. -- * A number of functions to get information about the game world. - -- + -- -- See [https://wiki.hoggitworld.com/view/DCS_singleton_world](https://wiki.hoggitworld.com/view/DCS_singleton_world) -- @field #world world world = {} - + --- [https://wiki.hoggitworld.com/view/DCS_enum_world](https://wiki.hoggitworld.com/view/DCS_enum_world) -- @type world.event - -- @field S_EVENT_INVALID - -- @field S_EVENT_SHOT [https://wiki.hoggitworld.com/view/DCS_event_shot](https://wiki.hoggitworld.com/view/DCS_event_shot) - -- @field S_EVENT_HIT [https://wiki.hoggitworld.com/view/DCS_event_hit](https://wiki.hoggitworld.com/view/DCS_event_hit) - -- @field S_EVENT_TAKEOFF [https://wiki.hoggitworld.com/view/DCS_event_takeoff](https://wiki.hoggitworld.com/view/DCS_event_takeoff) - -- @field S_EVENT_LAND [https://wiki.hoggitworld.com/view/DCS_event_land](https://wiki.hoggitworld.com/view/DCS_event_land) - -- @field S_EVENT_CRASH [https://wiki.hoggitworld.com/view/DCS_event_crash](https://wiki.hoggitworld.com/view/DCS_event_crash) - -- @field S_EVENT_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_ejection](https://wiki.hoggitworld.com/view/DCS_event_ejection) - -- @field S_EVENT_REFUELING [https://wiki.hoggitworld.com/view/DCS_event_refueling](https://wiki.hoggitworld.com/view/DCS_event_refueling) - -- @field S_EVENT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_dead](https://wiki.hoggitworld.com/view/DCS_event_dead) - -- @field S_EVENT_PILOT_DEAD [https://wiki.hoggitworld.com/view/DCS_event_pilot_dead](https://wiki.hoggitworld.com/view/DCS_event_pilot_dead) - -- @field S_EVENT_BASE_CAPTURED [https://wiki.hoggitworld.com/view/DCS_event_base_captured](https://wiki.hoggitworld.com/view/DCS_event_base_captured) - -- @field S_EVENT_MISSION_START [https://wiki.hoggitworld.com/view/DCS_event_mission_start](https://wiki.hoggitworld.com/view/DCS_event_mission_start) - -- @field S_EVENT_MISSION_END [https://wiki.hoggitworld.com/view/DCS_event_mission_end](https://wiki.hoggitworld.com/view/DCS_event_mission_end) - -- @field S_EVENT_TOOK_CONTROL - -- @field S_EVENT_REFUELING_STOP [https://wiki.hoggitworld.com/view/DCS_event_refueling_stop](https://wiki.hoggitworld.com/view/DCS_event_refueling_stop) - -- @field S_EVENT_BIRTH [https://wiki.hoggitworld.com/view/DCS_event_birth](https://wiki.hoggitworld.com/view/DCS_event_birth) - -- @field S_EVENT_HUMAN_FAILURE [https://wiki.hoggitworld.com/view/DCS_event_human_failure](https://wiki.hoggitworld.com/view/DCS_event_human_failure) - -- @field S_EVENT_ENGINE_STARTUP [https://wiki.hoggitworld.com/view/DCS_event_engine_startup](https://wiki.hoggitworld.com/view/DCS_event_engine_startup) - -- @field S_EVENT_ENGINE_SHUTDOWN [https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown](https://wiki.hoggitworld.com/view/DCS_event_engine_shutdown) - -- @field S_EVENT_PLAYER_ENTER_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit](https://wiki.hoggitworld.com/view/DCS_event_player_enter_unit) - -- @field S_EVENT_PLAYER_LEAVE_UNIT [https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit](https://wiki.hoggitworld.com/view/DCS_event_player_leave_unit) - -- @field S_EVENT_PLAYER_COMMENT - -- @field S_EVENT_SHOOTING_START [https://wiki.hoggitworld.com/view/DCS_event_shooting_start](https://wiki.hoggitworld.com/view/DCS_event_shooting_start) - -- @field S_EVENT_SHOOTING_END [https://wiki.hoggitworld.com/view/DCS_event_shooting_end](https://wiki.hoggitworld.com/view/DCS_event_shooting_end) - -- @field S_EVENT_MARK ADDED [https://wiki.hoggitworld.com/view/DCS_event_mark_added](https://wiki.hoggitworld.com/view/DCS_event_mark_added) DCS>=2.5.1 - -- @field S_EVENT_MARK CHANGE [https://wiki.hoggitworld.com/view/DCS_event_mark_change](https://wiki.hoggitworld.com/view/DCS_event_mark_change) DCS>=2.5.1 - -- @field S_EVENT_MARK REMOVE [https://wiki.hoggitworld.com/view/DCS_event_mark_remove](https://wiki.hoggitworld.com/view/DCS_event_mark_remove) DCS>=2.5.1 - -- @field S_EVENT_KILL [https://wiki.hoggitworld.com/view/DCS_event_kill](https://wiki.hoggitworld.com/view/DCS_event_kill) DCS>=2.5.6 - -- @field S_EVENT_SCORE [https://wiki.hoggitworld.com/view/DCS_event_score](https://wiki.hoggitworld.com/view/DCS_event_score) DCS>=2.5.6 - -- @field S_EVENT_UNIT_LOST [https://wiki.hoggitworld.com/view/DCS_event_unit_lost](https://wiki.hoggitworld.com/view/DCS_event_unit_lost) DCS>=2.5.6 - -- @field S_EVENT_LANDING_AFTER_EJECTION [https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection](https://wiki.hoggitworld.com/view/DCS_event_landing_after_ejection) DCS>=2.5.6 - -- @field S_EVENT_MAX - + -- @field S_EVENT_INVALID = 0 + -- @field S_EVENT_SHOT = 1 + -- @field S_EVENT_HIT = 2 + -- @field S_EVENT_TAKEOFF = 3 + -- @field S_EVENT_LAND = 4 + -- @field S_EVENT_CRASH = 5 + -- @field S_EVENT_EJECTION = 6 + -- @field S_EVENT_REFUELING = 7 + -- @field S_EVENT_DEAD = 8 + -- @field S_EVENT_PILOT_DEAD = 9 + -- @field S_EVENT_BASE_CAPTURED = 10 + -- @field S_EVENT_MISSION_START = 11 + -- @field S_EVENT_MISSION_END = 12 + -- @field S_EVENT_TOOK_CONTROL = 13 + -- @field S_EVENT_REFUELING_STOP = 14 + -- @field S_EVENT_BIRTH = 15 + -- @field S_EVENT_HUMAN_FAILURE = 16 + -- @field S_EVENT_DETAILED_FAILURE = 17 + -- @field S_EVENT_ENGINE_STARTUP = 18 + -- @field S_EVENT_ENGINE_SHUTDOWN = 19 + -- @field S_EVENT_PLAYER_ENTER_UNIT = 20 + -- @field S_EVENT_PLAYER_LEAVE_UNIT = 21 + -- @field S_EVENT_PLAYER_COMMENT = 22 + -- @field S_EVENT_SHOOTING_START = 23 + -- @field S_EVENT_SHOOTING_END = 24 + -- @field S_EVENT_MARK_ADDED = 25 + -- @field S_EVENT_MARK_CHANGE = 26 + -- @field S_EVENT_MARK_REMOVED = 27 + -- @field S_EVENT_KILL = 28 + -- @field S_EVENT_SCORE = 29 + -- @field S_EVENT_UNIT_LOST = 30 + -- @field S_EVENT_LANDING_AFTER_EJECTION = 31 + -- @field S_EVENT_PARATROOPER_LENDING = 32 -- who's lending whom what? ;) + -- @field S_EVENT_DISCARD_CHAIR_AFTER_EJECTION = 33 + -- @field S_EVENT_WEAPON_ADD = 34 + -- @field S_EVENT_TRIGGER_ZONE = 35 + -- @field S_EVENT_LANDING_QUALITY_MARK = 36 + -- @field S_EVENT_BDA = 37 -- battle damage assessment + -- @field S_EVENT_AI_ABORT_MISSION = 38 + -- @field S_EVENT_DAYNIGHT = 39 + -- @field S_EVENT_FLIGHT_TIME = 40 + -- @field S_EVENT_PLAYER_SELF_KILL_PILOT = 41 + -- @field S_EVENT_PLAYER_CAPTURE_AIRFIELD = 42 + -- @field S_EVENT_EMERGENCY_LANDING = 43 + -- @field S_EVENT_UNIT_CREATE_TASK = 44 + -- @field S_EVENT_UNIT_DELETE_TASK = 45 + -- @field S_EVENT_SIMULATION_START = 46 + -- @field S_EVENT_WEAPON_REARM = 47 + -- @field S_EVENT_WEAPON_DROP = 48 + -- @field S_EVENT_UNIT_TASK_COMPLETE = 49 + -- @field S_EVENT_UNIT_TASK_STAGE = 50 + -- @field S_EVENT_MAC_EXTRA_SCORE= 51 -- not sure what this is + -- @field S_EVENT_MISSION_RESTART= 52 + -- @field S_EVENT_MISSION_WINNER = 53 + -- @field S_EVENT_RUNWAY_TAKEOFF= 54 + -- @field S_EVENT_RUNWAY_TOUCH= 55 + -- @field S_EVENT_MAC_LMS_RESTART= 56 -- not sure what this is + -- @field S_EVENT_SIMULATION_FREEZE = 57 + -- @field S_EVENT_SIMULATION_UNFREEZE = 58 + -- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_START = 59 + -- @field S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH = 60 + -- @field S_EVENT_MAX = 61 + --- The birthplace enumerator is used to define where an aircraft or helicopter has spawned in association with birth events. -- @type world.BirthPlace -- @field wsBirthPlace_Air @@ -76,24 +107,24 @@ do -- world --- Adds a function as an event handler that executes whenever a simulator event occurs. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addEventHandler). -- @function [parent=#world] addEventHandler -- @param #table handler Event handler table. - + --- Removes the specified event handler from handling events. -- @function [parent=#world] removeEventHandler -- @param #table handler Event handler table. - + --- Returns a table of the single unit object in the game who's skill level is set as "Player". See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getPlayer). -- There is only a single player unit in a mission and in single player the user will always spawn into this unit automatically unless other client or Combined Arms slots are available. -- @function [parent=#world] getPlayer - -- @return DCS#Unit - + -- @return DCS#Unit + --- Searches a defined volume of 3d space for the specified objects within it and then can run function on each returned object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_searchObjects). -- @function [parent=#world] searchObjects -- @param DCS#Object.Category objectcategory Category (can be a table) of objects to search. -- @param DCS#word.VolumeType volume Shape of the search area/volume. -- @param ObjectSeachHandler handler A function that handles the search. -- @param #table any Additional data. - -- @return DCS#Unit - + -- @return DCS#Unit + --- Returns a table of mark panels indexed numerically that are present within the mission. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getMarkPanels) -- @function [parent=#world] getMarkPanels -- @return #table Table of marks. @@ -103,6 +134,36 @@ do -- world -- @param #number coalitionId The coalition side number ID. Default is all airbases are returned. -- @return #table Table of DCS airbase objects. + + --- Weather functions. + -- @type world.weather + + --- Fog animation data structure. + -- @type world.FogAnimation + -- @field #number time + -- @field #number visibility + -- @field #number thickness + + --- Returns the current fog thickness. + -- @function [parent=#world.weather] getFogThickness Returns the fog thickness. + -- @return #number Fog thickness in meters. If there is no fog, zero is returned. + + --- Sets the fog thickness instantly. Any current fog animation is discarded. + -- @function [parent=#world.weather] setFogThickness + -- @param #number thickness Fog thickness in meters. Set to zero to disable fog. + + --- Returns the current fog visibility distance. + -- @function [parent=#world.weather] getFogVisibilityDistance Returns the current maximum visibility distance in meters. Returns zero if fog is not present. + + --- Instantly sets the maximum visibility distance of fog at sea level when looking at the horizon. Any current fog animation is discarded. Set zero to disable the fog. + -- @function [parent=#world.weather] setFogVisibilityDistance + -- @param #number visibility Max fog visibility in meters. Set to zero to disable fog. + + --- Sets fog animation keys. Time is set in seconds and relative to the current simulation time, where time=0 is the current moment. + -- Time must be increasing. Previous animation is always discarded despite the data being correct. + -- @function [parent=#world.weather] setFogAnimation + -- @param #world.FogAnimation animation List of fog animations + end -- world @@ -115,90 +176,90 @@ do -- env -- @function [parent=#env] info -- @param #string message message string to add to log. -- @param #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - - --- Add message to simulator log with caption "WARNING". Message box is optional. + + --- Add message to simulator log with caption "WARNING". Message box is optional. -- @function [parent=#env] warning -- @param #string message message string to add to log. -- @param #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - + --- Add message to simulator log with caption "ERROR". Message box is optional. -- @function [parent=#env] error -- @param #string message message string to add to log. -- @param #boolean showMessageBox If the parameter is true Message Box will appear. Optional. - + --- Enables/disables appearance of message box each time lua error occurs. -- @function [parent=#env] setErrorMessageBoxEnabled -- @param #boolean on if true message box appearance is enabled. --- [DCS Singleton env](https://wiki.hoggitworld.com/view/DCS_singleton_env) - env = {} --#env - + env = {} --#env + end -- env do -- radio ---@type radio -- @field #radio.modulation modulation - + --- -- @type radio.modulation -- @field AM -- @field FM - + radio = {} radio.modulation = {} - radio.modulation.AM = 0 + radio.modulation.AM = 0 radio.modulation.FM = 1 - + end do -- timer --- [DCS Singleton timer](https://wiki.hoggitworld.com/view/DCS_singleton_timer) -- @type timer - + --- Returns model time in seconds. -- @function [parent=#timer] getTime - -- @return #Time - + -- @return #Time + --- Returns mission time in seconds. -- @function [parent=#timer] getAbsTime -- @return #Time - + --- Returns mission start time in seconds. -- @function [parent=#timer] getTime0 -- @return #Time - + --- Schedules function to call at desired model time. -- Time function FunctionToCall(any argument, Time time) - -- + -- -- ... - -- + -- -- return ... - -- + -- -- end - -- - -- Must return model time of next call or nil. Note that the DCS scheduler calls the function in protected mode and any Lua errors in the called function will be trapped and not reported. If the function triggers a Lua error then it will be terminated and not scheduled to run again. + -- + -- Must return model time of next call or nil. Note that the DCS scheduler calls the function in protected mode and any Lua errors in the called function will be trapped and not reported. If the function triggers a Lua error then it will be terminated and not scheduled to run again. -- @function [parent=#timer] scheduleFunction - -- @param #FunctionToCall functionToCall Lua-function to call. Must have prototype of FunctionToCall. + -- @param #FunctionToCall functionToCall Lua-function to call. Must have prototype of FunctionToCall. -- @param functionArgument Function argument of any type to pass to functionToCall. -- @param #Time time Model time of the function call. -- @return functionId - + --- Re-schedules function to call at another model time. - -- @function [parent=#timer] setFunctionTime - -- @param functionId Lua-function to call. Must have prototype of FunctionToCall. - -- @param #Time time Model time of the function call. - - + -- @function [parent=#timer] setFunctionTime + -- @param functionId Lua-function to call. Must have prototype of FunctionToCall. + -- @param #Time time Model time of the function call. + + --- Removes the function from schedule. -- @function [parent=#timer] removeFunction - -- @param functionId Function identifier to remove from schedule - + -- @param functionId Function identifier to remove from schedule + --- [DCS Singleton timer](https://wiki.hoggitworld.com/view/DCS_singleton_timer) timer = {} --#timer -end +end do -- land @@ -206,7 +267,7 @@ do -- land --- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land) -- @type land -- @field #land.SurfaceType SurfaceType - + --- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land) -- @type land.SurfaceType -- @field LAND Land=1 @@ -214,52 +275,37 @@ do -- land -- @field WATER Water=3 -- @field ROAD Road=4 -- @field RUNWAY Runway=5 - + --- Returns the distance from sea level (y-axis) of a given vec2 point. -- @function [parent=#land] getHeight - -- @param #Vec2 point Point on the ground. + -- @param #Vec2 point Point on the ground. -- @return #number Height in meters. - --- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship. - -- Both values are positive. When checked over water at sea level the first value is always zero. + --- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship. + -- Both values are positive. When checked over water at sea level the first value is always zero. -- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at. -- @function [parent=#land] getSurfaceHeightWithSeabed -- @param #Vec2 point Position where to check. -- @return #number Height in meters. -- @return #number Depth in meters. - + --- Returns surface type at the given point. -- @function [parent=#land] getSurfaceType - -- @param #Vec2 point Point on the land. + -- @param #Vec2 point Point on the land. -- @return #number Enumerator value from `land.SurfaceType` (LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5) - + --- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land) land = {} --#land end -- land -do - - --- [DCS enum radio](https://wiki.hoggitworld.com/view/DCS_enum_radio) - -- @type radio - -- @field #radio.modulation modulation Modulation - - --- [Type of modulation](https://wiki.hoggitworld.com/view/DCS_singleton_land) - -- @type radio.modulation - -- @field #number AM Amplitude modulation (AM=0) - -- @field #number FM Frequency modulation (FM=1) - - radio={} --#radio - -end -- radio - do -- country --- [DCS Enum country](https://wiki.hoggitworld.com/view/DCS_enum_country) -- @type country - -- @field #country.id id - - + -- @field #country.id id + + --- [DCS enumerator country](https://wiki.hoggitworld.com/view/DCS_enum_country) -- @type country.id -- @field RUSSIA @@ -325,7 +371,7 @@ do -- country -- @field HONDURAS -- @field ETHIOPIA -- @field CHILE - -- @field BRAZIL + -- @field BRAZIL -- @field BAHRAIN -- @field THIRDREICH -- @field YUGOSLAVIA @@ -363,7 +409,7 @@ do -- Command -- @type Command -- @field #string id -- @field #Command.params params - + -- @type Command.params end -- Command @@ -373,13 +419,13 @@ do -- coalition --- [DCS Enum coalition](https://wiki.hoggitworld.com/view/DCS_enum_coalition) -- @type coalition -- @field #coalition.side side - + --- [DCS Enum coalition.side](https://wiki.hoggitworld.com/view/DCS_enum_coalition) -- @type coalition.side -- @field NEUTRAL -- @field RED -- @field BLUE - + --- Get country coalition. -- @function [parent=#coalition] getCountryCoalition -- @param #number countryId Country ID. @@ -392,12 +438,12 @@ do -- coalition -- @param #table groupData Group data table. -- @return DCS#Group The spawned Group object. - --- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup) + --- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addStaticObject) -- @function [parent=#coalition] addStaticObject -- @param #number countryId Id of the country. -- @param #table groupData Group data table. -- @return DCS#Static The spawned static object. - + coalition = {} -- #coalition end -- coalition @@ -405,7 +451,7 @@ end -- coalition do -- Types - --- Descriptors + --- Descriptors. -- @type Desc -- @field #number speedMax0 Max speed in meters/second at zero altitude. -- @field #number massEmpty Empty mass in kg. @@ -428,79 +474,79 @@ do -- Types -- @field #TypeName typeName Type Name. -- @field #string displayName Localized display name. -- @field #number category Unit category. - + --- A distance type -- @type Distance - + --- An angle type -- @type Angle - + --- Time is given in seconds. -- @type Time -- @extends #number Time in seconds. - - --- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. + + --- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. -- @type ModelTime -- @extends #number - + --- Mission time is a model time plus time of the mission start. -- @type MissionTime -- @extends #number Time in seconds. - - + + --- Distance is given in meters. -- @type Distance -- @extends #number Distance in meters. - + --- Angle is given in radians. -- @type Angle -- @extends #number Angle in radians. - + --- Azimuth is an angle of rotation around world axis y counter-clockwise. -- @type Azimuth -- @extends #number Angle in radians. - + --- Mass is given in kilograms. -- @type Mass -- @extends #number - + --- Vec3 type is a 3D-vector. -- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. -- @type Vec3 -- @field #Distance x is directed to the North -- @field #Distance z is directed to the East -- @field #Distance y is directed up - + --- Vec2 is a 2D-vector for the ground plane as a reference plane. -- @type Vec2 -- @field #Distance x Vec2.x = Vec3.x -- @field #Distance y Vec2.y = Vec3.z - - --- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: + + --- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: -- @type Position3 -- @field #Vec3 p 3D position vector. -- @field #Vec3 x Orientation component of vector pointing East. -- @field #Vec3 y Orientation component of vector pointing up. -- @field #Vec3 z Orientation component of vector pointing North. - + --- 3-dimensional box. -- @type Box3 -- @field #Vec3 min Min. -- @field #Vec3 max Max - - --- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". + + --- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". -- @type TypeName -- @extends #string - - --- AttributeName = string + + --- AttributeName = string -- Each object type may have attributes. -- Attributes are enlisted in ./Scripts/Database/db_attributes.Lua. - -- To know what attributes the object type has, look for the unit type script in sub-directories planes/, helicopter/s, vehicles, navy/ of ./Scripts/Database/ directory. + -- To know what attributes the object type has, look for the unit type script in sub-directories planes/, helicopter/s, vehicles, navy/ of ./Scripts/Database/ directory. -- @type AttributeName -- @extends #string - + --- List of @{#AttributeName} - -- @type AttributeNameArray + -- @type AttributeNameArray -- @list <#AttributeName> -- @type Zone @@ -511,18 +557,18 @@ do -- Types -- @type ModelTime -- @extends #number - + -- @type Time -- @extends #number - + --- A task descriptor (internal structure for DCS World). See [https://wiki.hoggitworld.com/view/Category:Tasks](https://wiki.hoggitworld.com/view/Category:Tasks). -- In MOOSE, these tasks can be accessed via @{Wrapper.Controllable#CONTROLLABLE}. -- @type Task -- @field #string id -- @field #Task.param param - + -- @type Task.param - + --- List of @{#Task} -- @type TaskArray -- @list <#Task> @@ -539,8 +585,8 @@ do -- Types -- @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. @@ -558,7 +604,7 @@ do -- Object -- @type Object -- @field #Object.Category Category -- @field #Object.Desc Desc - + --- [DCS Enum Object.Category](https://wiki.hoggitworld.com/view/DCS_Class_Object) -- @type Object.Category -- @field UNIT @@ -567,12 +613,12 @@ do -- Object -- @field BASE -- @field SCENERY -- @field CARGO - + -- @type Object.Desc -- @extends #Desc -- @field #number life initial life level -- @field #Box3 box bounding box of collision geometry - + --- @function [parent=#Object] isExist -- @param #Object self -- @return #boolean @@ -580,55 +626,59 @@ do -- Object --- @function [parent=#Object] isActive -- @param #Object self -- @return #boolean - + --- @function [parent=#Object] destroy -- @param #Object self - - --- @function [parent=#Object] getCategory + + --- Returns an enumerator of the category for the specific object. + -- The enumerator returned is dependent on the category of the object and how the function is called. + -- As of DCS 2.9.2 when this function is called on an Object, Unit, Weapon, or Airbase a 2nd value will be returned which details the object sub-category value. + -- @function [parent=#Object] getCategory -- @param #Object self - -- @return #Object.Category - + -- @return #Object.Category The object category (1=UNIT, 2=WEAPON, 3=STATIC, 4=BASE, 5=SCENERY, 6=Cargo) + -- @return #number The subcategory of the passed object, e.g. Unit.Category if a unit object was passed. + --- Returns type name of the Object. -- @function [parent=#Object] getTypeName -- @param #Object self - -- @return #string - + -- @return #string + --- Returns object descriptor. -- @function [parent=#Object] getDesc -- @param #Object self -- @return #Object.Desc - + --- Returns true if the object belongs to the category. -- @function [parent=#Object] hasAttribute -- @param #Object self -- @param #AttributeName attributeName Attribute name to check. -- @return #boolean - + --- Returns name of the object. This is the name that is assigned to the object in the Mission Editor. -- @function [parent=#Object] getName -- @param #Object self -- @return #string - + --- Returns object coordinates for current time. -- @function [parent=#Object] getPoint -- @param #Object self -- @return #Vec3 3D position vector with x,y,z components. - - --- Returns object position for current time. + + --- Returns object position for current time. -- @function [parent=#Object] getPosition -- @param #Object self -- @return #Position3 - + --- Returns the unit's velocity vector. -- @function [parent=#Object] getVelocity -- @param #Object self -- @return #Vec3 3D velocity vector. - + --- Returns true if the unit is in air. -- @function [parent=#Object] inAir -- @param #Object self -- @return #boolean - + Object = {} --#Object end -- Object @@ -638,12 +688,12 @@ do -- CoalitionObject --- [DCS Class CoalitionObject](https://wiki.hoggitworld.com/view/DCS_Class_Coalition_Object) -- @type CoalitionObject -- @extends #Object - + --- Returns coalition of the object. -- @function [parent=#CoalitionObject] getCoalition -- @param #CoalitionObject self -- @return #coalition.side - + --- Returns object country. -- @function [parent=#CoalitionObject] getCountry -- @param #CoalitionObject self @@ -662,7 +712,7 @@ do -- Weapon -- @field #Weapon.flag flag enum stores weapon flags. Some of them are combination of another flags. -- @field #Weapon.Category Category enum that stores weapon categories. -- @field #Weapon.GuidanceType GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB). - -- @field #Weapon.MissileCategory MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE). + -- @field #Weapon.MissileCategory MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE). -- @field #Weapon.WarheadType WarheadType enum that stores warhead types. -- @field #Weapon.Desc Desc The descriptor of a weapon. @@ -695,20 +745,20 @@ do -- Weapon -- @field TeleASM -- @field CruiseMissile -- @field GuidedASM = LaserASM + TeleASM - -- @field TacticASM = GuidedASM + FireAndForgetASM + -- @field TacticASM = GuidedASM + FireAndForgetASM -- @field AnyASM = AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile -- @field SRAAM - -- @field MRAAM - -- @field LRAAM - -- @field IR_AAM - -- @field SAR_AAM - -- @field AR_AAM - -- @field AnyAAM = IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM + -- @field MRAAM + -- @field LRAAM + -- @field IR_AAM + -- @field SAR_AAM + -- @field AR_AAM + -- @field AnyAAM = IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM -- @field AnyMissile = AnyASM + AnyAAM -- @field AnyAutonomousMissile = IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile -- @field GUN_POD -- @field BuiltInCannon - -- @field Cannons = GUN_POD + BuiltInCannon + -- @field Cannons = GUN_POD + BuiltInCannon -- @field AnyAGWeapon = BuiltInCannon + GUN_POD + AnyBomb + AnyRocket + AnyASM -- @field AnyAAWeapon = BuiltInCannon + GUN_POD + AnyAAM -- @field UnguidedWeapon = Cannons + BuiltInCannon + GUN_POD + AnyUnguidedBomb + AnyRocket @@ -724,9 +774,9 @@ do -- Weapon -- @field #number ROCKET Rocket. -- @field #number BOMB Bomb. -- @field #number TORPEDO Torpedo. + - - --- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB). + --- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB). -- @type Weapon.GuidanceType -- @field INS -- @field IR @@ -735,10 +785,10 @@ do -- Weapon -- @field RADAR_PASSIVE -- @field TV -- @field LASER - -- @field TELE + -- @field TELE - - --- Weapon.MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE). + + --- Weapon.MissileCategory enum that stores missile category. Available only for missiles (Weapon.Category.MISSILE). -- @type Weapon.MissileCategory -- @field AAM -- @field SAM @@ -747,23 +797,23 @@ do -- Weapon -- @field CRUISE -- @field OTHER - --- Weapon.WarheadType enum that stores warhead types. + --- Weapon.WarheadType enum that stores warhead types. -- @type Weapon.WarheadType -- @field AP -- @field HE -- @field SHAPED_EXPLOSIVE - + --- Returns the unit that launched the weapon. -- @function [parent=#Weapon] getLauncher -- @param #Weapon self -- @return #Unit - - --- returns target of the guided weapon. Unguided weapons and guided weapon that is targeted at the point on the ground will return nil. + + --- returns target of the guided weapon. Unguided weapons and guided weapon that is targeted at the point on the ground will return nil. -- @function [parent=#Weapon] getTarget -- @param #Weapon self -- @return #Object - - --- returns weapon descriptor. Descriptor type depends on weapon category. + + --- returns weapon descriptor. Descriptor type depends on weapon category. -- @function [parent=#Weapon] getDesc -- @param #Weapon self -- @return #Weapon.Desc @@ -778,50 +828,50 @@ end -- Weapon do -- Airbase --- [DCS Class Airbase](https://wiki.hoggitworld.com/view/DCS_Class_Airbase) - -- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. + -- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. -- @type Airbase -- @extends #CoalitionObject - -- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. - -- @field #Airbase.Category Category enum contains identifiers of airbase categories. - -- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. - + -- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. + -- @field #Airbase.Category Category enum contains identifiers of airbase categories. + -- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. + --- Enum contains identifiers of airbase categories. -- @type Airbase.Category -- @field AIRDROME -- @field HELIPAD -- @field SHIP - - --- Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. + + --- Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. -- @type Airbase.Desc -- @extends #Desc -- @field #Airbase.Category category Category of the airbase type. - + --- Returns airbase by its name. If no airbase found the function will return nil. -- @function [parent=#Airbase] getByName -- @param #string name -- @return #Airbase - + --- Returns airbase descriptor by type name. If no descriptor is found the function will return nil. -- @function [parent=#Airbase] getDescByName -- @param #TypeName typeName Airbase type name. -- @return #Airbase.Desc - + --- Returns Unit that is corresponded to the airbase. Works only for ships. -- @function [parent=#Airbase] getUnit -- @param self -- @return #Unit - + --- Returns identifier of the airbase. -- @function [parent=#Airbase] getID -- @param self -- @return #Airbase.ID - + --- Returns the airbase's callsign - the localized string. -- @function [parent=#Airbase] getCallsign -- @param self -- @return #string - - --- Returns descriptor of the airbase. + + --- Returns descriptor of the airbase. -- @function [parent=#Airbase] getDesc -- @param self -- @return #Airbase.Desc @@ -831,7 +881,7 @@ do -- Airbase -- @param self -- @return #Warehouse The DCS warehouse object of this airbase. - --- Enables or disables the airbase and FARP auto capture game mechanic where ownership of a base can change based on the presence of ground forces or the + --- Enables or disables the airbase and FARP auto capture game mechanic where ownership of a base can change based on the presence of ground forces or the -- default setting assigned in the editor. -- @function [parent=#Airbase] autoCapture -- @param self @@ -842,13 +892,13 @@ do -- Airbase -- @param self -- @return #boolean `true` if autoCapture behavior is enabled and `false` otherwise. - --- Changes the passed airbase object's coalition to the set value. Must be used with Airbase.autoCapture to disable auto capturing of the base, + --- Changes the passed airbase object's coalition to the set value. Must be used with Airbase.autoCapture to disable auto capturing of the base, -- otherwise the base can revert back to a different coalition depending on the situation and built in game capture rules. -- @function [parent=#Airbase] setCoalition -- @param self -- @param #number coa The new owner coalition: 0=neutra, 1=red, 2=blue. - --- Returns the wsType of every object that exists in DCS. A wsType is a table consisting of 4 entries indexed numerically. + --- Returns the wsType of every object that exists in DCS. A wsType is a table consisting of 4 entries indexed numerically. -- It can be used to broadly categorize object types. The table can be broken down as: {mainCategory, subCat1, subCat2, index} -- @function [parent=#Airbase] getResourceMap -- @param self @@ -865,7 +915,7 @@ do -- Warehouse -- The warehouse class gives control over warehouses that exist in airbase objects. These warehouses can limit the aircraft, munitions, and fuel available to coalition aircraft. -- @type Warehouse - + --- Get a warehouse by passing its name. -- @function [parent=#Warehouse] getByName -- @param #string Name Name of the warehouse. @@ -933,7 +983,7 @@ do -- Warehouse -- @return #table Itemized list of everything currently in a warehouse - Warehouse = {} --#Warehouse + Warehouse = {} --#Warehouse end @@ -941,15 +991,15 @@ 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 + -- @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. @@ -969,7 +1019,7 @@ do -- Spot -- @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 @@ -984,7 +1034,7 @@ do -- Spot -- @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 @@ -999,117 +1049,119 @@ do -- Spot end -- Spot do -- Controller - --- Controller is an object that performs A.I.-tasks. 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. - -- + + --- Controller is an object that performs A.I.-tasks. 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. + -- -- This class has 2 types of functions: - -- + -- -- * Tasks -- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics. + -- -- @type Controller -- @field #Controller.Detection Detection Enum contains identifiers of surface types. - + --- Enables and disables the controller. -- Note: Now it works only for ground / naval groups! -- @function [parent=#Controller] setOnOff -- @param self -- @param #boolean value Enable / Disable. - + -- Tasks - + --- Resets current task and then sets the task to the controller. Task is a table that contains task identifier and task parameters. -- @function [parent=#Controller] setTask -- @param self -- @param #Task task - + --- Resets current task of the controller. - -- @function [parent=#Controller] resetTask + -- @function [parent=#Controller] resetTask -- @param self - + --- Pushes the task to the front of the queue and makes the task active. Further call of function Controller.setTask() function will stop current task, clear the queue and set the new task active. If the task queue is empty the function will work like function Controller.setTask() function. -- @function [parent=#Controller] pushTask -- @param self -- @param #Task task - + --- Pops current (front) task from the queue and makes active next task in the queue (if exists). If no more tasks in the queue the function works like function Controller.resetTask() function. Does nothing if the queue is empty. -- @function [parent=#Controller] popTask -- @param self - - --- Returns true if the controller has a task. + + --- Returns true if the controller has a task. -- @function [parent=#Controller] hasTask -- @param self -- @return #boolean - + -- Commands - + --TODO: describe #Command structure --- Sets the command to perform by controller. -- @function [parent=#Controller] setCommand -- @param self - -- @param #Command command Table that contains command identifier and command parameters. - - + -- @param #Command command Table that contains command identifier and command parameters. + + -- Behaviours - + --- Sets the option to the controller. -- Option is a pair of identifier and value. Behavior options are global parameters those affect controller behavior in all tasks it performs. -- Option identifiers and values are stored in table AI.Option in subtables Air, Ground and Naval. - -- + -- -- OptionId = @{#AI.Option.Air.id} or @{#AI.Option.Ground.id} or @{#AI.Option.Naval.id} -- OptionValue = AI.Option.Air.val[optionName] or AI.Option.Ground.val[optionName] or AI.Option.Naval.val[optionName] - -- + -- -- @function [parent=#Controller] setOption -- @param self - -- @param #OptionId optionId Option identifier. + -- @param #OptionId optionId Option identifier. -- @param #OptionValue optionValue Value of the option. - - + + -- Detection - - --- Enum contains identifiers of surface types. + + --- Enum containing detection types. -- @type Controller.Detection - -- @field VISUAL - -- @field OPTIC - -- @field RADAR - -- @field IRST - -- @field RWR - -- @field DLINK - - --- Detected target. - -- @type DetectedTarget - -- @field Wrapper.Object#Object object The target + -- @field #number VISUAL Visual detection. Numeric value 1. + -- @field #number OPTIC Optical detection. Numeric value 2. + -- @field #number RADAR Radar detection. Numeric value 4. + -- @field #number IRST Infra-red search and track detection. Numeric value 8. + -- @field #number RWR Radar Warning Receiver detection. Numeric value 16. + -- @field #number DLINK Data link detection. Numeric value 32. + + --- Detected target. + -- @type Controller.DetectedTarget + -- @field DCS#Object object The target -- @field #boolean visible The target is visible -- @field #boolean type The target type is known -- @field #boolean distance Distance to the target is known - - - --- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. + + + --- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. -- @function [parent=#Controller] isTargetDetected -- @param self -- @param Wrapper.Object#Object target Target to check - -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN - -- @return #boolean detected True if the target is detected. - -- @return #boolean visible Has effect only if detected is true. True if the target is visible now. - -- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen. + -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN + -- @return #boolean detected True if the target is detected. + -- @return #boolean visible Has effect only if detected is true. True if the target is visible now. -- @return #boolean type Has effect only if detected is true. True if the target type is known. -- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known. - -- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen. - -- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen. - - + -- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen. + -- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen. + -- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen. + + --- Returns list of detected targets. If one or more detection method is specified the function will return targets which were detected by at least one of these methods. If no detection methods are specified the function will return targets which were detected by any method. -- @function [parent=#Controller] getDetectedTargets -- @param self - -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN + -- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN -- @return #list<#DetectedTarget> array of DetectedTarget - + --- Know a target. -- @function [parent=#Controller] knowTarget -- @param self -- @param Wrapper.Object#Object object The target. -- @param #boolean type Target type is known. -- @param #boolean distance Distance to target is known. - - + + Controller = {} --#Controller end -- Controller @@ -1117,9 +1169,10 @@ end -- Controller do -- Unit + --- Unit. -- @type Unit -- @extends #CoalitionObject - -- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. + -- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. -- @field #Unit.Category Category -- @field #Unit.RefuelingSystem RefuelingSystem -- @field #Unit.SensorType SensorType @@ -1137,8 +1190,8 @@ do -- Unit -- @field #Unit.Optic Optic -- @field #Unit.Radar Radar -- @field #Unit.IRST IRST - - + + --- Enum that stores unit categories. -- @type Unit.Category -- @field AIRPLANE @@ -1146,39 +1199,39 @@ do -- Unit -- @field GROUND_UNIT -- @field SHIP -- @field STRUCTURE - + --- Enum that stores aircraft refueling system types. -- @type Unit.RefuelingSystem -- @field BOOM_AND_RECEPTACLE Tanker with a boom. -- @field PROBE_AND_DROGUE Tanker with a probe. - + --- Enum that stores sensor types. -- @type Unit.SensorType -- @field OPTIC -- @field RADAR -- @field IRST -- @field RWR - + --- Enum that stores types of optic sensors. -- @type Unit.OpticType -- @field TV TV-sensor -- @field LLTV Low-level TV-sensor -- @field IR Infra-Red optic sensor - + --- Enum that stores radar types. -- @type Unit.RadarType -- @field AS air search radar -- @field SS surface/land search radar - - - --- A unit descriptor. + + + --- A unit descriptor. -- @type Unit.Desc -- @extends #Object.Desc -- @field #Unit.Category category Unit Category -- @field #Mass massEmpty mass of empty unit -- @field #number speedMax istance / Time, --maximal velocity - - --- An aircraft descriptor. + + --- An aircraft descriptor. -- @type Unit.DescAircraft -- @extends #Unit.Desc -- @field #Mass fuelMassMax maximal inner fuel mass @@ -1188,137 +1241,137 @@ do -- Unit -- @field #number NyMin minimal safe acceleration -- @field #number NyMax maximal safe acceleration -- @field #Unit.RefuelingSystem tankerType refueling system type - + --- An airplane descriptor. - -- @type Unit.DescAirplane + -- @type Unit.DescAirplane -- @extends #Unit.DescAircraft -- @field #number speedMax0 Distance / Time maximal TAS at ground level -- @field #number speedMax10K Distance / Time maximal TAS at altitude of 10 km - + --- A helicopter descriptor. - -- @type Unit.DescHelicopter + -- @type Unit.DescHelicopter -- @extends #Unit.DescAircraft -- @field #Distance HmaxStat static ceiling - + --- A vehicle descriptor. - -- @type Unit.DescVehicle + -- @type Unit.DescVehicle -- @extends #Unit.Desc -- @field #Angle maxSlopeAngle maximal slope angle -- @field #boolean riverCrossing can the vehicle cross a rivers - + --- A ship descriptor. - -- @type Unit.DescShip + -- @type Unit.DescShip -- @extends #Unit.Desc - + --- ammunition item: "type-count" pair. -- @type Unit.AmmoItem -- @field #Weapon.Desc desc ammunition descriptor -- @field #number count ammunition count - + --- A unit sensor. -- @type Unit.Sensor -- @field #TypeName typeName -- @field #Unit.SensorType type - + --- An optic sensor. - -- @type Unit.Optic + -- @type Unit.Optic -- @extends #Unit.Sensor -- @field #Unit.OpticType opticType - + --- A radar. - -- @type Unit.Radar + -- @type Unit.Radar -- @extends #Unit.Sensor -- @field #Distance detectionDistanceRBM detection distance for RCS=1m^2 in real-beam mapping mode, nil if radar doesn't support surface/land search -- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM -- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search - + --- A radar. - -- @type Unit.Radar.detectionDistanceAir + -- @type Unit.Radar.detectionDistanceAir -- @field #Unit.Radar.detectionDistanceAir.upperHemisphere upperHemisphere -- @field #Unit.Radar.detectionDistanceAir.lowerHemisphere lowerHemisphere - + --- A radar. -- @type Unit.Radar.detectionDistanceAir.upperHemisphere -- @field #Distance headOn -- @field #Distance tailOn - + --- A radar. - -- @type Unit.Radar.detectionDistanceAir.lowerHemisphere + -- @type Unit.Radar.detectionDistanceAir.lowerHemisphere -- @field #Distance headOn -- @field #Distance tailOn - + --- An IRST. - -- @type Unit.IRST + -- @type Unit.IRST -- @extends #Unit.Sensor -- @field #Distance detectionDistanceIdle detection of tail-on target with heat signature = 1 in upper hemisphere, engines are in idle -- @field #Distance detectionDistanceMaximal ..., engines are in maximal mode -- @field #Distance detectionDistanceAfterburner ..., engines are in afterburner mode - + --- An RWR. - -- @type Unit.RWR + -- @type Unit.RWR -- @extends #Unit.Sensor - + --- table that stores all unit sensors. -- TODO @type Sensors - -- - - - --- Returns unit object by the name assigned to the unit in Mission Editor. If there is unit with such name or the unit is destroyed the function will return nil. The function provides access to non-activated units too. + -- + + + --- Returns unit object by the name assigned to the unit in Mission Editor. If there is unit with such name or the unit is destroyed the function will return nil. The function provides access to non-activated units too. -- @function [parent=#Unit] getByName -- @param #string name -- @return #Unit - + --- Returns if the unit is activated. -- @function [parent=#Unit] isActive -- @param #Unit self -- @return #boolean - + --- Returns name of the player that control the unit or nil if the unit is controlled by A.I. -- @function [parent=#Unit] getPlayerName -- @param #Unit self -- @return #string - + --- returns the unit's unique identifier. -- @function [parent=#Unit] getID -- @param #Unit self -- @return #Unit.ID - - + + --- 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. If any unit in the group is destroyed, the numbers of another units will not be changed. -- @function [parent=#Unit] getNumber -- @param #Unit self -- @return #number - + --- Returns controller of the unit if it exist and nil otherwise -- @function [parent=#Unit] getController -- @param #Unit self -- @return #Controller - + --- Returns the unit's group if it exist and nil otherwise -- @function [parent=#Unit] getGroup -- @param #Unit self -- @return #Group - + --- Returns the unit's callsign - the localized string. -- @function [parent=#Unit] getCallsign -- @param #Unit self -- @return #string - + --- Returns the unit's health. Dead units has health <= 1.0 -- @function [parent=#Unit] getLife -- @param #Unit self -- @return #number - + --- returns the unit's initial health. -- @function [parent=#Unit] getLife0 -- @param #Unit self -- @return #number - + --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. -- @function [parent=#Unit] getFuel -- @param #Unit self -- @return #number - + --- Returns the unit ammunition. -- @function [parent=#Unit] getAmmo -- @param #Unit self @@ -1328,12 +1381,12 @@ do -- Unit -- @function [parent=#Unit] getDescentCapacity -- @param #Unit self -- @return #number Number of soldiers that embark. - - --- Returns the unit sensors. + + --- Returns the unit sensors. -- @function [parent=#Unit] getSensors -- @param #Unit self -- @return #Unit.Sensors - + --- Returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. -- @function [parent=#Unit] hasSensors -- @param #Unit self @@ -1347,25 +1400,25 @@ do -- Unit -- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) -- If no additional parameters are specified the function returns true if the unit has at least one sensor of specified type. -- If sensor type is not specified the function returns true if the unit has at least one sensor of any type. - -- - + -- + --- returns two values: -- First value indicates if at least one of the unit's radar(s) is on. - -- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. + -- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. -- @function [parent=#Unit] getRadar -- @param #Unit self -- @return #boolean, Wrapper.Object#Object - - --- Returns unit descriptor. Descriptor type depends on unit category. + + --- Returns unit descriptor. Descriptor type depends on unit category. -- @function [parent=#Unit] getDesc -- @param #Unit self -- @return #Unit.Desc - + --- GROUND - Switch on/off radar emissions -- @function [parent=#Unit] enableEmission -- @param #Unit self -- @param #boolean switch - + Unit = {} --#Unit end -- Unit @@ -1375,9 +1428,9 @@ do -- Group --- Represents group of Units. -- @type Group - -- @field #ID ID Identifier of a group. It is assigned to a group by Mission Editor automatically. - -- @field #Group.Category Category Enum contains identifiers of group types. - + -- @field #ID ID Identifier of a group. It is assigned to a group by Mission Editor automatically. + -- @field #Group.Category Category Enum contains identifiers of group types. + --- Enum contains identifiers of group types. -- @type Group.Category -- @field AIRPLANE @@ -1385,76 +1438,76 @@ do -- Group -- @field GROUND -- @field SHIP -- @field TRAIN - + -- Static Functions - - --- Returns group by the name assigned to the group in Mission Editor. + + --- Returns group by the name assigned to the group in Mission Editor. -- @function [parent=#Group] getByName -- @param #string name -- @return #Group - + -- Member Functions - - --- returns true if the group exist or false otherwise. + + --- returns true if the group exist or false otherwise. -- @function [parent=#Group] isExist - -- @param #Group self + -- @param #Group self -- @return #boolean - + --- Destroys the group and all of its units. -- @function [parent=#Group] destroy - -- @param #Group self - + -- @param #Group self + --- Returns category of the group. -- @function [parent=#Group] getCategory - -- @param #Group self + -- @param #Group self -- @return #Group.Category - + --- Returns the coalition of the group. -- @function [parent=#Group] getCoalition - -- @param #Group self + -- @param #Group self -- @return #coalition.side - + --- Returns the group's name. This is the same name assigned to the group in Mission Editor. -- @function [parent=#Group] getName - -- @param #Group self + -- @param #Group self -- @return #string - + --- Returns the group identifier. -- @function [parent=#Group] getID - -- @param #Group self + -- @param #Group self -- @return #ID - + --- Returns the unit with number unitNumber. If the unit is not exists the function will return nil. -- @function [parent=#Group] getUnit - -- @param #Group self + -- @param #Group self -- @param #number unitNumber -- @return #Unit - + --- Returns current size of the group. If some of the units will be destroyed, As units are destroyed the size of the group will be changed. -- @function [parent=#Group] getSize - -- @param #Group self + -- @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. -- @function [parent=#Group] getInitialSize - -- @param #Group self + -- @param #Group self -- @return #number - + --- Returns array of the units present in the group now. Destroyed units will not be enlisted at all. -- @function [parent=#Group] getUnits - -- @param #Group self + -- @param #Group self -- @return #list<#Unit> array of Units - - --- Returns controller of the group. + + --- Returns controller of the group. -- @function [parent=#Group] getController - -- @param #Group self + -- @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 @@ -1503,7 +1556,7 @@ do -- AI -- @field #AI.Skill Skill -- @field #AI.Task Task -- @field #AI.Option Option - + --- [https://wiki.hoggitworld.com/view/DCS_enum_AI](https://wiki.hoggitworld.com/view/DCS_enum_AI) -- @type AI.Skill -- @field AVERAGE @@ -1512,7 +1565,7 @@ do -- AI -- @field EXCELLENT -- @field PLAYER -- @field CLIENT - + --- [https://wiki.hoggitworld.com/view/DCS_enum_AI](https://wiki.hoggitworld.com/view/DCS_enum_AI) -- @type AI.Task -- @field #AI.Task.WeaponExpend WeaponExpend @@ -1522,7 +1575,7 @@ do -- AI -- @field #AI.Task.TurnMethod TurnMethod -- @field #AI.Task.AltitudeType AltitudeType -- @field #AI.Task.VehicleFormation VehicleFormation - + --- [https://wiki.hoggitworld.com/view/DCS_enum_AI](https://wiki.hoggitworld.com/view/DCS_enum_AI) -- @type AI.Task.WeaponExpend -- @field ONE @@ -1531,12 +1584,12 @@ do -- AI -- @field QUARTER -- @field HALF -- @field ALL - + --- [https://wiki.hoggitworld.com/view/DCS_enum_AI](https://wiki.hoggitworld.com/view/DCS_enum_AI) -- @type AI.Task.OrbitPattern -- @field CIRCLE -- @field RACE_TRACK - + --- [https://wiki.hoggitworld.com/view/DCS_enum_AI](https://wiki.hoggitworld.com/view/DCS_enum_AI) -- @type AI.Task.Designation -- @field NO @@ -1544,7 +1597,7 @@ do -- AI -- @field WP -- @field IR_POINTER -- @field LASER - + --- -- @type AI.Task.WaypointType -- @field TAKEOFF @@ -1552,17 +1605,17 @@ do -- AI -- @field TURNING_POINT -- @field TAKEOFF_PARKING_HOT -- @field LAND - + --- -- @type AI.Task.TurnMethod -- @field FLY_OVER_POINT -- @field FIN_POINT - + --- -- @type AI.Task.AltitudeType -- @field BARO -- @field RADIO - + --- -- @type AI.Task.VehicleFormation -- @field OFF_ROAD @@ -1573,29 +1626,29 @@ do -- AI -- @field VEE -- @field ECHELON_LEFT -- @field ECHELON_RIGHT - + --- -- @type AI.Option -- @field #AI.Option.Air Air -- @field #AI.Option.Ground Ground -- @field #AI.Option.Naval Naval - + --- -- @type AI.Option.Air -- @field #AI.Option.Air.id id -- @field #AI.Option.Air.val val - + --- -- @type AI.Option.Ground -- @field #AI.Option.Ground.id id -- @field #AI.Option.Ground.val val -- @field #AI.Option.Ground.mid mid -- @field #AI.Option.Ground.mval mval - -- + -- -- @type AI.Option.Naval -- @field #AI.Option.Naval.id id -- @field #AI.Option.Naval.val val - + --- -- @type AI.Option.Air.id -- @field NO_OPTION @@ -1626,7 +1679,7 @@ do -- AI -- @field #AI.Option.Air.val.REACTION_ON_THREAT REACTION_ON_THREAT -- @field #AI.Option.Air.val.RADAR_USING RADAR_USING -- @field #AI.Option.Air.val.FLARE_USING FLARE_USING - + --- -- @type AI.Option.Air.val.ROE -- @field WEAPON_FREE @@ -1634,36 +1687,36 @@ do -- AI -- @field OPEN_FIRE -- @field RETURN_FIRE -- @field WEAPON_HOLD - - --- + + --- -- @type AI.Option.Air.val.REACTION_ON_THREAT -- @field NO_REACTION -- @field PASSIVE_DEFENCE -- @field EVADE_FIRE -- @field BYPASS_AND_ESCAPE -- @field ALLOW_ABORT_MISSION - + --- -- @type AI.Option.Air.val.RADAR_USING -- @field NEVER -- @field FOR_ATTACK_ONLY -- @field FOR_SEARCH_IF_REQUIRED -- @field FOR_CONTINUOUS_SEARCH - + --- -- @type AI.Option.Air.val.FLARE_USING -- @field NEVER -- @field AGAINST_FIRED_MISSILE -- @field WHEN_FLYING_IN_SAM_WEZ -- @field WHEN_FLYING_NEAR_ENEMIES - + --- -- @type AI.Option.Air.val.ECM_USING -- @field NEVER_USE -- @field USE_IF_ONLY_LOCK_BY_RADAR -- @field USE_IF_DETECTED_LOCK_BY_RADAR -- @field ALWAYS_USE - + --- -- @type AI.Option.Air.val.MISSILE_ATTACK -- @field MAX_RANGE @@ -1681,56 +1734,57 @@ do -- AI -- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE} -- @field ENGAGE_AIR_WEAPONS -- @field AC_ENGAGEMENT_RANGE_RESTRICTION - + -- @field EVASION_OF_ARM + --- -- @type AI.Option.Ground.mid -- Moose added -- @field RESTRICT_AAA_MIN 27 -- @field RESTRICT_AAA_MAX 29 -- @field RESTRICT_TARGETS @{#AI.Option.Ground.mval.ENGAGE_TARGETS} 28 - + --- -- @type AI.Option.Ground.val -- @field #AI.Option.Ground.val.ROE ROE -- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE -- @field #AI.Option.Ground.val.ENGAGE_TARGETS RESTRICT_TARGETS - + --- -- @type AI.Option.Ground.val.ROE -- @field OPEN_FIRE -- @field RETURN_FIRE -- @field WEAPON_HOLD - + --- -- @type AI.Option.Ground.mval -- Moose added -- @field #AI.Option.Ground.mval.ENGAGE_TARGETS ENGAGE_TARGETS - + --- -- @type AI.Option.Ground.mval.ENGAGE_TARGETS -- Moose added -- @field ANY_TARGET -- 0 -- @field AIR_UNITS_ONLY -- 1 -- @field GROUND_UNITS_ONLY -- 2 - + --- -- @type AI.Option.Ground.val.ALARM_STATE -- @field AUTO -- @field GREEN -- @field RED - + --- -- @type AI.Option.Naval.id -- @field NO_OPTION -- @field ROE - + --- -- @type AI.Option.Naval.val -- @field #AI.Option.Naval.val.ROE ROE - + --- -- @type AI.Option.Naval.val.ROE -- @field OPEN_FIRE -- @field RETURN_FIRE -- @field WEAPON_HOLD - + AI = {} --#AI end -- AI diff --git a/Moose Development/Moose/Functional/ATC_Ground.lua b/Moose Development/Moose/Functional/ATC_Ground.lua index cdb90e712..7d1669d51 100644 --- a/Moose Development/Moose/Functional/ATC_Ground.lua +++ b/Moose Development/Moose/Functional/ATC_Ground.lua @@ -18,7 +18,7 @@ -- ### Author: FlightControl - Framework Design & Programming -- ### Refactoring to use the Runway auto-detection: Applevangelist -- @date August 2022 --- Last Update Nov 2023 +-- Last Update Feb 2025 -- -- === -- @@ -416,7 +416,7 @@ end -- @field #ATC_GROUND_UNIVERSAL ATC_GROUND_UNIVERSAL = { ClassName = "ATC_GROUND_UNIVERSAL", - Version = "0.0.1", + Version = "0.0.2", SetClient = nil, Airbases = nil, AirbaseList = nil, @@ -441,17 +441,25 @@ function ATC_GROUND_UNIVERSAL:New(AirbaseList) self:T( { self.ClassName } ) self.Airbases = {} - - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.Airbases[_name]={} - end self.AirbaseList = AirbaseList if not self.AirbaseList then self.AirbaseList = {} - for _name,_ in pairs(_DATABASE.AIRBASES) do - self.AirbaseList[_name]=_name + for _name,_base in pairs(_DATABASE.AIRBASES) do + -- DONE exclude FARPS and Ships + if _base and _base.isAirdrome == true then + self.AirbaseList[_name]=_name + self.Airbases[_name]={} + end + end + else + for _,_name in pairs(AirbaseList) do + -- DONE exclude FARPS and Ships + local airbase = _DATABASE:FindAirbase(_name) + if airbase and (airbase.isAirdrome == true) then + self.Airbases[_name]={} + end end end @@ -721,14 +729,18 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() if NotInRunwayZone then + local Taxi = Client:GetState( self, "Taxi" ) + if IsOnGround then - local Taxi = Client:GetState( self, "Taxi" ) + self:T( Taxi ) if Taxi == false then local Velocity = VELOCITY:New( AirbaseMeta.KickSpeed or self.KickSpeed ) Client:Message( "Welcome to " .. AirbaseID .. ". The maximum taxiing speed is " .. Velocity:ToString() , 20, "ATC" ) Client:SetState( self, "Taxi", true ) + Client:SetState( self, "Speeding", false ) + Client:SetState( self, "Warnings", 0 ) end -- TODO: GetVelocityKMH function usage @@ -737,7 +749,7 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() local IsAboveRunway = Client:IsAboveRunway() self:T( {IsAboveRunway, IsOnGround, Velocity:Get() }) - if IsOnGround then + if IsOnGround and not Taxi then local Speeding = false if AirbaseMeta.MaximumKickSpeed then if Velocity:Get() > AirbaseMeta.MaximumKickSpeed then @@ -749,15 +761,17 @@ function ATC_GROUND_UNIVERSAL:_AirbaseMonitor() end end if Speeding == true then - MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. - " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() - Client:Destroy() - Client:SetState( self, "Speeding", false ) - Client:SetState( self, "Warnings", 0 ) + --MESSAGE:New( "Penalty! Player " .. Client:GetPlayerName() .. + -- " has been kicked, due to a severe airbase traffic rule violation ...", 10, "ATC" ):ToAll() + --Client:Destroy() + Client:SetState( self, "Speeding", true ) + local SpeedingWarnings = Client:GetState( self, "Warnings" ) + Client:SetState( self, "Warnings", SpeedingWarnings + 1 ) + Client:Message( "Warning " .. SpeedingWarnings .. "/3! Airbase traffic rule violation! Slow down now! Your speed is " .. + Velocity:ToString(), 5, "ATC" ) end end - - + if IsOnGround then local Speeding = false @@ -1035,23 +1049,23 @@ end -- The following airbases are monitored at the Nevada region. -- Use the @{Wrapper.Airbase#AIRBASE.Nevada} enumeration to select the airbases to be monitored. -- --- * `AIRBASE.Nevada.Beatty_Airport` --- * `AIRBASE.Nevada.Boulder_City_Airport` --- * `AIRBASE.Nevada.Creech_AFB` +-- * `AIRBASE.Nevada.Beatty` +-- * `AIRBASE.Nevada.Boulder_City` +-- * `AIRBASE.Nevada.Creech` -- * `AIRBASE.Nevada.Echo_Bay` --- * `AIRBASE.Nevada.Groom_Lake_AFB` --- * `AIRBASE.Nevada.Henderson_Executive_Airport` --- * `AIRBASE.Nevada.Jean_Airport` --- * `AIRBASE.Nevada.Laughlin_Airport` +-- * `AIRBASE.Nevada.Groom_Lake` +-- * `AIRBASE.Nevada.Henderson_Executive` +-- * `AIRBASE.Nevada.Jean` +-- * `AIRBASE.Nevada.Laughlin` -- * `AIRBASE.Nevada.Lincoln_County` --- * `AIRBASE.Nevada.McCarran_International_Airport` +-- * `AIRBASE.Nevada.McCarran_International` -- * `AIRBASE.Nevada.Mesquite` --- * `AIRBASE.Nevada.Mina_Airport` --- * `AIRBASE.Nevada.Nellis_AFB` +-- * `AIRBASE.Nevada.Mina` +-- * `AIRBASE.Nevada.Nellis` -- * `AIRBASE.Nevada.North_Las_Vegas` --- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip` --- * `AIRBASE.Nevada.Tonopah_Airport` --- * `AIRBASE.Nevada.Tonopah_Test_Range_Airfield` +-- * `AIRBASE.Nevada.Pahute_Mesa` +-- * `AIRBASE.Nevada.Tonopah` +-- * `AIRBASE.Nevada.Tonopah_Test_Range` -- -- # Installation -- @@ -1088,10 +1102,10 @@ end -- -- -- Monitor specific airbases. -- ATC_Ground = ATC_GROUND_NEVADA:New( --- { AIRBASE.Nevada.Laughlin_Airport, +-- { AIRBASE.Nevada.Laughlin, -- AIRBASE.Nevada.Lincoln_County, -- AIRBASE.Nevada.North_Las_Vegas, --- AIRBASE.Nevada.McCarran_International_Airport +-- AIRBASE.Nevada.McCarran_International -- } -- ) -- @@ -1330,33 +1344,33 @@ end -- The following airbases are monitored at the PersianGulf region. -- Use the @{Wrapper.Airbase#AIRBASE.PersianGulf} enumeration to select the airbases to be monitored. -- --- * `AIRBASE.PersianGulf.Abu_Musa_Island_Airport` --- * `AIRBASE.PersianGulf.Al_Dhafra_AB` +-- * `AIRBASE.PersianGulf.Abu_Musa_Island` +-- * `AIRBASE.PersianGulf.Al_Dhafra_AFB` -- * `AIRBASE.PersianGulf.Al_Maktoum_Intl` --- * `AIRBASE.PersianGulf.Al_Minhad_AB` +-- * `AIRBASE.PersianGulf.Al_Minhad_AFB` -- * `AIRBASE.PersianGulf.Bandar_Abbas_Intl` -- * `AIRBASE.PersianGulf.Bandar_Lengeh` -- * `AIRBASE.PersianGulf.Dubai_Intl` -- * `AIRBASE.PersianGulf.Fujairah_Intl` -- * `AIRBASE.PersianGulf.Havadarya` --- * `AIRBASE.PersianGulf.Kerman_Airport` +-- * `AIRBASE.PersianGulf.Kerman` -- * `AIRBASE.PersianGulf.Khasab` --- * `AIRBASE.PersianGulf.Lar_Airbase` +-- * `AIRBASE.PersianGulf.Lar` -- * `AIRBASE.PersianGulf.Qeshm_Island` -- * `AIRBASE.PersianGulf.Sharjah_Intl` --- * `AIRBASE.PersianGulf.Shiraz_International_Airport` +-- * `AIRBASE.PersianGulf.Shiraz_Intl` -- * `AIRBASE.PersianGulf.Sir_Abu_Nuayr` -- * `AIRBASE.PersianGulf.Sirri_Island` -- * `AIRBASE.PersianGulf.Tunb_Island_AFB` -- * `AIRBASE.PersianGulf.Tunb_Kochak` --- * `AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport` --- * `AIRBASE.PersianGulf.Bandar_e_Jask_airfield` --- * `AIRBASE.PersianGulf.Abu_Dhabi_International_Airport` --- * `AIRBASE.PersianGulf.Al_Bateen_Airport` --- * `AIRBASE.PersianGulf.Kish_International_Airport` --- * `AIRBASE.PersianGulf.Al_Ain_International_Airport` --- * `AIRBASE.PersianGulf.Lavan_Island_Airport` --- * `AIRBASE.PersianGulf.Jiroft_Airport` +-- * `AIRBASE.PersianGulf.Sas_Al_Nakheel` +-- * `AIRBASE.PersianGulf.Bandar_e_Jask` +-- * `AIRBASE.PersianGulf.Abu_Dhabi_Intl` +-- * `AIRBASE.PersianGulf.Al_Bateen` +-- * `AIRBASE.PersianGulf.Kish_Intl` +-- * `AIRBASE.PersianGulf.Al_Ain_Intl` +-- * `AIRBASE.PersianGulf.Lavan_Island` +-- * `AIRBASE.PersianGulf.Jiroft` -- -- # Installation -- @@ -1391,8 +1405,8 @@ end -- AirbasePoliceCaucasus = ATC_GROUND_PERSIANGULF:New() -- -- ATC_Ground = ATC_GROUND_PERSIANGULF:New( --- { AIRBASE.PersianGulf.Kerman_Airport, --- AIRBASE.PersianGulf.Al_Minhad_AB +-- { AIRBASE.PersianGulf.Kerman, +-- AIRBASE.PersianGulf.Al_Minhad_AFB -- } -- ) -- @@ -1441,11 +1455,10 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds ) self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, RepeatScanSeconds ) end - - -- @type ATC_GROUND_MARIANAISLANDS +--- +-- @type ATC_GROUND_MARIANAISLANDS -- @extends #ATC_GROUND - --- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND} -- diff --git a/Moose Development/Moose/Functional/AmmoTruck.lua b/Moose Development/Moose/Functional/AmmoTruck.lua index e59ab2872..d4181f1c8 100644 --- a/Moose Development/Moose/Functional/AmmoTruck.lua +++ b/Moose Development/Moose/Functional/AmmoTruck.lua @@ -77,7 +77,7 @@ -- ammotruck.monitor = -60 -- 1 minute - AMMOTRUCK checks run every one minute -- ammotruck.routeonroad = true -- Trucks will **try** to drive on roads -- ammotruck.usearmygroup = false -- If true, will make use of ARMYGROUP in the background (if used in DEV branch) --- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited +-- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited -- -- ## 3 FSM Events to shape mission -- diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index c1745cac0..a9dc78ddb 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -619,63 +619,148 @@ ARTY.WeaponType={ } --- Database of common artillery unit properties. +-- @type ARTY.dbitem +-- @field #string displayname Name displayed in ME. +-- @field #number minrange Minimum firing range in meters. +-- @field #number maxrange Maximum firing range in meters. +-- @field #number reloadtime Reload time in seconds. + +--- Database of common artillery unit properties. +-- Table key is the "type name" and table value is and `ARTY.dbitem`. -- @type ARTY.db ARTY.db={ - ["2B11 mortar"] = { -- type "2B11 mortar" - minrange = 500, -- correct? - maxrange = 7000, -- 7 km - reloadtime = 30, -- 30 sec + ["LeFH_18-40-105"] = { + displayname = "FH LeFH-18 105mm", -- name displayed in the ME + minrange = 500, -- min range (green circle) in meters + maxrange = 10500, -- max range (red circle) in meters + reloadtime = nil, -- reload time in seconds }, - ["SPH 2S1 Gvozdika"] = { -- type "SAU Gvozdika" - minrange = 300, -- correct? - maxrange = 15000, -- 15 km - reloadtime = nil, -- unknown + ["M2A1-105"] = { + displayname = "FH M2A1 105mm", + minrange = 500, + maxrange = 11500, + reloadtime = nil, }, - ["SPH 2S19 Msta"] = { --type "SAU Msta", alias "2S19 Msta" - minrange = 300, -- correct? - maxrange = 23500, -- 23.5 km - reloadtime = nil, -- unknown + ["Pak40"] = { + displayname = "FH Pak 40 75mm", + minrange = 500, + maxrange = 3000, + reloadtime = nil, + }, + ["L118_Unit"] = { + displayname = "L118 Light Artillery Gun", + minrange = 500, + maxrange = 17500, + reloadtime = nil, }, - ["SPH 2S3 Akatsia"] = { -- type "SAU Akatsia", alias "2S3 Akatsia" - minrange = 300, -- correct? - maxrange = 17000, -- 17 km - reloadtime = nil, -- unknown + ["Smerch"] = { + displayname = "MLRS 9A52 Smerch CM 300mm", + minrange = 20000, + maxrange = 70000, + reloadtime = 2160, }, - ["SPH 2S9 Nona"] = { --type "SAU 2-C9" - minrange = 500, -- correct? - maxrange = 7000, -- 7 km - reloadtime = nil, -- unknown + ["Smerch_HE"] = { + displayname = "MLRS 9A52 Smerch HE 300mm", + minrange = 20000, + maxrange = 70000, + reloadtime = 2160, }, - ["SPH M109 Paladin"] = { -- type "M-109", alias "M109" - minrange = 300, -- correct? - maxrange = 22000, -- 22 km - reloadtime = nil, -- unknown + ["Uragan_BM-27"] = { + displayname = "MLRS 9K57 Uragan BM-27 220mm", + minrange = 11500, + maxrange = 35800, + reloadtime = 840, }, - ["SpGH Dana"] = { -- type "SpGH_Dana" - minrange = 300, -- correct? - maxrange = 18700, -- 18.7 km - reloadtime = nil, -- unknown + ["Grad-URAL"] = { + displayname = "MLRS BM-21 Grad 122mm", + minrange = 5000, + maxrange = 19000, + reloadtime = 420, }, - ["MLRS BM-21 Grad"] = { --type "Grad-URAL", alias "MLRS BM-21 Grad" - minrange = 5000, -- 5 km - maxrange = 19000, -- 19 km - reloadtime = 420, -- 7 min + ["HL_B8M1"] = { + displayname = "MLRS HL with B8M1 80mm", + minrange = 500, + maxrange = 5000, + reloadtime = nil, }, - ["MLRS 9K57 Uragan BM-27"] = { -- type "Uragan_BM-27" - minrange = 11500, -- 11.5 km - maxrange = 35800, -- 35.8 km - reloadtime = 840, -- 14 min + ["tt_B8M1"] = { + displayname = "MLRS LC with B8M1 80mm", + minrange = 500, + maxrange = 5000, + reloadtime = nil, }, - ["MLRS 9A52 Smerch"] = { -- type "Smerch" - minrange = 20000, -- 20 km - maxrange = 70000, -- 70 km - reloadtime = 2160, -- 36 min + ["MLRS"] = { + displayname = "MLRS M270 227mm", + minrange = 10000, + maxrange = 32000, + reloadtime = 540, }, - ["MLRS M270"] = { --type "MRLS", alias "M270 MRLS" - minrange = 10000, -- 10 km - maxrange = 32000, -- 32 km - reloadtime = 540, -- 9 min + ["2B11 mortar"] = { + displayname = "Mortar 2B11 120mm", + minrange = 500, + maxrange = 7000, + reloadtime = 30, }, + ["PLZ05"] = { + displayname = "PLZ-05", + minrange = 500, + maxrange = 23500, + reloadtime = nil, + }, + ["SAU Gvozdika"] = { + displayname = "SPH 2S1 Gvozdika 122mm", + minrange = 300, + maxrange = 15000, + reloadtime = nil, + }, + ["SAU Msta"] = { + displayname = "SPH 2S19 Msta 152mm", + minrange = 300, + maxrange = 23500, + reloadtime = nil, + }, + ["SAU Akatsia"] = { + displayname = "SPH 2S3 Akatsia 152mm", + minrange = 300, + maxrange = 17000, + reloadtime = nil, + }, + ["SpGH_Dana"] = { + displayname = "SPH Dana vz77 152mm", + minrange = 300, + maxrange = 18700, + reloadtime = nil, + }, + ["M-109"] = { + displayname = "SPH M109 Paladin 155mm", + minrange = 300, + maxrange = 22000, + reloadtime = nil, + }, + ["M12_GMC"] = { + displayname = "SPH M12 GMC 155mm", + minrange = 300, + maxrange = 18200, + reloadtime = nil, + }, + ["Wespe124"] = { + displayname = "SPH Sd.Kfz.124 Wespe 105mm", + minrange = 300, + maxrange = 7000, + reloadtime = nil, + }, + ["T155_Firtina"] = { + displayname = "SPH T155 Firtina 155mm", + minrange = 300, + maxrange = 41000, + reloadtime = nil, + }, + ["SAU 2-C9"] = { + displayname = "SPM 2S9 Nona 120mm M", + minrange = 500, + maxrange = 7000, + reloadtime = nil, + }, } --- Target. @@ -695,7 +780,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.3.1" +ARTY.version="1.3.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -797,8 +882,8 @@ function ARTY:New(group, alias) -- Maximum speed in km/h. self.SpeedMax=group:GetSpeedMax() - -- Group is mobile or not (e.g. mortars). - if self.SpeedMax>1 then + -- Group is mobile or not (e.g. mortars). Some immobile units have a speed of 1 m/s = 3.6 km/h. So we check this number. + if self.SpeedMax>3.6 then self.ismobile=true else self.ismobile=false @@ -1923,7 +2008,7 @@ function ARTY:onafterStart(Controllable, From, Event, To) end -- Check if we have and arty type that is in the DB. - local _dbproperties=self:_CheckDB(self.DisplayName) + local _dbproperties=self:_CheckDB(self.Type) self:T({dbproperties=_dbproperties}) if _dbproperties~=nil then for property,value in pairs(_dbproperties) do @@ -1969,8 +2054,8 @@ function ARTY:onafterStart(Controllable, From, Event, To) text=text..string.format("Type = %s\n", self.Type) text=text..string.format("Display Name = %s\n", self.DisplayName) text=text..string.format("Number of units = %d\n", self.IniGroupStrength) - text=text..string.format("Speed max = %d km/h\n", self.SpeedMax) - text=text..string.format("Speed default = %d km/h\n", self.Speed) + text=text..string.format("Speed max = %.1f km/h\n", self.SpeedMax) + text=text..string.format("Speed default = %.1f km/h\n", self.Speed) text=text..string.format("Is mobile = %s\n", tostring(self.ismobile)) text=text..string.format("Is cargo = %s\n", tostring(self.iscargo)) text=text..string.format("Min range = %.1f km\n", self.minrange/1000) @@ -2301,12 +2386,12 @@ function ARTY:OnEventShot(EventData) self.Nukes=self.Nukes-1 end - -- Decrease available illuminatin shells because we just fired one. + -- Decrease available illumination shells because we just fired one. if self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells then self.Nillu=self.Nillu-1 end - -- Decrease available illuminatin shells because we just fired one. + -- Decrease available smoke shells because we just fired one. if self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells then self.Nsmoke=self.Nsmoke-1 end @@ -3049,7 +3134,7 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) local nfire=Narty local _type="shots" if target.weapontype==ARTY.WeaponType.Auto then - nfire=Narty + nfire=Nammo -- We take everything that is available _type="shots" elseif target.weapontype==ARTY.WeaponType.Cannon then nfire=Narty @@ -3070,6 +3155,8 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) nfire=Nmissiles _type="cruise missiles" end + + --env.info(string.format("FF type=%s, Nrockets=%d, Nfire=%d target.nshells=%d", _type, Nrockets, nfire, target.nshells)) -- Adjust if less than requested ammo is left. target.nshells=math.min(target.nshells, nfire) @@ -3717,51 +3804,6 @@ function ARTY:_NuclearBlast(_coord) ignite(_fires) end ---[[ - local ZoneNuke=ZONE_RADIUS:New("Nukezone", _coord:GetVec2(), 2000) - - -- Scan for Scenery objects. - ZoneNuke:Scan(Object.Category.SCENERY) - - -- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group. - local scenery={} - - for SceneryTypeName, SceneryData in pairs(ZoneNuke:GetScannedScenery()) do - for SceneryName, SceneryObject in pairs(SceneryData) do - - local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY - - -- Position of the scenery object. - local spos=SceneryObject:GetCoordinate() - - -- Distance from group to impact point. - local distance= spos:Get2DDistance(_coord) - - -- Place markers on every possible scenery object. - if self.Debug then - local MarkerID=spos:MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(), SceneryObject:GetTypeName())) - local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS()) - self:T2(SUPPRESSION.id..text) - end - - -- Add to table. - table.insert(scenery, {object=SceneryObject, distance=distance}) - - --SceneryObject:Destroy() - end - end - - -- Sort scenery wrt to distance from impact point. --- local _sort = function(a,b) return a.distance < b.distance end --- table.sort(scenery,_sort) - --- for _,object in pairs(scenery) do --- local sobject=object -- Wrapper.Scenery#SCENERY --- sobject:Destroy() --- end - -]] - end --- Route group to a certain point. diff --git a/Moose Development/Moose/Functional/Autolase.lua b/Moose Development/Moose/Functional/Autolase.lua index 612162389..9fed56cfc 100644 --- a/Moose Development/Moose/Functional/Autolase.lua +++ b/Moose Development/Moose/Functional/Autolase.lua @@ -74,7 +74,7 @@ -- @image Designation.JPG -- -- Date: 24 Oct 2021 --- Last Update: May 2024 +-- Last Update: Mar 2025 -- --- Class AUTOLASE -- @type AUTOLASE @@ -89,6 +89,10 @@ -- @field #table playermenus -- @field #boolean smokemenu -- @field #boolean threatmenu +-- @field #number RoundingPrecision +-- @field #table smokeoffset +-- @field #boolean increasegroundawareness +-- @field #number MonitorFrequency -- @extends Ops.Intel#INTEL --- @@ -100,6 +104,9 @@ AUTOLASE = { alias = "", debug = false, smokemenu = true, + RoundingPrecision = 0, + increasegroundawareness = true, + MonitorFrequency = 30, } --- Laser spot info @@ -118,7 +125,7 @@ AUTOLASE = { --- AUTOLASE class version. -- @field #string version -AUTOLASE.version = "0.1.25" +AUTOLASE.version = "0.1.31" ------------------------------------------------------------------- -- Begin Functional.Autolase.lua @@ -191,6 +198,7 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.reporttimelong = 30 self.smoketargets = false self.smokecolor = SMOKECOLOR.Red + self.smokeoffset = nil self.notifypilots = true self.targetsperrecce = {} self.RecceUnits = {} @@ -207,6 +215,11 @@ function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet) self.playermenus = {} self.smokemenu = true self.threatmenu = true + self.RoundingPrecision = 0 + self.increasegroundawareness = true + self.MonitorFrequency = 30 + + self:EnableSmokeMenu({Angle=math.random(0,359),Distance=math.random(10,20)}) -- Set some string id for output to DCS.log file. self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown") @@ -309,16 +322,41 @@ end -- Helper Functions ------------------------------------------------------------------- +--- [User] When using Monitor, set the frequency here in which the report will appear +-- @param #AUTOLASE self +-- @param #number Seconds Run the report loop every number of seconds defined here. +-- @return #AUTOLASE self +function AUTOLASE:SetMonitorFrequency(Seconds) + self.MonitorFrequency = Seconds or 30 + return self +end + --- [User] Set a table of possible laser codes. --- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 } . +-- Each new RECCE can select a code from this table, default is { 1688, 1130, 4785, 6547, 1465, 4578 }. -- @param #AUTOLASE self -- @param #list<#number> LaserCodes --- @return #AUTOLASE +-- @return #AUTOLASE self function AUTOLASE:SetLaserCodes( LaserCodes ) self.LaserCodes = ( type( LaserCodes ) == "table" ) and LaserCodes or { LaserCodes } return self end +--- [User] Improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:EnableImproveGroundUnitsDetection() + self.increasegroundawareness = true + return self +end + +--- [User] Do not improve ground unit detection by using a zone scan and LOS check. +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:DisableImproveGroundUnitsDetection() + self.increasegroundawareness = false + return self +end + --- (Internal) Function to set pilot menu. -- @param #AUTOLASE self -- @return #AUTOLASE self @@ -600,11 +638,26 @@ function AUTOLASE:SetSmokeTargets(OnOff,Color) return self end +--- (User) Function to set rounding precision for BR distance output. +-- @param #AUTOLASE self +-- @param #number IDP Rounding precision before/after the decimal sign. Defaults to zero. Positive values round right of the decimal sign, negative ones left of the decimal sign. +-- @return #AUTOLASE self +function AUTOLASE:SetRoundingPrecsion(IDP) + self.RoundingPrecision = IDP or 0 + return self +end + --- (User) Show the "Switch smoke target..." menu entry for pilots. On by default. -- @param #AUTOLASE self +-- @param #table Offset (Optional) Define an offset for the smoke, i.e. not directly on the unit itself, angle is degrees and distance is meters. E.g. `autolase:EnableSmokeMenu({Angle=30,Distance=20})` -- @return #AUTOLASE self -function AUTOLASE:EnableSmokeMenu() +function AUTOLASE:EnableSmokeMenu(Offset) self.smokemenu = true + if Offset then + self.smokeoffset = {} + self.smokeoffset.Distance = Offset.Distance or math.random(10,20) + self.smokeoffset.Angle = Offset.Angle or math.random(0,359) + end return self end @@ -613,6 +666,7 @@ end -- @return #AUTOLASE self function AUTOLASE:DisableSmokeMenu() self.smokemenu = false + self.smokeoffset = nil return self end @@ -671,7 +725,8 @@ function AUTOLASE:CleanCurrentLasing() local unit = recce:GetUnit(1) local name = unit:GetName() if not self.RecceUnits[name] then - self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() } + local isground = (unit and unit.IsGround) and unit:IsGround() or false + self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } end end end @@ -757,9 +812,11 @@ function AUTOLASE:ShowStatus(Group,Unit) end local code = self:GetLaserCode(unit:GetName()) report:Add(string.format("Recce %s has code %d",name,code)) + report:Add("---------------") end end report:Add(string.format("Lasing min threat level %d",self.minthreatlevel)) + report:Add("---------------") local lines = 0 for _ind,_entry in pairs(self.CurrentLasing) do local entry = _entry -- #AUTOLASE.LaserSpot @@ -779,22 +836,28 @@ function AUTOLASE:ShowStatus(Group,Unit) if playername then local settings = _DATABASE:GetPlayerSettings(playername) if settings then - self:I("Get Settings ok!") + self:T("Get Settings ok!") if settings:IsA2G_MGRS() then locationstring = entry.coordinate:ToStringMGRS(settings) elseif settings:IsA2G_LL_DMS() then locationstring = entry.coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + locationstring = entry.coordinate:ToStringLLDDM(settings) elseif settings:IsA2G_BR() then - locationstring = entry.coordinate:ToStringBR(Group:GetCoordinate() or Unit:GetCoordinate(),settings) + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = Unit:GetCoordinate() or Group:GetCoordinate() + locationstring = entry.coordinate:ToStringBR(startcoordinate,settings,false,self.RoundingPrecision) end end end - local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring) + local text = string.format("+ %s lasing %s code %d\nat %s",reccename,typename,code,locationstring) report:Add(text) + report:Add("---------------") lines = lines + 1 end if lines == 0 then report:Add("No targets!") + report:Add("---------------") end local reporttime = self.reporttimelong if lines == 0 then reporttime = self.reporttimeshort end @@ -913,6 +976,65 @@ function AUTOLASE:CanLase(Recce,Unit) return canlase end +--- (Internal) Function to do a zone check per ground Recce and make found units and statics "known". +-- @param #AUTOLASE self +-- @return #AUTOLASE self +function AUTOLASE:_Prescient() + -- self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime(), isground=isground } + for _,_data in pairs(self.RecceUnits) do + -- ground units only + if _data.isground and _data.unit and _data.unit:IsAlive() then + local unit = _data.unit -- Wrapper.Unit#UNIT + local position = unit:GetCoordinate() -- Core.Point#COORDINATE + local needsinit = false + if position then + local lastposition = unit:GetProperty("lastposition") + -- property initiated? + if not lastposition then + unit:SetProperty("lastposition",position) + lastposition = position + needsinit = true + end + -- has moved? + local dist = position:Get2DDistance(lastposition) + -- refresh? + local TNow = timer.getAbsTime() + -- check + if dist > 10 or needsinit==true or TNow - _data.timestamp > 29 then + -- init scan objects + local hasunits,hasstatics,_,Units,Statics = position:ScanObjects(self.LaseDistance,true,true,false) + -- loop found units + if hasunits then + self:T(self.lid.."Checking possibly visible UNITs for Recce "..unit:GetName()) + for _,_target in pairs(Units) do -- Wrapper.Unit#UNIT object here + local target = _target -- Wrapper.Unit#UNIT + if target and target:GetCoalition() ~= self.coalition then + if unit:IsLOS(target) and (not target:IsUnitDetected(unit))then + unit:KnowUnit(target,true,true) + end + end + end + end + -- loop found statics + if hasstatics then + self:T(self.lid.."Checking possibly visible STATICs for Recce "..unit:GetName()) + for _,_static in pairs(Statics) do -- DCS static object here + local static = STATIC:Find(_static) + if static and static:GetCoalition() ~= self.coalition then + local IsLOS = position:IsLOS(static:GetCoordinate()) + if IsLOS then + unit:KnowUnit(static,true,true) + end + end + end + end + end + end + end + end + return self +end + ------------------------------------------------------------------- -- FSM Functions ------------------------------------------------------------------- @@ -925,6 +1047,9 @@ end -- @return #AUTOLASE self function AUTOLASE:onbeforeMonitor(From, Event, To) self:T({From, Event, To}) + if self.increasegroundawareness then + self:_Prescient() + end -- Check if group has detected any units. self:UpdateIntel() return self @@ -953,7 +1078,7 @@ function AUTOLASE:onafterMonitor(From, Event, To) local grp = contact.group local coord = contact.position local reccename = contact.recce or "none" - local threat = contact.threatlevel or 0 + local threat = contact.threatlevel or 0 local reccegrp = UNIT:FindByName(reccename) if reccegrp then local reccecoord = reccegrp:GetCoordinate() @@ -1077,6 +1202,9 @@ function AUTOLASE:onafterMonitor(From, Event, To) } if self.smoketargets then local coord = unit:GetCoordinate() + if self.smokeoffset then + coord:Translate(self.smokeoffset.Distance,self.smokeoffset.Angle,true,true) + end local color = self:GetSmokeColor(reccename) coord:Smoke(color) end @@ -1087,7 +1215,8 @@ function AUTOLASE:onafterMonitor(From, Event, To) end end - self:__Monitor(-30) + local nextloop = -self.MonitorFrequency or -30 + self:__Monitor(nextloop) return self end diff --git a/Moose Development/Moose/Functional/CleanUp.lua b/Moose Development/Moose/Functional/CleanUp.lua index 6bf5228f0..7975b3163 100644 --- a/Moose Development/Moose/Functional/CleanUp.lua +++ b/Moose Development/Moose/Functional/CleanUp.lua @@ -52,11 +52,13 @@ -- @module Functional.CleanUp -- @image CleanUp_Airbases.JPG ---- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-) +--- +-- @type CLEANUP_AIRBASE.__ Methods which are not intended for mission designers, but which are used interally by the moose designer :-) -- @field #map<#string,Wrapper.Airbase#AIRBASE> Airbases Map of Airbases. -- @extends Core.Base#BASE ---- @type CLEANUP_AIRBASE +--- +-- @type CLEANUP_AIRBASE -- @extends #CLEANUP_AIRBASE.__ --- Keeps airbases clean, and tries to guarantee continuous airbase operations, even under combat. @@ -93,7 +95,7 @@ CLEANUP_AIRBASE = { -- @field #CLEANUP_AIRBASE.__ CLEANUP_AIRBASE.__ = {} ---- @field #CLEANUP_AIRBASE.__.Airbases +-- @field #CLEANUP_AIRBASE.__.Airbases CLEANUP_AIRBASE.__.Airbases = {} --- Creates the main object which is handling the cleaning of the debris within the given Zone Names. @@ -240,7 +242,8 @@ function CLEANUP_AIRBASE.__:DestroyMissile( MissileObject ) end end ---- @param #CLEANUP_AIRBASE self +--- +-- @param #CLEANUP_AIRBASE self -- @param Core.Event#EVENTDATA EventData function CLEANUP_AIRBASE.__:OnEventBirth( EventData ) self:F( { EventData } ) @@ -354,7 +357,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event ) self:F({Event}) - if Event.IniDCSUnit and Event.IniCategory == Object.Category.UNIT then + if Event.IniDCSUnit and Event.IniUnit and Event.IniCategory == Object.Category.UNIT then if self.CleanUpList[Event.IniDCSUnitName] == nil then if self:IsInAirbase( Event.IniUnit:GetVec2() ) then self:AddForCleanUp( Event.IniUnit, Event.IniDCSUnitName ) @@ -362,7 +365,7 @@ function CLEANUP_AIRBASE.__:EventAddForCleanUp( Event ) end end - if Event.TgtDCSUnit and Event.TgtCategory == Object.Category.UNIT then + if Event.TgtDCSUnit and Event.TgtUnit and Event.TgtCategory == Object.Category.UNIT then if self.CleanUpList[Event.TgtDCSUnitName] == nil then if self:IsInAirbase( Event.TgtUnit:GetVec2() ) then self:AddForCleanUp( Event.TgtUnit, Event.TgtDCSUnitName ) @@ -384,7 +387,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() local CleanUpUnit = CleanUpListData.CleanUpUnit -- Wrapper.Unit#UNIT local CleanUpGroupName = CleanUpListData.CleanUpGroupName - if CleanUpUnit:IsAlive() ~= nil then + if CleanUpUnit and CleanUpUnit:IsAlive() ~= nil then if self:IsInAirbase( CleanUpUnit:GetVec2() ) then @@ -411,7 +414,7 @@ function CLEANUP_AIRBASE.__:CleanUpSchedule() end end -- Clean Units which are waiting for a very long time in the CleanUpZone. - if CleanUpUnit and not CleanUpUnit:GetPlayerName() then + if CleanUpUnit and (CleanUpUnit.GetPlayerName == nil or not CleanUpUnit:GetPlayerName()) then local CleanUpUnitVelocity = CleanUpUnit:GetVelocityKMH() if CleanUpUnitVelocity < 1 then if CleanUpListData.CleanUpMoved then diff --git a/Moose Development/Moose/Functional/ClientWatch.lua b/Moose Development/Moose/Functional/ClientWatch.lua new file mode 100644 index 000000000..65a410041 --- /dev/null +++ b/Moose Development/Moose/Functional/ClientWatch.lua @@ -0,0 +1,687 @@ +--- **Functional** - Manage and track client slots easily to add your own client-based menus and modules to. +-- +-- The @{#CLIENTWATCH} class adds a simplified way to create scripts and menus for individual clients. Instead of creating large algorithms and juggling multiple event handlers, you can simply provide one or more prefixes to the class and use the callback functions on spawn, despawn, and any aircraft related events to script to your hearts content. +-- +-- === +-- +-- ## Features: +-- +-- * Find clients by prefixes or by providing a Wrapper.CLIENT object +-- * Trigger functions when the client spawns and despawns +-- * Create multiple client instances without overwriting event handlers between instances +-- * More reliable aircraft lost events for when DCS thinks the aircraft id dead but a dead event fails to trigger +-- * Easily manage clients spawned in dynamic slots +-- +-- ==== +-- +-- ### Author: **Statua** +-- +-- ### Contributions: **FlightControl**: Wrapper.CLIENT +-- +-- ==== +-- @module Functional.ClientWatch +-- @image clientwatch.jpg + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- CLIENTWATCH class +-- @type CLIENTWATCH +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players. +-- @field #string lid String for DCS log file. +-- @field #number FilterCoalition If not nil, will only activate for aircraft of the given coalition value. +-- @field #number FilterCategory If not nil, will only activate for aircraft of the given category value. +-- @extends Core.Fsm#FSM_CONTROLLABLE + +--- Manage and track client slots easily to add your own client-based menus and modules to. +-- +-- ## Creating a new instance +-- +-- To start, you must first create a new instance of the client manager and provide it with either a Wrapper.Client#CLIENT object, a string prefix of the unit name, or a table of string prefixes for unit names. These are used to capture the client unit when it spawns and apply your scripted functions to it. Only fixed wing and rotary wing aircraft controlled by players can be used by this class. +-- **This will not work if the client aircraft is alive!** +-- +-- ### Examples +-- +-- -- Create an instance with a Wrapper.Client#CLIENT object +-- local heliClient = CLIENT:FindByName('Rotary1-1') +-- local clientInstance = CLIENTWATCH:New(heliClient) +-- +-- -- Create an instance with part of the unit name in the Mission Editor +-- local clientInstance = CLIENTWATCH:New("Rotary") +-- +-- -- Create an instance using prefixes for a few units as well as a FARP name for any dynamic spawns coming out of it +-- local clientInstance = CLIENTWATCH:New({"Rescue","UH-1H","FARP ALPHA"}) +-- +-- ## Applying functions and methods to client aircraft when they spawn +-- +-- Once the instance is created, it will watch for birth events. If the unit name of the client aircraft matches the one provided in the instance, the callback method @{#CLIENTWATCH:OnAfterSpawn}() can be used to apply functions and methods to the client object. +-- +-- In the OnAfterSpawn() callback method are four values. From, Event, To, and ClientObject. From,Event,To are standard FSM strings for the state changes. ClientObject is where the magic happens. This is a special object which you can use to access all the data of the client aircraft. The following entries in ClientObject are available for you to use: +-- +-- * **ClientObject.Unit**: The Moose @{Wrapper.Unit#UNIT} of the client aircraft +-- * **ClientObject.Group**: The Moose @{Wrapper.Group#GRUP} of the client aircraft +-- * **ClientObject.Client**: The Moose @{Wrapper.Client#CLIENT} of the client aircraft +-- * **ClientObject.PlayerName**: A #string of the player controlling the aircraft +-- * **ClientObject.UnitName**: A #string of the client aircraft unit. +-- * **ClientObject.GroupName**: A #string of the client aircraft group. +-- +-- ### Examples +-- +-- -- Create an instance with a client unit prefix and send them a message when they spawn +-- local clientInstance = CLIENTWATCH:New("Rotary") +-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData) +-- MESSAGE:New("Welcome to your aircraft!",10):ToUnit(ClientObject.Unit) +-- end +-- +-- ## Using event callbacks +-- +-- In a normal setting, you can only use a callback function for a specific option in one location. If you have multiple scripts that rely on the same callback from the same object, this can get quite messy. With the ClientWatch module, these callbacks are isolated t the instances and therefore open the possibility to use many instances with the same callback doing different things. ClientWatch instances subscribe to all events that are applicable to player controlled aircraft and provides callbacks for each, forwarding the EventData in the callback function. +-- +-- The following event callbacks can be used inside the OnAfterSpawn() callback: +-- +-- * **:OnAfterDespawn(From,Event,To)**: Triggers whenever DCS no longer sees the aircraft as 'alive'. No event data is given in this callback as it is derived from other events +-- * **:OnAfterHit(From,Event,To,EventData)**: Triggers every time the aircraft takes damage or is struck by a weapon/explosion +-- * **:OnAfterKill(From,Event,To,EventData)**: Triggers after the aircraft kills something with its weapons +-- * **:OnAfterScore(From,Event,To,EventData)**: Triggers after accumulating score +-- * **:OnAfterShot(From,Event,To,EventData)**: Triggers after a single-shot weapon is released +-- * **:OnAfterShootingStart(From,Event,To,EventData)**: Triggers when an automatic weapon begins firing +-- * **:OnAfterShootingEnd(From,Event,To,EventData)**: Triggers when an automatic weapon stops firing +-- * **:OnAfterLand(From,Event,To,EventData)**: Triggers when an aircraft transitions from being airborne to on the ground +-- * **:OnAfterTakeoff(From,Event,To,EventData)**: Triggers when an aircraft transitions from being on the ground to airborne +-- * **:OnAfterRunwayTakeoff(From,Event,To,EventData)**: Triggers after lifting off from a runway +-- * **:OnAfterRunwayTouch(From,Event,To,EventData)**: Triggers when an aircraft's gear makes contact with a runway +-- * **:OnAfterRefueling(From,Event,To,EventData)**: Triggers when an aircraft begins taking on fuel +-- * **:OnAfterRefuelingStop(From,Event,To,EventData)**: Triggers when an aircraft stops taking on fuel +-- * **:OnAfterPlayerLeaveUnit(From,Event,To,EventData)**: Triggers when a player leaves an operational aircraft +-- * **:OnAfterCrash(From,Event,To,EventData)**: Triggers when an aircraft is destroyed (may fail to trigger if the aircraft is only partially destroyed) +-- * **:OnAfterDead(From,Event,To,EventData)**: Triggers when an aircraft is considered dead (may fail to trigger if the aircraft was partially destroyed first) +-- * **:OnAfterPilotDead(From,Event,To,EventData)**: Triggers when the pilot is killed (may fail to trigger if the aircraft was partially destroyed first) +-- * **:OnAfterUnitLost(From,Event,To,EventData)**: Triggers when an aircraft is lost for any reason (may fail to trigger if the aircraft was partially destroyed first) +-- * **:OnAfterEjection(From,Event,To,EventData)**: Triggers when a pilot ejects from an aircraft +-- * **:OnAfterHumanFailure(From,Event,To,EventData)**: Triggers when an aircraft or system is damaged from any source or action by the player +-- * **:OnAfterHumanAircraftRepairStart(From,Event,To,EventData)**: Triggers when an aircraft repair is started +-- * **:OnAfterHumanAircraftRepairFinish(From,Event,To,EventData)**: Triggers when an aircraft repair is completed +-- * **:OnAfterEngineStartup(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a started state. Parameters vary by aircraft +-- * **:OnAfterEngineShutdown(From,Event,To,EventData)**: Triggers when the engine enters what DCS considers to be a stopped state. Parameters vary by aircraft +-- * **:OnAfterWeaponAdd(From,Event,To,EventData)**: Triggers when an item is added to an aircraft's payload +-- * **:OnAfterWeaponDrop(From,Event,To,EventData)**: Triggers when an item is jettisoned or dropped from an aircraft (unconfirmed) +-- * **:OnAfterWeaponRearm(From,Event,To,EventData)**: Triggers when an item with internal supply is restored (unconfirmed) +-- +-- ### Examples +-- +-- -- Show a message to player when they take damage from a weapon +-- local clientInstance = CLIENTWATCH:New("Rotary") +-- function clientInstance:OnAfterSpawn(From,Event,To,ClientObject,EventData) +-- function ClientObject:OnAfterHit(From,Event,To,EventData) +-- local typeShooter = EventData.IniTypeName +-- local nameWeapon = EventData.weapon_name +-- MESSAGE:New("A "..typeShooter.." hit you with a "..nameWeapon,20):ToUnit(ClientObject.Unit) +-- end +-- end +-- +-- @field #CLIENTWATCH +CLIENTWATCH = {} +CLIENTWATCH.ClassName = "CLIENTWATCH" +CLIENTWATCH.Debug = false +CLIENTWATCH.DebugEventData = false +CLIENTWATCH.lid = nil + +-- @type CLIENTWATCHTools +-- @field #table Unit Wrapper.UNIT of the cient object +-- @field #table Group Wrapper.GROUP of the cient object +-- @field #table Client Wrapper.CLIENT of the cient object +-- @field #string PlayerName Name of the player controlling the client object +-- @field #string UnitName Name of the unit that is the client object +-- @field #string GroupName Name of the group the client object belongs to +CLIENTWATCHTools = {} + +--- CLIENTWATCH version +-- @field #string version +CLIENTWATCH.version="1.0.1" + +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Creates a new instance of CLIENTWATCH to add scripts to. Can be used multiple times with the same client/prefixes if you need it for multiple scripts. +-- @param #CLIENTWATCH self +-- @param #string Will watch for clients whos UNIT NAME or GROUP NAME matches part of the #string as a prefix. +-- @param #table Put strings in a table to use multiple prefixes for the above method. +-- @param Wrapper.Client#CLIENT Provide a Moose CLIENT object to apply to that specific aircraft slot (static slots only!) +-- @param #nil Leave blank to activate for ALL CLIENTS +-- @return #CLIENTWATCH self +function CLIENTWATCH:New(client) + --Init FSM + local self=BASE:Inherit(self, FSM:New()) + self:SetStartState( "Idle" ) + self:AddTransition( "*", "Spawn", "*" ) + + self.FilterCoalition = nil + self.FilterCategory = nil + + --- User function for OnAfter "Spawn" event. + -- @function [parent=#CLIENTWATCH] OnAfterSpawn + -- @param #CLIENTWATCH self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table clientObject Custom object that handles events and stores Moose object data. See top documentation for more details. + -- @param #table eventdata Data from EVENTS.Birth. + + --Set up spawn tracking + if not client then + if self.Debug then self:I({"New client instance created. ClientType = All clients"}) end + self:HandleEvent(EVENTS.Birth) + function self:OnEventBirth(eventdata) + if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName + and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition) + and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then + if self.Debug then + self:I({"Client spawned in.",IniCategory = eventdata.IniCategory}) + end + local clientWatchDebug = self.Debug + local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata) + self:Spawn(clientObject,eventdata) + end + end + elseif type(client) == "table" or type(client) == "string" then + if type(client) == "table" then + + --CLIENT TABLE + if client.ClassName == "CLIENT" then + if self.Debug then self:I({"New client instance created. ClientType = Wrapper.CLIENT",client}) end + self.ClientName = client:GetName() + self:HandleEvent(EVENTS.Birth) + function self:OnEventBirth(eventdata) + if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName + and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition) + and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then + if self.ClientName == eventdata.IniUnitName then + if self.Debug then + self:I({"Client spawned in.",IniCategory = eventdata.IniCategory}) + end + local clientWatchDebug = self.Debug + local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata) + self:Spawn(clientObject,eventdata) + end + end + end + + --STRING TABLE + else + if self.Debug then self:I({"New client instance created. ClientType = Multiple Prefixes",client}) end + local tableValid = true + for _,entry in pairs(client) do + if type(entry) ~= "string" then + tableValid = false + self:E({"The base handler failed to start because at least one entry in param1's table is not a string!",InvalidEntry = entry}) + return nil + end + end + if tableValid then + self:HandleEvent(EVENTS.Birth) + function self:OnEventBirth(eventdata) + for _,entry in pairs(client) do + if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName + and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition) + and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then + if string.match(eventdata.IniUnitName,entry) or string.match(eventdata.IniGroupName,entry) then + if self.Debug then + self:I({"Client spawned in.",IniCategory = eventdata.IniCategory}) + end + local clientWatchDebug = self.Debug + local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata) + self:Spawn(clientObject,eventdata) + break + end + end + end + end + end + end + else + if self.Debug then self:I({"New client instance created. ClientType = Single Prefix",client}) end + + --SOLO STRING + self:HandleEvent(EVENTS.Birth) + function self:OnEventBirth(eventdata) + if (eventdata.IniCategory == 0 or eventdata.IniCategory == 1) and eventdata.IniPlayerName + and (not self.FilterCoalition or self.FilterCoalition == eventdata.IniCoalition) + and (not self.FilterCategory or self.FilterCategory == eventdata.IniCategory) then + if string.match(eventdata.IniUnitName,client) or string.match(eventdata.IniGroupName,client) then + if self.Debug then + self:I({"Client spawned in.",IniCategory = eventdata.IniCategory}) + end + local clientWatchDebug = self.Debug + local clientObject = CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata) + self:Spawn(clientObject,eventdata) + end + end + end + end + else + self:E({"The base handler failed to start because param1 is not a CLIENT object or a prefix string!",param1 = client}) + return nil + end + + return self +end + + +--- Filter out all clients not belonging to the provided coalition +-- @param #CLIENTWATCH self +-- @param #number Coalition number (1 = red, 2 = blue) +-- @param #string Coalition string ('red' or 'blue') +function CLIENTWATCH:FilterByCoalition(value) + if value == 1 or value == "red" then + self.FilterCoalition = 1 + else + self.FilterCoalition = 2 + end + return self +end + + +--- Filter out all clients that are not of the given category +-- @param #CLIENTWATCH self +-- @param #number Category number (0 = airplane, 1 = helicopter) +-- @param #string Category string ('airplane' or 'helicopter') +function CLIENTWATCH:FilterByCategory(value) + if value == 1 or value == "helicopter" then + self.FilterCategory = 1 + else + self.FilterCategory = 0 + end + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Internal function for creating a new client on birth. Do not use!!!. +-- @param #CLIENTWATCHTools self +-- @param #EVENTS.Birth EventData +-- @return #CLIENTWATCHTools self +function CLIENTWATCHTools:_newClient(clientWatchDebug,eventdata) + --Init FSM + local self=BASE:Inherit(self, FSM:New()) + self:SetStartState( "Alive" ) + self:AddTransition( "Alive", "Despawn", "Dead" ) + + self.Unit = eventdata.IniUnit + self.Group = self.Unit:GetGroup() + self.Client = self.Unit:GetClient() + self.PlayerName = self.Unit:GetPlayerName() + self.UnitName = self.Unit:GetName() + self.GroupName = self.Group:GetName() + + --Event events + self:AddTransition( "*", "Hit", "*" ) + self:AddTransition( "*", "Kill", "*" ) + self:AddTransition( "*", "Score", "*" ) + self:AddTransition( "*", "Shot", "*" ) + self:AddTransition( "*", "ShootingStart", "*" ) + self:AddTransition( "*", "ShootingEnd", "*" ) + self:AddTransition( "*", "Land", "*" ) + self:AddTransition( "*", "Takeoff", "*" ) + self:AddTransition( "*", "RunwayTakeoff", "*" ) + self:AddTransition( "*", "RunwayTouch", "*" ) + self:AddTransition( "*", "Refueling", "*" ) + self:AddTransition( "*", "RefuelingStop", "*" ) + self:AddTransition( "*", "PlayerLeaveUnit", "*" ) + self:AddTransition( "*", "Crash", "*" ) + self:AddTransition( "*", "Dead", "*" ) + self:AddTransition( "*", "PilotDead", "*" ) + self:AddTransition( "*", "UnitLost", "*" ) + self:AddTransition( "*", "Ejection", "*" ) + self:AddTransition( "*", "HumanFailure", "*" ) + self:AddTransition( "*", "HumanAircraftRepairFinish", "*" ) + self:AddTransition( "*", "HumanAircraftRepairStart", "*" ) + self:AddTransition( "*", "EngineShutdown", "*" ) + self:AddTransition( "*", "EngineStartup", "*" ) + self:AddTransition( "*", "WeaponAdd", "*" ) + self:AddTransition( "*", "WeaponDrop", "*" ) + self:AddTransition( "*", "WeaponRearm", "*" ) + + --Event Handlers + self:HandleEvent( EVENTS.Hit ) + self:HandleEvent( EVENTS.Kill ) + self:HandleEvent( EVENTS.Score ) + self:HandleEvent( EVENTS.Shot ) + self:HandleEvent( EVENTS.ShootingStart ) + self:HandleEvent( EVENTS.ShootingEnd ) + self:HandleEvent( EVENTS.Land ) + self:HandleEvent( EVENTS.Takeoff ) + self:HandleEvent( EVENTS.RunwayTakeoff ) + self:HandleEvent( EVENTS.RunwayTouch ) + self:HandleEvent( EVENTS.Refueling ) + self:HandleEvent( EVENTS.RefuelingStop ) + self:HandleEvent( EVENTS.PlayerLeaveUnit ) + self:HandleEvent( EVENTS.Crash ) + self:HandleEvent( EVENTS.Dead ) + self:HandleEvent( EVENTS.PilotDead ) + self:HandleEvent( EVENTS.UnitLost ) + self:HandleEvent( EVENTS.Ejection ) + self:HandleEvent( EVENTS.HumanFailure ) + self:HandleEvent( EVENTS.HumanAircraftRepairFinish ) + self:HandleEvent( EVENTS.HumanAircraftRepairStart ) + self:HandleEvent( EVENTS.EngineShutdown ) + self:HandleEvent( EVENTS.EngineStartup ) + self:HandleEvent( EVENTS.WeaponAdd ) + self:HandleEvent( EVENTS.WeaponDrop ) + self:HandleEvent( EVENTS.WeaponRearm ) + + function self:OnEventHit(EventData) + if EventData.TgtUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered hit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Hit(EventData) + end + end + + function self:OnEventKill(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered kill event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Kill(EventData) + end + end + + function self:OnEventScore(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered score event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Score(EventData) + end + end + + function self:OnEventShot(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered shot event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Shot(EventData) + end + end + + function self:OnEventShootingStart(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered shooting start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:ShootingStart(EventData) + end + end + + function self:OnEventShootingEnd(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered shooting end event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:ShootingEnd(EventData) + end + end + + function self:OnEventLand(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered land event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Land(EventData) + end + end + + function self:OnEventTakeoff(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Takeoff(EventData) + end + end + + function self:OnEventRunwayTakeoff(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered runway takeoff event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:RunwayTakeoff(EventData) + end + end + + function self:OnEventRunwayTouch(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered runway touch event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:RunwayTouch(EventData) + end + end + + function self:OnEventRefueling(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Refueling(EventData) + end + end + + function self:OnEventRefuelingStop(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered refueling event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:RefuelingStop(EventData) + end + end + + function self:OnEventPlayerLeaveUnit(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered leave unit event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:PlayerLeaveUnit(EventData) + self._deadRoutine() + end + end + + function self:OnEventCrash(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered crash event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Crash(EventData) + self._deadRoutine() + end + end + + function self:OnEventDead(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Dead(EventData) + self._deadRoutine() + end + end + + function self:OnEventPilotDead(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered pilot dead event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:PilotDead(EventData) + self._deadRoutine() + end + end + + function self:OnEventUnitLost(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered unit lost event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:UnitLost(EventData) + self._deadRoutine() + end + end + + function self:OnEventEjection(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered ejection event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:Ejection(EventData) + self._deadRoutine() + end + end + + function self:OnEventHumanFailure(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered human failure event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:HumanFailure(EventData) + if not self.Unit:IsAlive() then + self._deadRoutine() + end + end + end + + function self:OnEventHumanAircraftRepairFinish(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered repair finished event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:HumanAircraftRepairFinish(EventData) + end + end + + function self:OnEventHumanAircraftRepairStart(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered repair start event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:HumanAircraftRepairStart(EventData) + end + end + + function self:OnEventEngineShutdown(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered engine shutdown event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:EngineShutdown(EventData) + end + end + + function self:OnEventEngineStartup(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered engine startup event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:EngineStartup(EventData) + end + end + + function self:OnEventWeaponAdd(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered weapon add event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:WeaponAdd(EventData) + end + end + + function self:OnEventWeaponDrop(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered weapon drop event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:WeaponDrop(EventData) + end + end + + function self:OnEventWeaponRearm(EventData) + if EventData.IniUnitName == self.UnitName then + if clientWatchDebug then + self:I({"Client triggered weapon rearm event.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + + end + self:WeaponRearm(EventData) + end + end + + + --Fallback timer + self.FallbackTimer = TIMER:New(function() + if not self.Unit:IsAlive() then + if clientWatchDebug then + self:I({"Client is registered as dead without an event trigger. Running fallback dead routine.",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) + end + self._deadRoutine() + end + end) + self.FallbackTimer:Start(5,5) + + --Stop event handlers and trigger Despawn + function self._deadRoutine() + if clientWatchDebug then self:I({"Client dead routine triggered. Shutting down tracking...",Player = self.PlayerName,Group = self.GroupName,Unit = self.UnitName}) end + self:UnHandleEvent( EVENTS.Hit ) + self:UnHandleEvent( EVENTS.Kill ) + self:UnHandleEvent( EVENTS.Score ) + self:UnHandleEvent( EVENTS.Shot ) + self:UnHandleEvent( EVENTS.ShootingStart ) + self:UnHandleEvent( EVENTS.ShootingEnd ) + self:UnHandleEvent( EVENTS.Land ) + self:UnHandleEvent( EVENTS.Takeoff ) + self:UnHandleEvent( EVENTS.RunwayTakeoff ) + self:UnHandleEvent( EVENTS.RunwayTouch ) + self:UnHandleEvent( EVENTS.Refueling ) + self:UnHandleEvent( EVENTS.RefuelingStop ) + self:UnHandleEvent( EVENTS.PlayerLeaveUnit ) + self:UnHandleEvent( EVENTS.Crash ) + self:UnHandleEvent( EVENTS.Dead ) + self:UnHandleEvent( EVENTS.PilotDead ) + self:UnHandleEvent( EVENTS.UnitLost ) + self:UnHandleEvent( EVENTS.Ejection ) + self:UnHandleEvent( EVENTS.HumanFailure ) + self:UnHandleEvent( EVENTS.HumanAircraftRepairFinish ) + self:UnHandleEvent( EVENTS.HumanAircraftRepairStart ) + self:UnHandleEvent( EVENTS.EngineShutdown ) + self:UnHandleEvent( EVENTS.EngineStartup ) + self:UnHandleEvent( EVENTS.WeaponAdd ) + self:UnHandleEvent( EVENTS.WeaponDrop ) + self:UnHandleEvent( EVENTS.WeaponRearm ) + self.FallbackTimer:Stop() + self:Despawn() + end + + self:I({"Detected client spawn and applied internal functions and events.", PlayerName = self.PlayerName, UnitName = self.UnitName, GroupName = self.GroupName}) + return self +end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index a6196f861..76e768ef3 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -595,7 +595,8 @@ do -- DETECTION_BASE return self end - + + --- -- @param #DETECTION_BASE self -- @param #string From The From State string. -- @param #string Event The Event string. @@ -604,7 +605,7 @@ do -- DETECTION_BASE -- @param #number DetectionTimeStamp Time stamp of detection event. function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) - self:I( { DetectedObjects = self.DetectedObjects } ) + self:T( { DetectedObjects = self.DetectedObjects } ) self.DetectionRun = self.DetectionRun + 1 @@ -612,10 +613,10 @@ do -- DETECTION_BASE if Detection and Detection:IsAlive() then - self:I( { "DetectionGroup is Alive", Detection:GetName() } ) + self:T( { "DetectionGroup is Alive", Detection:GetName() } ) local DetectionGroupName = Detection:GetName() - local DetectionUnit = Detection:GetUnit( 1 ) + local DetectionUnit = Detection:GetFirstUnitAlive() local DetectedUnits = {} @@ -628,30 +629,30 @@ do -- DETECTION_BASE self.DetectDLINK ) - --self:I( { DetectedTargets = DetectedTargets } ) - --self:I(UTILS.PrintTableToLog(DetectedTargets)) + --self:T( { DetectedTargets = DetectedTargets } ) + --self:T(UTILS.PrintTableToLog(DetectedTargets)) - for DetectionObjectID, Detection in pairs( DetectedTargets ) do + for DetectionObjectID, Detection in pairs( DetectedTargets or {}) do local DetectedObject = Detection.object -- DCS#Object if DetectedObject and DetectedObject:isExist() and DetectedObject.id_ < 50000000 then -- and ( DetectedObject:getCategory() == Object.Category.UNIT or DetectedObject:getCategory() == Object.Category.STATIC ) then local DetectedObjectName = DetectedObject:getName() if not self.DetectedObjects[DetectedObjectName] then self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {} - self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName + self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName self.DetectedObjects[DetectedObjectName].Object = DetectedObject end end end - for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects ) do + for DetectionObjectName, DetectedObjectData in pairs( self.DetectedObjects or {}) do local DetectedObject = DetectedObjectData.Object if DetectedObject:isExist() then - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected( + local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity = DetectionUnit:IsTargetDetected( DetectedObject, self.DetectVisual, self.DetectOptical, diff --git a/Moose Development/Moose/Functional/DetectionZones.lua b/Moose Development/Moose/Functional/DetectionZones.lua index 20680559d..8a8984658 100644 --- a/Moose Development/Moose/Functional/DetectionZones.lua +++ b/Moose Development/Moose/Functional/DetectionZones.lua @@ -4,7 +4,7 @@ do -- DETECTION_ZONES - --- @type DETECTION_ZONES + -- @type DETECTION_ZONES -- @field DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. -- @field #DETECTION_BASE.DetectedItems DetectedItems A list of areas containing the set of @{Wrapper.Unit}s, @{Core.Zone}s, the center @{Wrapper.Unit} within the zone, and ID of each area that was detected within a DetectionZoneRange. -- @extends Functional.Detection#DETECTION_BASE @@ -68,7 +68,7 @@ do -- DETECTION_ZONES return self end - --- @param #DETECTION_ZONES self + -- @param #DETECTION_ZONES self -- @param #number The amount of alive recce. function DETECTION_ZONES:CountAliveRecce() @@ -76,7 +76,7 @@ do -- DETECTION_ZONES end - --- @param #DETECTION_ZONES self + -- @param #DETECTION_ZONES self function DETECTION_ZONES:ForEachAliveRecce( IteratorFunction, ... ) self:F2( arg ) @@ -352,7 +352,7 @@ do -- DETECTION_ZONES --DetectedSet:Flush( self ) DetectedSet:ForEachUnit( - --- @param Wrapper.Unit#UNIT DetectedUnit + -- @param Wrapper.Unit#UNIT DetectedUnit function( DetectedUnit ) if DetectedUnit:IsAlive() then --self:T( "Detected Set #" .. DetectedItem.ID .. ":" .. DetectedUnit:GetName() ) @@ -380,7 +380,7 @@ do -- DETECTION_ZONES end - --- @param #DETECTION_ZONES self + -- @param #DETECTION_ZONES self -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. diff --git a/Moose Development/Moose/Functional/Escort.lua b/Moose Development/Moose/Functional/Escort.lua index a1997334e..ecf53a382 100644 --- a/Moose Development/Moose/Functional/Escort.lua +++ b/Moose Development/Moose/Functional/Escort.lua @@ -1154,8 +1154,6 @@ function ESCORT:_ReportTargetsScheduler() if self.EscortGroup:IsAlive() and self.EscortClient:IsAlive() then - if true then - local EscortGroupName = self.EscortGroup:GetName() self.EscortMenuAttackNearbyTargets:RemoveSubMenus() @@ -1226,177 +1224,6 @@ function ESCORT:_ReportTargetsScheduler() end return true - else --- local EscortGroupName = self.EscortGroup:GetName() --- local EscortTargets = self.EscortGroup:GetDetectedTargets() --- --- local ClientEscortTargets = self.EscortClient._EscortGroups[EscortGroupName].Targets --- --- local EscortTargetMessages = "" --- for EscortTargetID, EscortTarget in pairs( EscortTargets ) do --- local EscortObject = EscortTarget.object --- self:T( EscortObject ) --- if EscortObject and EscortObject:isExist() and EscortObject.id_ < 50000000 then --- --- local EscortTargetUnit = UNIT:Find( EscortObject ) --- local EscortTargetUnitName = EscortTargetUnit:GetName() --- --- --- --- -- local EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity --- -- = self.EscortGroup:IsTargetDetected( EscortObject ) --- -- --- -- self:T( { EscortTargetIsDetected, --- -- EscortTargetIsVisible, --- -- EscortTargetLastTime, --- -- EscortTargetKnowType, --- -- EscortTargetKnowDistance, --- -- EscortTargetLastPos, --- -- EscortTargetLastVelocity } ) --- --- --- local EscortTargetUnitVec3 = EscortTargetUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget } ) --- --- if Distance <= 15 then --- --- if not ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = {} --- end --- ClientEscortTargets[EscortTargetUnitName].AttackUnit = EscortTargetUnit --- ClientEscortTargets[EscortTargetUnitName].visible = EscortTarget.visible --- ClientEscortTargets[EscortTargetUnitName].type = EscortTarget.type --- ClientEscortTargets[EscortTargetUnitName].distance = EscortTarget.distance --- else --- if ClientEscortTargets[EscortTargetUnitName] then --- ClientEscortTargets[EscortTargetUnitName] = nil --- end --- end --- end --- end --- --- self:T( { "Sorting Targets Table:", ClientEscortTargets } ) --- table.sort( ClientEscortTargets, function( a, b ) return a.Distance < b.Distance end ) --- self:T( { "Sorted Targets Table:", ClientEscortTargets } ) --- --- -- Remove the sub menus of the Attack menu of the Escort for the EscortGroup. --- self.EscortMenuAttackNearbyTargets:RemoveSubMenus() --- --- if self.EscortMenuTargetAssistance then --- self.EscortMenuTargetAssistance:RemoveSubMenus() --- end --- --- --for MenuIndex = 1, #self.EscortMenuAttackTargets do --- -- self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) --- -- self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() --- --end --- --- --- if ClientEscortTargets then --- for ClientEscortTargetUnitName, ClientEscortTargetData in pairs( ClientEscortTargets ) do --- --- for ClientEscortGroupName, EscortGroupData in pairs( self.EscortClient._EscortGroups ) do --- --- if ClientEscortTargetData and ClientEscortTargetData.AttackUnit:IsAlive() then --- --- local EscortTargetMessage = "" --- local EscortTargetCategoryName = ClientEscortTargetData.AttackUnit:GetCategoryName() --- local EscortTargetCategoryType = ClientEscortTargetData.AttackUnit:GetTypeName() --- if ClientEscortTargetData.type then --- EscortTargetMessage = EscortTargetMessage .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " --- else --- EscortTargetMessage = EscortTargetMessage .. "Unknown target at " --- end --- --- local EscortTargetUnitVec3 = ClientEscortTargetData.AttackUnit:GetVec3() --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( EscortTargetUnitVec3.x - EscortVec3.x )^2 + --- ( EscortTargetUnitVec3.y - EscortVec3.y )^2 + --- ( EscortTargetUnitVec3.z - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- --- self:T( { self.EscortGroup:GetName(), ClientEscortTargetData.AttackUnit:GetName(), Distance, ClientEscortTargetData.AttackUnit } ) --- if ClientEscortTargetData.visible == false then --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " estimated km" --- else --- EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" --- end --- --- if ClientEscortTargetData.visible then --- EscortTargetMessage = EscortTargetMessage .. ", visual" --- end --- --- if ClientEscortGroupName == EscortGroupName then --- --- MENU_GROUP_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- self.EscortMenuAttackNearbyTargets, --- ESCORT._AttackTarget, --- { ParamSelf = self, --- ParamUnit = ClientEscortTargetData.AttackUnit --- } --- ) --- EscortTargetMessages = EscortTargetMessages .. "\n - " .. EscortTargetMessage --- else --- if self.EscortMenuTargetAssistance then --- local MenuTargetAssistance = MENU_GROUP:New( self.EscortClient, EscortGroupData.EscortName, self.EscortMenuTargetAssistance ) --- MENU_GROUP_COMMAND:New( self.EscortClient, --- EscortTargetMessage, --- MenuTargetAssistance, --- ESCORT._AssistTarget, --- self, --- EscortGroupData.EscortGroup, --- ClientEscortTargetData.AttackUnit --- ) --- end --- end --- else --- ClientEscortTargetData = nil --- end --- end --- end --- --- if EscortTargetMessages ~= "" and self.ReportTargets == true then --- self.EscortGroup:MessageToClient( "Detected targets within 15 km range:" .. EscortTargetMessages:gsub("\n$",""), 20, self.EscortClient ) --- else --- self.EscortGroup:MessageToClient( "No targets detected!", 20, self.EscortClient ) --- end --- end --- --- if self.EscortMenuResumeMission then --- self.EscortMenuResumeMission:RemoveSubMenus() --- --- -- if self.EscortMenuResumeWayPoints then --- -- for MenuIndex = 1, #self.EscortMenuResumeWayPoints do --- -- self:T( { "Remove Menu:", self.EscortMenuResumeWayPoints[MenuIndex] } ) --- -- self.EscortMenuResumeWayPoints[MenuIndex] = self.EscortMenuResumeWayPoints[MenuIndex]:Remove() --- -- end --- -- end --- --- local TaskPoints = self:RegisterRoute() --- for WayPointID, WayPoint in pairs( TaskPoints ) do --- local EscortVec3 = self.EscortGroup:GetVec3() --- local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + --- ( WayPoint.y - EscortVec3.z )^2 --- ) ^ 0.5 / 1000 --- MENU_GROUP_COMMAND:New( self.EscortClient, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", self.EscortMenuResumeMission, ESCORT._ResumeMission, { ParamSelf = self, ParamWayPoint = WayPointID } ) --- end --- end --- --- return true - end end return false diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 5ca6d4e51..66a790c41 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -141,7 +141,7 @@ FOX = { explosiondist = 200, explosiondist2 = 500, bigmissilemass = 50, - destroy = nil, + --destroy = nil, dt50 = 5, dt10 = 1, dt05 = 0.5, @@ -1060,7 +1060,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Tracking info and init of last bomb position. local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName) - self:I(FOX.lid..text) + self:T(FOX.lid..text) MESSAGE:New(text, 10):ToAllIf(self.Debug) -- Loop over players. diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index b6d711964..0ebe76f4a 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -22,7 +22,7 @@ -- @module Functional.Mantis -- @image Functional.Mantis.jpg -- --- Last Update: May 2024 +-- Last Update: Mar 2025 ------------------------------------------------------------------------- --- **MANTIS** class, extends Core.Base#BASE @@ -59,6 +59,10 @@ -- @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. -- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range. +-- @field #table FilterZones Table of Core.Zone#ZONE Zones Consider SAM groups in this zone(s) only for this MANTIS instance, must be handed as #table of Zone objects. +-- @field #boolean SmokeDecoy If true, smoke short range SAM units as decoy if a plane is in firing range. +-- @field #number SmokeDecoyColor Color to use, defaults to SMOKECOLOR.White +-- @field #number checkcounter Counter for SAM Table refreshes -- @extends Core.Base#BASE @@ -70,7 +74,7 @@ -- -- * Moose derived Modular, Automatic and Network capable Targeting and Interception System. -- * Controls a network of SAM sites. Uses detection to switch on the SAM site closest to the enemy. --- * **Automatic mode** (default since 0.8) can set-up your SAM site network automatically for you +-- * **Automatic mode** (default since 0.8) will set-up your SAM site network automatically for you -- * **Classic mode** behaves like before -- * Leverage evasiveness from SEAD, leverage attack range setting -- * Automatic setup of SHORAD based on groups of the class "short-range" @@ -85,6 +89,7 @@ -- * SAM sites, e.g. each **group name** begins with "Red SAM" -- * EWR network and AWACS, e.g. each **group name** begins with "Red EWR" and *not* e.g. "Red SAM EWR" (overlap with "Red SAM"), "Red EWR Awacs" will be found by "Red EWR" -- * SHORAD, e.g. each **group name** begins with "Red SHORAD" and *not" e.g. just "SHORAD" because you might also have "Blue SHORAD" +-- * Point Defense, e.g. each **group name** begins with "Red AAA" and *not" e.g. just "AAA" because you might also have "Blue AAA" -- -- It's important to get this right because of the nature of the filter-system in @{Core.Set#SET_GROUP}. Filters are "greedy", that is they -- will match *any* string that contains the search string - hence we need to avoid that SAMs, EWR and SHORAD step on each other\'s toes. @@ -143,6 +148,7 @@ -- **Location** is of highest importance 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. +-- **HINT** Set at least one EWR on invisible and immortal so MANTIS doesn't stop working. -- -- ## 1.2 SAM sites -- @@ -191,26 +197,24 @@ -- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- -- --- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target: +-- ### 2.1.2 Change the number of long-, mid- and short-range, point defense systems going live on a detected target: -- --- -- parameters are numbers. Defaults are 1,2,2,6 respectively --- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) +-- -- parameters are numbers. Defaults are 1,2,2,6,6 respectively +-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) -- --- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range" +-- ### 2.1.3 SHORAD/Point defense will automatically be added from SAM sites of type "point" or if the range is less than 5km or if the type is AAA. -- -- ### 2.1.4 Advanced features -- --- -- switch off auto mode **before** you start MANTIS. +-- -- Option to switch off auto mode **before** you start MANTIS (not recommended) -- mybluemantis.automode = false -- --- -- switch off auto shorad **before** you start MANTIS. --- mybluemantis.autoshorad = false --- --- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. +-- -- Option to set the scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- also see engagerange below. -- self.radiusscale[MANTIS.SamType.LONG] = 1.1 -- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 -- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 +-- self.radiusscale[MANTIS.SamType.POINT] = 1.4 -- -- ### 2.1.5 Friendlies check in firing range -- @@ -239,9 +243,9 @@ -- -- Use this option if you want to make use of or allow advanced SEAD tactics. -- --- # 5. Integrate SHORAD [classic mode] +-- # 5. Integrate SHORAD [classic mode, not necessary in automode, not recommended for manual setup] -- --- 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 +-- You can also choose to integrate Mantis with @{Functional.Shorad#SHORAD} for protection against HARMs and AGMs manually. 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() @@ -295,6 +299,7 @@ MANTIS = { SAM_Table_Long = {}, SAM_Table_Medium = {}, SAM_Table_Short = {}, + SAM_Table_PointDef = {}, lid = "", Detection = nil, AWACS_Detection = nil, @@ -328,6 +333,9 @@ MANTIS = { autoshorad = true, ShoradGroupSet = nil, checkforfriendlies = false, + SmokeDecoy = false, + SmokeDecoyColor = SMOKECOLOR.White, + checkcounter = 1, } --- Advanced state enumerator @@ -344,8 +352,17 @@ MANTIS.SamType = { SHORT = "Short", MEDIUM = "Medium", LONG = "Long", + POINT = "Point", } +--- SAM Radiusscale +-- @type MANTIS.radiusscale +MANTIS.radiusscale = {} +MANTIS.radiusscale[MANTIS.SamType.LONG] = 1.1 +MANTIS.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 +MANTIS.radiusscale[MANTIS.SamType.SHORT] = 1.75 +MANTIS.radiusscale[MANTIS.SamType.POINT] = 3 + --- SAM data -- @type MANTIS.SamData -- @field #number Range Max firing range in km @@ -353,6 +370,7 @@ MANTIS.SamType = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamData = { ["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B @@ -364,16 +382,16 @@ MANTIS.SamData = { ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, - ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, + ["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Point", Radar="Roland" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, - ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, ["SA-19"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Tunguska" }, - ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Short", Radar="Tor 9A331" }, - ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, + ["SA-15"] = { Range=11, Blindspot=0, Height=6, Type="Point", Radar="Tor 9A331", Point="true" }, + ["SA-13"] = { Range=5, Blindspot=0, Height=3, Type="Point", Radar="Strela", Point="true" }, ["Avenger"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Avenger" }, ["Chaparral"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Chaparral" }, - ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Linebacker" }, + ["Linebacker"] = { Range=4, Blindspot=0, Height=3, Type="Point", Radar="Linebacker", Point="true" }, ["Silkworm"] = { Range=90, Blindspot=1, Height=0.2, Type="Long", Radar="Silkworm" }, -- units from HDS Mod, multi launcher options is tricky ["SA-10B"] = { Range=75, Blindspot=0, Height=18, Type="Medium" , Radar="SA-10B"}, @@ -381,7 +399,6 @@ MANTIS.SamData = { ["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"}, ["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"}, ["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" }, - ["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" }, ["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" }, ["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" }, } @@ -393,6 +410,7 @@ MANTIS.SamData = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataHDS = { -- units from HDS Mod, multi launcher options is tricky -- group name MUST contain HDS to ID launcher type correctly! @@ -414,20 +432,21 @@ MANTIS.SamDataHDS = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataSMA = { -- units from SMA Mod (Sweedish Military Assets) -- https://forum.dcs.world/topic/295202-swedish-military-assets-for-dcs-by-currenthill/ -- group name MUST contain SMA to ID launcher type correctly! - ["RBS98M SMA"] = { Range=20, Blindspot=0, Height=8, Type="Short", Radar="RBS-98" }, - ["RBS70 SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-70" }, - ["RBS70M SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="BV410_RBS70" }, - ["RBS90 SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="RBS-90" }, - ["RBS90M SMA"] = { Range=8, Blindspot=0, Height=5.5, Type="Short", Radar="BV410_RBS90" }, - ["RBS103A SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, - ["RBS103B SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_Rb103B" }, - ["RBS103AM SMA"] = { Range=150, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, - ["RBS103BM SMA"] = { Range=35, Blindspot=0, Height=36, Type="Medium", Radar="LvS-103_Lavett103_HX_Rb103B" }, - ["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" }, + ["RBS98M SMA"] = { Range=20, Blindspot=0.2, Height=8, Type="Short", Radar="RBS-98" }, + ["RBS70 SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-70" }, + ["RBS70M SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS70" }, + ["RBS90 SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-90" }, + ["RBS90M SMA"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS90" }, + ["RBS103A SMA"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, + ["RBS103B SMA"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103B" }, + ["RBS103AM SMA"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, + ["RBS103BM SMA"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103B" }, + ["Lvkv9040M SMA"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" }, } --- SAM data CH @@ -437,28 +456,54 @@ MANTIS.SamDataSMA = { -- @field #number Height Max firing height in km -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Radar Radar typename on unit level (used as key) +-- @field #string Point Point defense capable MANTIS.SamDataCH = { - -- units from CH (Military Assets by Currenthill) - -- https://www.currenthill.com/ - -- group name MUST contain CHM to ID launcher type correctly! - ["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" }, - ["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, - ["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, - ["PGL-625 CH"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" }, - ["HQ-17A CH"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, - ["M903PAC2 CH"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, - ["M903PAC3 CH"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, - ["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, - ["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, - ["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, - ["NASAMS3-AMRAAMER CH"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, - ["NASAMS3-AIM9X2 CH"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, - ["C-RAM CH"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" }, - ["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" }, - ["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, - ["S350-9M96D CH"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, - ["LAV-AD CH"] = { Range=8, Blindspot=0.2, Height=4.8, Type="Short", Radar="CH_LAVAD" }, - ["HQ-22 CH"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, + -- units from CH (Military Assets by Currenthill) + -- https://www.currenthill.com/ + -- group name MUST contain CHM to ID launcher type correctly! + ["2S38 CHM"] = { Range=6, Blindspot=0.1, Height=4.5, Type="Short", Radar="2S38" }, + ["PantsirS1 CHM"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" }, + ["PantsirS2 CHM"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" }, + ["PGL-625 CHM"] = { Range=10, Blindspot=1, Height=5, Type="Short", Radar="PGL_625" }, + ["HQ-17A CHM"] = { Range=15, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" }, + ["M903PAC2 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" }, + ["M903PAC3 CHM"] = { Range=160, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" }, + ["TorM2 CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" }, + ["TorM2K CHM"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" }, + ["TorM2M CHM"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" }, + ["NASAMS3-AMRAAMER CHM"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" }, + ["NASAMS3-AIM9X2 CHM"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" }, + ["C-RAM CHM"] = { Range=2, Blindspot=0, Height=2, Type="Point", Radar="CH_Centurion_C_RAM", Point="true" }, + ["PGZ-09 CHM"] = { Range=4, Blindspot=0.5, Height=3, Type="Point", Radar="CH_PGZ09", Point="true" }, + ["S350-9M100 CHM"] = { Range=15, Blindspot=1, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" }, + ["S350-9M96D CHM"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" }, + ["LAV-AD CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_LAVAD" }, + ["HQ-22 CHM"] = { Range=170, Blindspot=5, Height=27, Type="Long", Radar="CH_HQ22_LN" }, + ["PGZ-95 CHM"] = { Range=2.5, Blindspot=0.5, Height=2, Type="Point", Radar="CH_PGZ95",Point="true" }, + ["LD-3000 CHM"] = { Range=2.5, Blindspot=0.1, Height=3, Type="Point", Radar="CH_LD3000_stationary", Point="true" }, + ["LD-3000M CHM"] = { Range=2.5, Blindspot=0.1, Height=3, Type="Point", Radar="CH_LD3000", Point="true" }, + ["FlaRakRad CHM"] = { Range=8, Blindspot=1.5, Height=6, Type="Short", Radar="CH_FlaRakRad" }, + ["IRIS-T SLM CHM"] = { Range=40, Blindspot=0.5, Height=20, Type="Medium", Radar="CH_IRIST_SLM" }, + ["M903PAC2KAT1 CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="CH_MIM104_M903_PAC2_KAT1" }, + ["Skynex CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_SkynexHX", Point="true" }, + ["Skyshield CHM"] = { Range=3.5, Blindspot=0.1, Height=3.5, Type="Point", Radar="CH_Skyshield_Gun", Point="true" }, + ["WieselOzelot CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_Wiesel2Ozelot" }, + ["BukM3-9M317M CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317M" }, + ["BukM3-9M317MA CHM"] = { Range=70, Blindspot=0.25, Height=35, Type="Medium", Radar="CH_BukM3_9A317MA" }, + ["SkySabre CHM"] = { Range=30, Blindspot=0.5, Height=10, Type="Medium", Radar="CH_SkySabreLN" }, + ["Stormer CHM"] = { Range=7.5, Blindspot=0.3, Height=7, Type="Short", Radar="CH_StormerHVM" }, + ["THAAD CHM"] = { Range=200, Blindspot=40, Height=150, Type="Long", Radar="CH_THAAD_M1120" }, + ["USInfantryFIM92K CHM"] = { Range=8, Blindspot=0.16, Height=4.8, Type="Short", Radar="CH_USInfantry_FIM92" }, + ["RBS98M CHM"] = { Range=20, Blindspot=0.2, Height=8, Type="Short", Radar="RBS-98" }, + ["RBS70 CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-70" }, + ["RBS70M CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS70" }, + ["RBS90 CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="RBS-90" }, + ["RBS90M CHM"] = { Range=8, Blindspot=0.25, Height=6, Type="Short", Radar="BV410_RBS90" }, + ["RBS103A CHM"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_Rb103A" }, + ["RBS103B CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_Rb103B" }, + ["RBS103AM CHM"] = { Range=160, Blindspot=1, Height=36, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103A" }, + ["RBS103BM CHM"] = { Range=120, Blindspot=3, Height=24.5, Type="Long", Radar="LvS-103_Lavett103_HX_Rb103B" }, + ["Lvkv9040M CHM"] = { Range=2, Blindspot=0.1, Height=1.2, Type="Point", Radar="LvKv9040",Point="true" }, } ----------------------------------------------------------------------- @@ -519,6 +564,7 @@ do self.SAM_Table_Long = {} self.SAM_Table_Medium = {} self.SAM_Table_Short = {} + self.SAM_Table_PointDef = {} self.dynamic = dynamic or false self.checkradius = 25000 self.grouping = 5000 @@ -547,10 +593,6 @@ do self.SuppressedGroups = {} -- 0.8 additions self.automode = true - self.radiusscale = {} - self.radiusscale[MANTIS.SamType.LONG] = 1.1 - self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 - self.radiusscale[MANTIS.SamType.SHORT] = 1.3 --self.SAMCheckRanges = {} self.usezones = false self.AcceptZones = {} @@ -559,6 +601,7 @@ do self.maxlongrange = 1 self.maxmidrange = 2 self.maxshortrange = 2 + self.maxpointdefrange = 6 self.maxclassic = 6 self.autoshorad = true self.ShoradGroupSet = SET_GROUP:New() -- Core.Set#SET_GROUP @@ -566,7 +609,10 @@ do self.SkateZones = nil self.SkateNumber = 3 - self.shootandscoot = false + self.shootandscoot = false + + self.SmokeDecoy = false + self.SmokeDecoyColor = SMOKECOLOR.White self.UseEmOnOff = true if EmOnOff == false then @@ -578,6 +624,7 @@ do else self.advAwacs = false end + -- Set the string id for output to DCS.log file. self.lid=string.format("MANTIS %s | ", self.name) @@ -637,9 +684,12 @@ do self.HQ_CC = GROUP:FindByName(self.HQ_Template_CC) end + -- counter for SAM table updates + self.checkcounter = 1 + -- TODO Version -- @field #string version - self.version="0.8.18" + self.version="0.9.27" self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) --- FSM Functions --- @@ -836,7 +886,7 @@ do self.AcceptZones = AcceptZones or {} self.RejectZones = RejectZones or {} self.ConflictZones = ConflictZones or {} - if #AcceptZones > 0 or #RejectZones > 0 or #ConflictZones > 0 then + if #self.AcceptZones > 0 or #self.RejectZones > 0 or #self.ConflictZones > 0 then self.usezones = true end return self @@ -875,19 +925,31 @@ do return self end + --- Function to set Short Range SAMs to spit out smoke as decoy, if an enemy plane is in range. + -- @param #MANTIS self + -- @param #boolean Onoff Set to true for on and nil/false for off. + -- @param #number Color (Optional) Color to use, defaults to `SMOKECOLOR.White` + function MANTIS:SetSmokeDecoy(Onoff,Color) + self.SmokeDecoy = Onoff + self.SmokeDecoyColor = Color or SMOKECOLOR.White + return self + end + --- Function to set number of SAMs going active on a valid, detected thread -- @param #MANTIS self -- @param #number Short Number of short-range systems activated, defaults to 1. -- @param #number Mid Number of mid-range systems activated, defaults to 2. -- @param #number Long Number of long-range systems activated, defaults to 2. -- @param #number Classic (non-automode) Number of overall systems activated, defaults to 6. + -- @param #number Point Number of point defense and AAA systems activated, defaults to 6. -- @return #MANTIS self - function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic) + function MANTIS:SetMaxActiveSAMs(Short,Mid,Long,Classic,Point) self:T(self.lid .. "SetMaxActiveSAMs") self.maxclassic = Classic or 6 self.maxlongrange = Long or 1 self.maxmidrange = Mid or 2 self.maxshortrange = Short or 2 + self.maxpointdefrange= Point or 6 return self end @@ -1089,6 +1151,24 @@ do end return self end + + --- [Internal] Check if any EWR or AWACS is still alive + -- @param #MANTIS self + -- @return #boolean outcome + function MANTIS:_CheckAnyEWRAlive() + self:T(self.lid .. "_CheckAnyEWRAlive") + local alive = false + if self.EWR_Group:CountAlive() > 0 then + alive = true + end + if not alive and self.AWACS_Prefix then + local awacs = GROUP:FindByName(self.AWACS_Prefix) + if awacs and awacs:IsAlive() then + alive = true + end + end + return alive + end --- [Internal] Function to determine state of the advanced mode -- @param #MANTIS self @@ -1263,9 +1343,9 @@ do -- DEBUG set = self:_PreFilterHeight(height) end - local friendlyset -- Core.Set#SET_GROUP - if self.checkforfriendlies == true then - friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce() + --self.friendlyset -- Core.Set#SET_GROUP + if self.checkforfriendlies == true and self.friendlyset == nil then + self.friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterStart() end for _,_coord in pairs (set) do local coord = _coord -- get current coord to check @@ -1281,20 +1361,21 @@ do zonecheck = self:_CheckCoordinateInZones(coord) end if self.verbose and self.debug then - local dectstring = coord:ToStringLLDMS() - local samstring = samcoordinate:ToStringLLDMS() + --local dectstring = coord:ToStringLLDMS() + local samstring = samcoordinate:ToStringMGRS({MGRS_Accuracy=0}) + samstring = string.gsub(samstring,"%s","") local inrange = "false" if targetdistance <= rad then inrange = "true" end - local text = string.format("Checking SAM at %s | Targetdist %d | Rad %d | Inrange %s", samstring, targetdistance, rad, inrange) + local text = string.format("Checking SAM at %s | Tgtdist %.1fkm | Rad %.1fkm | Inrange %s", samstring, targetdistance/1000, rad/1000, inrange) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) self:T(self.lid..text) end -- friendlies around? local nofriendlies = true if self.checkforfriendlies == true then - local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate) + local closestfriend, distance = self.friendlyset:GetClosestGroup(samcoordinate) if closestfriend and distance and distance < rad then nofriendlies = false end @@ -1395,7 +1476,7 @@ do -- @return #string type Long, medium or short range -- @return #number blind "blind" spot function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm) - self:T(self.lid.."_GetSAMRangeFromUnits") + self:T(self.lid.."_GetSAMDataFromUnits") local found = false local range = self.checkradius local height = 3000 @@ -1434,6 +1515,17 @@ do end if found then break end end + --- AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end + end if not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end @@ -1448,7 +1540,7 @@ do -- @return #string type Long, medium or short range -- @return #number blind "blind" spot function MANTIS:_GetSAMRange(grpname) - self:T(self.lid.."_GetSAMRange") + self:T(self.lid.."_GetSAMRange for "..tostring(grpname)) local range = self.checkradius local height = 3000 local type = MANTIS.SamType.MEDIUM @@ -1465,9 +1557,9 @@ do elseif string.find(grpname,"CHM",1,true) then CHMod = true end - if self.automode then + --if self.automode then for idx,entry in pairs(self.SamData) do - --self:I("ID = " .. idx) + self:T2("ID = " .. idx) if string.find(grpname,idx,1,true) then local _entry = entry -- #MANTIS.SamData type = _entry.Type @@ -1475,18 +1567,32 @@ do range = _entry.Range * 1000 * radiusscale -- max firing range height = _entry.Height * 1000 -- max firing height blind = _entry.Blindspot - --self:I("Matching Groupname = " .. grpname .. " Range= " .. range) + self:T("Matching Groupname = " .. grpname .. " Range= " .. range) found = true break end end + --end + --- Secondary - AAA or Point Defense + if not found then + local grp = GROUP:FindByName(grpname) + if (grp and grp:IsAlive() and grp:IsAAA()) or string.find(grpname,"AAA",1,true) then + range = 2000 + height = 2000 + blind = 50 + type = MANTIS.SamType.POINT + found = true + end end - -- secondary filter if not found - if (not found and self.automode) or HDSmod or SMAMod or CHMod then + --- Tertiary filter if not found + if (not found) or HDSmod or SMAMod or CHMod then range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod) elseif not found then self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname)) end + if found and string.find(grpname,"SHORAD",1,true) then + type = MANTIS.SamType.POINT -- force short on match + end return range, height, type, blind end @@ -1504,6 +1610,7 @@ do local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA 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 @@ -1522,23 +1629,27 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange,grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) --table.insert( SEAD_Grps, grpname ) if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type LONG") + self:T("SAM "..grpname.." is type LONG") elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) table.insert( SEAD_Grps, grpname ) - --self:T("SAM "..grpname.." is type MEDIUM") + self:T("SAM "..grpname.." is type MEDIUM") elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) - --self:T("SAM "..grpname.." is type SHORT") + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) + table.insert( SEAD_Grps, grpname ) + self:T("SAM "..grpname.." is type SHORT") + elseif type == MANTIS.SamType.POINT then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T("SAM "..grpname.." is type POINT") self.ShoradGroupSet:Add(grpname,group) if not self.autoshorad then table.insert( SEAD_Grps, grpname ) - end + end end self.SamStateTracker[grpname] = "GREEN" end @@ -1547,6 +1658,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive local mysead = SEAD:New( SEAD_Grps, self.Padding ) -- Functional.Sead#SEAD mysead:SetEngagementRange(engagerange) @@ -1570,7 +1682,8 @@ do local SAM_Tbl = {} -- table of SAM defense zones local SAM_Tbl_lg = {} -- table of long range SAM defense zones local SAM_Tbl_md = {} -- table of mid range SAM defense zones - local SAM_Tbl_sh = {} -- table of short range SAM defense zon + local SAM_Tbl_sh = {} -- table of short range SAM defense zones + local SAM_Tbl_pt = {} -- table of point defense/AAA 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 @@ -1581,17 +1694,21 @@ do local grpname = group:GetName() local grpcoord = group:GetCoordinate() local grprange, grpheight,type,blind = self:_GetSAMRange(grpname) - table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind}) -- make the table lighter, as I don't really use the zone here + local radaralive = group:IsSAM() + table.insert( SAM_Tbl, {grpname, grpcoord, grprange, grpheight, blind, type}) -- make the table lighter, as I don't really use the zone here table.insert( SEAD_Grps, grpname ) - if type == MANTIS.SamType.LONG then - table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.MEDIUM then - table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind}) - --self:I({grpname,grprange, grpheight}) - elseif type == MANTIS.SamType.SHORT then - table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind}) - -- self:I({grpname,grprange, grpheight}) + if type == MANTIS.SamType.LONG and radaralive then + table.insert( SAM_Tbl_lg, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.MEDIUM and radaralive then + table.insert( SAM_Tbl_md, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.SHORT and radaralive then + table.insert( SAM_Tbl_sh, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) + elseif type == MANTIS.SamType.POINT or (not radaralive) then + table.insert( SAM_Tbl_pt, {grpname, grpcoord, grprange, grpheight, blind, type}) + self:T({grpname,grprange, grpheight}) self.ShoradGroupSet:Add(grpname,group) if self.autoshorad then self.Shorad.Groupset = self.ShoradGroupSet @@ -1603,6 +1720,7 @@ do self.SAM_Table_Long = SAM_Tbl_lg self.SAM_Table_Medium = SAM_Tbl_md self.SAM_Table_Short = SAM_Tbl_sh + self.SAM_Table_PointDef = SAM_Tbl_pt -- make SAMs evasive if self.mysead ~= nil then local mysead = self.mysead @@ -1646,20 +1764,33 @@ do -- @param #table detset Table of COORDINATES -- @param #boolean dlink Using DLINK -- @param #number limit of SAM sites to go active on a contact - -- @return #MANTIS self + -- @return #number instatusred + -- @return #number instatusgreen + -- @return #number activeshorads function MANTIS:_CheckLoop(samset,detset,dlink,limit) self:T(self.lid .. "CheckLoop " .. #detset .. " Coordinates") local switchedon = 0 + local instatusred = 0 + local instatusgreen = 0 + local activeshorads = 0 + local SEADactive = 0 for _,_data in pairs (samset) do local samcoordinate = _data[2] local name = _data[1] local radius = _data[3] local height = _data[4] local blind = _data[5] * 1.25 + 1 + local shortsam = (_data[6] == MANTIS.SamType.SHORT) and true or false + if not shortsam then + shortsam = (_data[6] == MANTIS.SamType.POINT) and true or false + end local samgroup = GROUP:FindByName(name) local IsInZone, Distance = self:_CheckObjectInZone(detset, samcoordinate, radius, height, dlink) local suppressed = self.SuppressedGroups[name] or false - local activeshorad = self.Shorad.ActiveGroups[name] or false + local activeshorad = false + if self.Shorad and self.Shorad.ActiveGroups and self.Shorad.ActiveGroups[name] then + activeshorad = true + end if IsInZone and not suppressed and not activeshorad then --check any target in zone and not currently managed by SEAD if samgroup:IsAlive() then -- switch on SAM @@ -1672,12 +1803,23 @@ do elseif (not self.UseEmOnOff) and switchedon < limit then samgroup:OptionAlarmStateRed() switchedon = switchedon + 1 - switch = true + switch = true end if self.SamStateTracker[name] ~= "RED" and switch then self:__RedState(1,samgroup) self.SamStateTracker[name] = "RED" end + -- TODO doesn't work + if shortsam == true and self.SmokeDecoy == true then + self:T("Smoking") + local units = samgroup:GetUnits() or {} + local smoke = self.SmokeDecoyColor or SMOKECOLOR.White + for _,unit in pairs(units) do + if unit and unit:IsAlive() then + unit:GetCoordinate():Smoke(smoke) + end + end + end -- link in to SHORAD if available -- DONE: Test integration fully if self.ShoradLink and (Distance < self.ShoradActDistance or Distance < blind ) then -- don't give SHORAD position away too early @@ -1690,7 +1832,7 @@ do -- debug output if (self.debug or self.verbose) and switch then local text = string.format("SAM %s in alarm state RED!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + --local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug if self.verbose then self:I(self.lid..text) end end end --end alive @@ -1708,13 +1850,27 @@ do end if self.debug or self.verbose then local text = string.format("SAM %s in alarm state GREEN!", name) - local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) + --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 - return self + end --end check + end --for loop + if self.debug or self.verbose then + for _,_status in pairs(self.SamStateTracker) do + if _status == "GREEN" then + instatusgreen=instatusgreen+1 + elseif _status == "RED" then + instatusred=instatusred+1 + end + end + if self.Shorad then + for _,_name in pairs(self.Shorad.ActiveGroups or {}) do + activeshorads=activeshorads+1 + end + end + end + return instatusred, instatusgreen, activeshorads end --- [Internal] Check detection function @@ -1727,22 +1883,38 @@ do --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 + -- update SAM Table evey 3 runs + if self.checkcounter%3 == 0 then self:_RefreshSAMTable() end + self.checkcounter = self.checkcounter + 1 + local instatusred = 0 + local instatusgreen = 0 + local activeshorads = 0 -- switch SAMs on/off if (n)one of the detected groups is inside their reach if self.automode then local samset = self.SAM_Table_Long -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxlongrange) + local instatusredl, instatusgreenl, activeshoradsl = self:_CheckLoop(samset,detset,dlink,self.maxlongrange) local samset = self.SAM_Table_Medium -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxmidrange) + local instatusredm, instatusgreenm, activeshoradsm = self:_CheckLoop(samset,detset,dlink,self.maxmidrange) local samset = self.SAM_Table_Short -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local instatusreds, instatusgreens, activeshoradss = self:_CheckLoop(samset,detset,dlink,self.maxshortrange) + local samset = self.SAM_Table_PointDef -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxpointdefrange) else local samset = self:_GetSAMTable() -- table of i.1=names, i.2=coordinates, i.3=firing range, i.4=firing height - self:_CheckLoop(samset,detset,dlink,self.maxclassic) + instatusred, instatusgreen, activeshorads = self:_CheckLoop(samset,detset,dlink,self.maxclassic) + end + if self.debug or self.verbose then + local statusreport = REPORT:New("\nMANTIS Status "..self.name) + statusreport:Add("+-----------------------------+") + statusreport:Add(string.format("+ SAM in RED State: %2d",instatusred)) + statusreport:Add(string.format("+ SAM in GREEN State: %2d",instatusgreen)) + if self.Shorad then + statusreport:Add(string.format("+ SHORAD active: %2d",activeshorads)) + end + statusreport:Add("+-----------------------------+") + MESSAGE:New(statusreport:Text(),10):ToAll():ToLog() end return self end @@ -1827,7 +1999,7 @@ do end --]] if self.autoshorad then - self.Shorad = SHORAD:New(self.name.."-SHORAD",self.name.."-SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) + self.Shorad = SHORAD:New(self.name.."-SHORAD","SHORAD",self.SAM_Group,self.ShoradActDistance,self.ShoradTime,self.coalition,self.UseEmOnOff) self.Shorad:SetDefenseLimits(80,95) self.ShoradLink = true self.Shorad.Groupset=self.ShoradGroupSet @@ -1852,12 +2024,34 @@ do if not self.state2flag then self:_Check(self.Detection,self.DLink) end - - --[[ check Awacs - if self.advAwacs and not self.state2flag then - self:_Check(self.AWACS_Detection,false) + + local EWRAlive = self:_CheckAnyEWRAlive() + + local function FindSAMSRTR() + for i=1,1000 do + local randomsam = self.SAM_Group:GetRandom() + if randomsam and randomsam:IsAlive() then + if randomsam:IsSAM() then return randomsam end + end + end + end + + -- Switch on a random SR/TR if no EWR left over + if not EWRAlive then + local randomsam = FindSAMSRTR() -- Wrapper.Group#GROUP + if randomsam and randomsam:IsAlive() then + if self.UseEmOnOff then + randomsam:EnableEmission(true) + else + randomsam:OptionAlarmStateRed() + end + local name = randomsam:GetName() + if self.SamStateTracker[name] ~= "RED" then + self:__RedState(1,randomsam) + self.SamStateTracker[name] = "RED" + end + end end - --]] -- relocate HQ and EWR if self.autorelocate then @@ -1867,8 +2061,6 @@ do local halfintv = math.floor(timepassed / relointerval) - --self:T({timepassed=timepassed, halfintv=halfintv}) - if halfintv >= 1 then self.TimeStamp = timer.getAbsTime() self:_Relocate() @@ -1997,7 +2189,7 @@ do local Shorad = self.Shorad local radius = self.checkradius local ontime = self.ShoradTime - Shorad:WakeUpShorad(Name, radius, ontime) + Shorad:WakeUpShorad(Name, radius, ontime, nil, true) self:__ShoradActivated(1,Name, radius, ontime) end return self diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index a27582f27..560410ae2 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -1,4 +1,4 @@ ---- **Functional** - Create random airtraffic in your missions. +--- **Functional** - Create random air traffic in your missions. -- -- === -- @@ -179,8 +179,8 @@ -- * Climb rate is set to a moderate value of ~1500 ft/min. -- * The standard descent rate follows the 3:1 rule, i.e. 1000 ft decent per 3 miles of travel. Hence, angle of descent is ~3.6 degrees. -- * A holding point is randomly selected at a distance between 5 and 10 km away from destination airport. --- * The altitude of theholding point is ~1200 m AGL. Holding patterns might or might not happen with variable duration. --- * If an aircraft is spawned in air, the procedure omitts taxi and take-off and starts with the climb/cruising part. +-- * The altitude of the holding point is ~1200 m AGL. Holding patterns might or might not happen with variable duration. +-- * If an aircraft is spawned in air, the procedure omits taxi and take-off and starts with the climb/cruising part. -- * All values are randomized for each spawned aircraft. -- -- ## Mission Editor Setup @@ -196,13 +196,13 @@ -- Voilà, your already done! -- -- Optionally, you can set a specific livery for the aircraft or give it some weapons. --- However, the aircraft will by default not engage any enemies. Think of them as beeing on a peaceful or ferry mission. +-- However, the aircraft will by default not engage any enemies. Think of them as being on a peaceful or ferry mission. -- -- ## Basic Lua Script -- -- ![Process](..\Presentations\RAT\RAT_Basic_Lua_Script.png) -- --- The basic Lua script for one template group consits of two simple lines as shown in the picture above. +-- The basic Lua script for one template group consists of two simple lines as shown in the picture above. -- -- * **Line 2** creates a new RAT object "yak". The only required parameter for the constructor @{#RAT.New}() is the name of the group as defined in the mission editor. In this example it is "RAT_YAK". -- * **Line 5** trigger the command to spawn the aircraft. The (optional) parameter for the @{#RAT.Spawn}() function is the number of aircraft to be spawned of this object. @@ -216,9 +216,9 @@ -- ## Parking Problems -- -- One big issue in DCS is that not all aircraft can be spawned on every airport or airbase. In particular, bigger aircraft might not have a valid parking spot at smaller airports and --- airstripes. This can lead to multiple problems in DCS. +-- airstrips. This can lead to multiple problems in DCS. -- --- * Landing: When an aircraft tries to land at an airport where it does not have a valid parking spot, it is immidiately despawned the moment its wheels touch the runway, i.e. +-- * Landing: When an aircraft tries to land at an airport where it does not have a valid parking spot, it is immediately despawned the moment its wheels touch the runway, i.e. -- when a landing event is triggered. This leads to the loss of the RAT aircraft. On possible way to circumvent the this problem is to let another RAT aircraft spawn at landing -- and not when it shuts down its engines. See the @{#RAT.RespawnAfterLanding}() function. -- * Spawning: When a big aircraft is dynamically spawned on a small airbase a few things can go wrong. For example, it could be spawned at a parking spot with a shelter. @@ -246,9 +246,9 @@ -- c17:Spawn(5) -- -- This would randomly spawn five C-17s but only on airports which have big open air parking spots. Note that also only destination airports are allowed --- which do have this type of parking spot. This should ensure that the aircraft is able to land at the destination without beeing despawned immidiately. +-- which do have this type of parking spot. This should ensure that the aircraft is able to land at the destination without being despawned immediately. -- --- Also, the aircraft are spawned only on the requested parking spot types and not on any other type. If no parking spot of this type is availabe at the +-- Also, the aircraft are spawned only on the requested parking spot types and not on any other type. If no parking spot of this type is available at the -- moment of spawning, the group is automatically spawned in air above the selected airport. -- -- ## Examples @@ -274,7 +274,7 @@ -- -- It is also possible to make aircraft "commute" between two airports, i.e. flying from airport A to B and then back from B to A, etc. -- This can be done by the @{#RAT.Commute}() function. Note that if no departure or destination airports are specified, the first departure and destination are chosen randomly. --- Then the aircraft will fly back and forth between those two airports indefinetly. +-- Then the aircraft will fly back and forth between those two airports indefinitely. -- -- -- ### Spawn in Air @@ -302,7 +302,7 @@ -- * @{#RAT.SetTakeoff}("cold"), which means that all aircraft are spawned with their engines off, -- * @{#RAT.SetTakeoff}("hot"), which means that all aircraft are spawned with their engines on, -- * @{#RAT.SetTakeoff}("runway"), which means that all aircraft are spawned already at the runway ready to takeoff. --- Note that in this case the default spawn intervall is set to 180 seconds in order to avoid aircraft jamms on the runway. Generally, this takeoff at runways should be used with care and problems are to be expected. +-- Note that in this case the default spawn intervall is set to 180 seconds in order to avoid aircraft jams on the runway. Generally, this takeoff at runways should be used with care and problems are to be expected. -- -- -- The options @{#RAT.SetMinDistance}() and @{#RAT.SetMaxDistance}() can be used to restrict the range from departure to destination. For example @@ -325,7 +325,7 @@ -- -- * @{#RAT.SetFLcruise}(300) will cause most planes fly around FL300. -- * @{#RAT.SetFLmin}(100) restricts the cruising alt such that no plane will fly below FL100. Note that this automatically changes the minimum distance from departure to destination. --- That means that only destinations are possible for which the aircraft has had enought time to reach that flight level and descent again. +-- That means that only destinations are possible for which the aircraft has had enough time to reach that flight level and descent again. -- * @{#RAT.SetFLmax}(200) will restrict the cruise alt to maximum FL200, i.e. no aircraft will travel above this height. -- -- @@ -762,10 +762,10 @@ function RAT:Spawn(naircraft) -- Set the coalition table based on choice of self.coalition and self.friendly. self:_SetCoalitionTable() - -- Get all airports of this map beloning to friendly coalition(s). + -- Get all airports of this map belonging to friendly coalition(s). self:_GetAirportsOfCoalition() - -- Set submenuname if it has not been set by user. + -- Set sub-menu name if it has not been set by user. if not self.SubMenuName then self.SubMenuName=self.alias end @@ -1302,9 +1302,9 @@ end --- Set name of destination airports or zones for the AI aircraft. -- @param #RAT self --- @param #string destinationnames Name of the destination airport or table of destination airports. +-- @param #string destinationnames Name of the destination airport or #table of destination airports. -- @return #RAT RAT self object. --- @usage RAT:SetDestination("Krymsk") makes all aircraft of this RAT oject fly to Krymsk airport. +-- @usage RAT:SetDestination("Krymsk") makes all aircraft of this RAT object fly to Krymsk airport. function RAT:SetDestination(destinationnames) self:F2(destinationnames) @@ -1564,7 +1564,7 @@ function RAT:NoRespawn() return self end ---- Number of tries to respawn an aircraft in case it has accitentally been spawned on runway. +--- Number of tries to respawn an aircraft in case it has accidentally been spawned on runway. -- @param #RAT self -- @param #number n Number of retries. Default is 3. -- @return #RAT RAT self object. @@ -1621,7 +1621,7 @@ function RAT:RespawnInAirNotAllowed() return self end ---- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediatly. +--- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediately. -- @param #RAT self -- @param #boolean switch If true, check is performed. If false, this check is omitted. -- @param #number radius Distance in meters until a unit is considered to have spawned accidentally on the runway. Default is 75 m. @@ -2135,7 +2135,12 @@ function RAT:_InitAircraft(DCSgroup) self.aircraft.length=16 self.aircraft.height=5 self.aircraft.width=9 + elseif DCStype == "Saab340" then -- <- These lines added + self.aircraft.length=19.73 -- <- These lines added + self.aircraft.height=6.97 -- <- These lines added + self.aircraft.width=21.44 -- <- These lines added end + self.aircraft.box=math.max(self.aircraft.length,self.aircraft.width) -- info message @@ -3809,15 +3814,20 @@ function RAT:Status(message, forID) local N0units=group:GetInitialSize() -- Monitor travelled distance since last check. - local Pnow=coords - local Dtravel=Pnow:Get2DDistance(ratcraft.Pnow) - ratcraft.Pnow=Pnow + local Dtravel=0 + if coords and ratcraft.Pnow then + local Dtravel=coords:Get2DDistance(ratcraft.Pnow) + ratcraft.Pnow=coords + end -- Add up the travelled distance. ratcraft.Distance=ratcraft.Distance+Dtravel -- Distance remaining to destination. - local Ddestination=Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) + local Ddestination=-1 + if ratcraft.Pnow then + Ddestination=ratcraft.Pnow:Get2DDistance(ratcraft.destination:GetCoordinate()) + end -- Status report. if (forID and spawnindex==forID) or (not forID) then diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index eb3fc4f28..62b85e828 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -107,6 +107,9 @@ -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor. -- @field #number Coalition Coalition side for the menu, if any. -- @field Core.Menu#MENU_MISSION menuF10root Specific user defined root F10 menu. +-- @field #number ceilingaltitude Range ceiling altitude in ft MSL. Aircraft above this altitude are not considered to be in the range. Default is 20000 ft. +-- @field #boolean ceilingenabled Range has a ceiling and is not unlimited. Default is false. + -- @extends Core.Fsm#FSM --- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven @@ -273,6 +276,10 @@ -- -- Create a range object. -- GoldwaterRange=RANGE:New("Goldwater Range") -- +-- -- Set and enable the range ceiling altitude in feet MSL. If aircraft are above this altitude they are not considered to be in the range. +-- GoldwaterRange:SetRangeCeiling(20000) +-- GoldwaterRange:EnableRangeCeiling(true) +-- -- -- Distance between strafe target and foul line. You have to specify the names of the unit or static objects. -- -- Note that this could also be done manually by simply measuring the distance between the target and the foul line in the ME. -- GoldwaterRange:GetFoullineDistance("GWR Strafe Pit Left 1", "GWR Foul Line Left") @@ -358,6 +365,8 @@ RANGE = { targetpath = nil, targetprefix = nil, Coalition = nil, + ceilingaltitude = 20000, + ceilingenabled = false, } --- Default range parameters. @@ -1085,6 +1094,37 @@ function RANGE:SetRangeZone( zone ) return self end +--- Set range ceiling altitude in feet MSL. +-- @param #RANGE self +-- @param #number altitude (optional) Ceiling altitude of the range in ft MSL. Default 20000ft MSL +-- @return #RANGE self +function RANGE:SetRangeCeiling( altitude ) + self:T(self.lid.."SetRangeCeiling") + if altitude and type(altitude) == "number" then + self.ceilingaltitude=altitude + else + self:E(self.lid.."Altitude either not provided or is not a number, using default setting (20000).") + self.ceilingaltitude=20000 + end + return self +end + +--- Enable range ceiling. Aircraft must be below the ceiling altitude to be considered in the range zone. +-- @param #RANGE self +-- @param #boolean enabled True if you would like to enable the ceiling check. If no value give, will Default to false. +-- @return #RANGE self +function RANGE:EnableRangeCeiling( enabled ) + self:T(self.lid.."EnableRangeCeiling") + if enabled and type(enabled) == "boolean" then + self.ceilingenabled=enabled + else + self:E(self.lid.."Enabled either not provide or is not a boolean, using default setting (false).") + self.ceilingenabled=false + end + + return self +end + --- Set smoke color for marking bomb targets. By default bomb targets are marked by red smoke. -- @param #RANGE self -- @param Utilities.Utils#SMOKECOLOR colorid Color id. Default `SMOKECOLOR.Red`. @@ -1239,10 +1279,12 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, self.instructmsrs:SetLabel("RANGEI") self.instructmsrs:SetVolume(Volume or 1.0) self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") - - if PathToGoogleKey then - self.controlmsrs:SetGoogle(PathToGoogleKey) - self.instructmsrs:SetGoogle(PathToGoogleKey) + + if PathToGoogleKey then + self.controlmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) + self.controlmsrs:SetProvider(MSRS.Provider.GOOGLE) + self.instructmsrs:SetProviderOptionsGoogle(PathToGoogleKey,PathToGoogleKey) + self.instructmsrs:SetProvider(MSRS.Provider.GOOGLE) end else @@ -1338,7 +1380,7 @@ end -- @return #RANGE self function RANGE:SetSoundfilesPath( path ) self.soundpath = tostring( path or "Range Soundfiles/" ) - self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) + self:T2( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) ) return self end @@ -1634,9 +1676,9 @@ function RANGE:AddBombingTargetUnit( unit, goodhitrange, randommove ) -- Debug or error output. if _isstatic == true then - self:I( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) + self:T( self.lid .. string.format( "Adding STATIC bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) elseif _isstatic == false then - self:I( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) + self:T( self.lid .. string.format( "Adding UNIT bombing target %s with good hit range %d. Random move = %s.", name, goodhitrange, tostring( randommove ) ) ) else self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found. Carefully check all UNIT and STATIC names defined in the mission editor!", name ) ) end @@ -1704,7 +1746,7 @@ function RANGE:AddBombingTargetScenery( scenery, goodhitrange) -- Debug or error output. if name then - self:I( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) ) + self:T( self.lid .. string.format( "Adding SCENERY bombing target %s with good hit range %d", name, goodhitrange) ) else self:E( self.lid .. string.format( "ERROR! No bombing target with name %s could be found!", name ) ) end @@ -1809,7 +1851,7 @@ function RANGE:OnEventBirth( EventData ) if not EventData.IniPlayerName then return end local _unitName = EventData.IniUnitName - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName ) self:T3( self.lid .. "BIRTH: unit = " .. tostring( EventData.IniUnitName ) ) self:T3( self.lid .. "BIRTH: group = " .. tostring( EventData.IniGroupName ) ) @@ -1891,7 +1933,7 @@ function RANGE:OnEventHit( EventData ) local _currentTarget = self.strafeStatus[_unitID] --#RANGE.StrafeStatus -- Player has rolled in on a strafing target. - if _currentTarget and target:IsAlive() then + if _currentTarget and target and target:IsAlive() then local playerPos = _unit:GetCoordinate() local targetPos = target:GetCoordinate() @@ -1965,7 +2007,9 @@ end -- @param #number attackAlt Attack altitude. -- @param #number attackVel Attack velocity. function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel) - + + if not playerData then return end + -- Get closet target to last position. local _closetTarget = nil -- #RANGE.BombTarget local _distance = nil @@ -1982,13 +2026,13 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV -- Coordinate of impact point. local impactcoord = weapon:GetImpactCoordinate() - -- Check if impact happened in range zone. + -- Check if impact happened in range zone.+ local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) -- Smoke impact point of bomb. - if playerData.smokebombimpact and insidezone then - if playerData.delaysmoke then + if playerData and playerData.smokebombimpact and insidezone then + if playerData and playerData.delaysmoke then timer.scheduleFunction( self._DelayedSmoke, { coord = impactcoord, color = playerData.smokecolor }, timer.getTime() + self.TdelaySmoke ) else impactcoord:Smoke( playerData.smokecolor ) @@ -2113,7 +2157,7 @@ function RANGE:OnEventShot( EventData ) local _unitName = EventData.IniUnitName -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername = self:_GetPlayerUnitAndName( _unitName, EventData.IniPlayerName ) -- Distance Player-to-Range. Set this to larger value than the threshold. local dPR = self.BombtrackThreshold * 2 @@ -2125,11 +2169,13 @@ function RANGE:OnEventShot( EventData ) end -- Only track if distance player to range is < 25 km. Also check that a player shot. No need to track AI weapons. - if _track and dPR <= self.BombtrackThreshold and _unit and _playername then + if _track and dPR <= self.BombtrackThreshold and _unit and _playername and self.PlayerSettings[_playername] then -- Player data. local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData - + + if not playerData then return end + -- Attack parameters. local attackHdg=_unit:GetHeading() local attackAlt=_unit:GetHeight() @@ -2190,7 +2236,7 @@ function RANGE:onafterStatus( From, Event, To ) end -- Check range status. - self:I( self.lid .. text ) + self:T( self.lid .. text ) end @@ -2391,14 +2437,14 @@ function RANGE:onafterSave( From, Event, To ) if f then f:write( data ) f:close() - self:I( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) ) + self:T( self.lid .. string.format( "Saving player results to file %s", tostring( filename ) ) ) else self:E( self.lid .. string.format( "ERROR: Could not save results to file %s", tostring( filename ) ) ) end end -- Path. - local path = lfs.writedir() .. [[Logs\]] + local path = self.targetpath or lfs.writedir() .. [[Logs\]] -- Set file name. local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) @@ -2463,14 +2509,14 @@ function RANGE:onafterLoad( From, Event, To ) end -- Path in DCS log file. - local path = lfs.writedir() .. [[Logs\]] + local path = self.targetpath or lfs.writedir() .. [[Logs\]] -- Set file name. local filename = path .. string.format( "RANGE-%s_BombingResults.csv", self.rangename ) -- Info message. local text = string.format( "Loading player bomb results from file %s", filename ) - self:I( self.lid .. text ) + self:T( self.lid .. text ) -- Load asset data from file. local data = _loadfile( filename ) @@ -2843,7 +2889,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) -- Check if we have a player. if unit and playername then - self:I(playername) + --self:I(playername) -- Message text. local text = "" @@ -2981,7 +3027,7 @@ function RANGE:_DisplayBombTargets( _unitname ) end end - self:_DisplayMessageToGroup( _unit, _text, 120, true, true, _multiplayer ) + self:_DisplayMessageToGroup( _unit, _text, 150, true, true, _multiplayer ) end end @@ -3106,7 +3152,10 @@ function RANGE:_CheckPlayers() if unit and unit:IsAlive() then - if unit:IsInZone( self.rangezone ) then + local unitalt = unit:GetAltitude(false) + local unitaltinfeet = UTILS.MetersToFeet(unitalt) + + if unit:IsInZone(self.rangezone) and (not self.ceilingenabled or unitaltinfeet < self.ceilingaltitude) then ------------------------------ -- Player INSIDE Range Zone -- @@ -3447,10 +3496,10 @@ function RANGE:_AddF10Commands( _unitName ) -- Range menu local _rangePath = MENU_GROUP:New( group, self.rangename, _rootMenu ) - local _statsPath = MENU_GROUP:New( group, "Statistics", _rangePath ) - local _markPath = MENU_GROUP:New( group, "Mark Targets", _rangePath ) - local _settingsPath = MENU_GROUP:New( group, "My Settings", _rangePath ) local _infoPath = MENU_GROUP:New( group, "Range Info", _rangePath ) + local _markPath = MENU_GROUP:New( group, "Mark Targets", _rangePath ) + local _statsPath = MENU_GROUP:New( group, "Statistics", _rangePath ) + local _settingsPath = MENU_GROUP:New( group, "My Settings", _rangePath ) -- F10/On the Range//My Settings/ local _mysmokePath = MENU_GROUP:New( group, "Smoke Color", _settingsPath ) @@ -3854,13 +3903,13 @@ function RANGE:_TargetsheetOnOff( _unitname ) -- Inform player. if playerData and playerData.targeton == true then - text = string.format( "roger, your targetsheets are now SAVED." ) + text = string.format( "Roger, your targetsheets are now SAVED." ) else - text = string.format( "affirm, your targetsheets are NOT SAVED." ) + text = string.format( "Affirm, your targetsheets are NOT SAVED." ) end else - text = "negative, target sheet data recorder is broken on this range." + text = "Negative, target sheet data recorder is broken on this range." end -- Message to player. @@ -4097,8 +4146,8 @@ end -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. -- @return #boolean If true, group has > 1 player in it -function RANGE:_GetPlayerUnitAndName( _unitName ) - self:F2( _unitName ) +function RANGE:_GetPlayerUnitAndName( _unitName, PlayerName ) + --self:I( _unitName ) if _unitName ~= nil then @@ -4107,9 +4156,9 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) -- Get DCS unit from its name. local DCSunit = Unit.getByName( _unitName ) - if DCSunit then + if DCSunit and DCSunit.getPlayerName then - local playername = DCSunit:getPlayerName() + local playername = DCSunit:getPlayerName() or PlayerName or "None" local unit = UNIT:Find( DCSunit ) self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index c50e2d310..34b5e7d13 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -19,7 +19,7 @@ -- -- ### Authors: **applevangelist**, **FlightControl** -- --- Last Update: Dec 2023 +-- Last Update: Dec 2024 -- -- === -- @@ -28,6 +28,16 @@ --- -- @type SEAD +-- @field #string ClassName The Class Name. +-- @field #table TargetSkill Table of target skills. +-- @field #table SEADGroupPrefixes Table of SEAD prefixes. +-- @field #table SuppressedGroups Table of currently suppressed groups. +-- @field #number EngagementRange Engagement Range. +-- @field #number Padding Padding in seconds. +-- @field #function CallBack Callback function for suppression plans. +-- @field #boolean UseCallBack Switch for callback function to be used. +-- @field #boolean debug Debug switch. +-- @field #boolen WeaponTrack Track switch, if true track weapon speed for 30 secs. -- @extends Core.Base#BASE --- Make SAM sites execute evasive and defensive behaviour when being fired upon. @@ -56,10 +66,11 @@ SEAD = { SEADGroupPrefixes = {}, SuppressedGroups = {}, EngagementRange = 75, -- default 75% engagement range Feature Request #1355 - Padding = 10, + Padding = 15, CallBack = nil, UseCallBack = false, debug = false, + WeaponTrack = false, } --- Missile enumerators @@ -69,6 +80,7 @@ SEAD = { ["AGM_122"] = "AGM_122", ["AGM_84"] = "AGM_84", ["AGM_45"] = "AGM_45", + ["AGM_65"] = "AGM_65", ["ALARM"] = "ALARM", ["LD-10"] = "LD-10", ["X_58"] = "X_58", @@ -88,6 +100,7 @@ SEAD = { -- km and mach ["AGM_88"] = { 150, 3}, ["AGM_45"] = { 12, 2}, + ["AGM_65"] = { 16, 0.9}, ["AGM_122"] = { 16.5, 2.3}, ["AGM_84"] = { 280, 0.8}, ["ALARM"] = { 45, 2}, @@ -144,7 +157,7 @@ function SEAD:New( SEADGroupPrefixes, Padding ) self:AddTransition("*", "ManageEvasion", "*") self:AddTransition("*", "CalculateHitZone", "*") - self:I("*** SEAD - Started Version 0.4.6") + self:I("*** SEAD - Started Version 0.4.9") return self end @@ -371,7 +384,7 @@ function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADP reach = wpndata[1] * 1.1 local mach = wpndata[2] wpnspeed = math.floor(mach * 340.29) - if Weapon then + if Weapon and Weapon:GetSpeed() > 0 then wpnspeed = Weapon:GetSpeed() self:T(string.format("*** SEAD - Weapon Speed from WEAPON: %f m/s",wpnspeed)) end @@ -452,29 +465,38 @@ end -- @return #SEAD self function SEAD:HandleEventShot( EventData ) self:T( { EventData.id } ) - local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT - local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP - local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE - local SEADUnit = EventData.IniDCSUnit - local SEADUnitName = EventData.IniDCSUnitName - local SEADWeapon = EventData.Weapon -- Identify the weapon fired - local SEADWeaponName = EventData.WeaponName -- return weapon type - - local WeaponWrapper = WEAPON:New(EventData.Weapon) - --local SEADWeaponSpeed = WeaponWrapper:GetSpeed() -- mps - self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) - --self:T({ SEADWeapon }) - + local SEADWeapon = EventData.Weapon -- Identify the weapon fired + local SEADWeaponName = EventData.WeaponName or "None" -- return weapon type + if self:_CheckHarms(SEADWeaponName) then + --UTILS.PrintTableToLog(EventData) + local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT + + if not SEADPlane then return self end -- case IniUnit is empty + + local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP + local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE + local SEADUnit = EventData.IniDCSUnit + local SEADUnitName = EventData.IniDCSUnitName + + local WeaponWrapper = WEAPON:New(EventData.Weapon) -- Wrapper.Weapon#WEAPON + + self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) + self:T( '*** SEAD - Weapon Match' ) + if self.WeaponTrack == true then + WeaponWrapper:SetFuncTrack(function(weapon) env.info(string.format("*** Weapon Speed: %d m/s",weapon:GetSpeed() or -1)) end) + WeaponWrapper:StartTrack(0.1) + WeaponWrapper:StopTrack(30) + end local _targetskill = "Random" local _targetgroupname = "none" local _target = EventData.Weapon:getTarget() -- Identify target if not _target or self.debug then -- AGM-88 or 154 w/o target data self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None")) if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then - self:I("**** Tracking AGM-88/154 with no target data.") + self:T("**** Tracking AGM-88/154 with no target data.") local pos0 = SEADPlane:GetCoordinate() local fheight = SEADPlane:GetHeight() self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName) @@ -520,7 +542,7 @@ function SEAD:HandleEventShot( EventData ) end if SEADGroupFound == true then -- yes we are being attacked if string.find(SEADWeaponName,"ADM_141",1,true) then - self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) + self:__ManageEvasion(2,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,2,WeaponWrapper) else self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,0,WeaponWrapper) end diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua index d4f70447f..526ff062f 100644 --- a/Moose Development/Moose/Functional/Shorad.lua +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -21,7 +21,7 @@ -- @image Functional.Shorad.jpg -- -- Date: Nov 2021 --- Last Update: Nov 2023 +-- Last Update: Jan 2025 ------------------------------------------------------------------------- --- **SHORAD** class, extends Core.Base#BASE @@ -113,7 +113,7 @@ SHORAD = { SkateNumber = 3, SkateZones = nil, minscootdist = 100, - minscootdist = 3000, + maxscootdist = 3000, scootrandomcoord = false, } @@ -443,7 +443,9 @@ do for _,_groups in pairs (shoradset) do local groupname = _groups:GetName() if string.find(groupname, tgtgrp, 1, true) then - returnname = true + if _groups:IsSAM() then + returnname = true + end end end return returnname @@ -470,6 +472,7 @@ do -- @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 + -- @param #boolean ShotAt If true, function is called after a shot -- @return #SHORAD self -- @usage Use this function to integrate with other systems, example -- @@ -479,7 +482,7 @@ do -- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs") -- mymantis:AddShorad(myshorad,720) -- mymantis:Start() - function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat) + function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat, ShotAt) self:T(self.lid .. " WakeUpShorad") self:T({TargetGroup, Radius, ActiveTimer, TargetCat}) local targetcat = TargetCat or Object.Category.UNIT @@ -521,7 +524,27 @@ do -- go through set and find the one(s) to activate local TDiff = 4 for _,_group in pairs (shoradset) do - if _group:IsAnyInZone(targetzone) then + + local groupname = _group:GetName() + + if groupname == TargetGroup and ShotAt==true then + -- Shot at a SHORAD group + if self.UseEmOnOff then + _group:EnableEmission(false) + end + _group:OptionAlarmStateGreen() + self.ActiveGroups[groupname] = nil + local text = string.format("Shot at SHORAD %s! Evading!", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + + --Shoot and Scoot + if self.shootandscoot then + self:__ShootAndScoot(1,_group) + end + + elseif _group:IsAnyInZone(targetzone) or groupname == TargetGroup then + -- shot at a group we protect local text = string.format("Waking up SHORAD %s", _group:GetName()) self:T(text) local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) @@ -529,7 +552,6 @@ do _group:EnableEmission(true) end _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 @@ -607,7 +629,7 @@ do _targetgroupname = tgtgrp:GetName() -- group name _targetskill = tgtgrp:GetUnit(1):GetSkill() self:T("*** Found Target = ".. _targetgroupname) - self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT) + self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT,true) end end end @@ -736,7 +758,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, targetcat) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer, targetcat, true) end end end diff --git a/Moose Development/Moose/Functional/Tiresias.lua b/Moose Development/Moose/Functional/Tiresias.lua index 078bfda56..74c2b930e 100644 --- a/Moose Development/Moose/Functional/Tiresias.lua +++ b/Moose Development/Moose/Functional/Tiresias.lua @@ -97,7 +97,7 @@ TIRESIAS = { ClassName = "TIRESIAS", debug = false, - version = "0.0.4", + version = "0.0.5", Interval = 20, GroundSet = nil, VehicleSet = nil, @@ -187,7 +187,7 @@ function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA) return self end ---- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. +--- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times. Does **not** work work for GROUP objects spawned into the SET after start, i.e. the groups need to exist in the game already. -- @param #TIRESIAS self -- @param Core.Set#SET_GROUP Set to add to the exception list. -- @return #TIRESIAS self @@ -203,7 +203,7 @@ function TIRESIAS:AddExceptionSet(Set) } exceptions:AddGroup(grp,true) end - BASE:I("TIRESIAS: Added exception group: "..grp:GetName()) + BASE:T("TIRESIAS: Added exception group: "..grp:GetName()) end ) return self @@ -288,7 +288,7 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true if SwitchAAA then @@ -315,10 +315,11 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp:SetAIOff() grp.Tiresias.invisible = true + grp.Tiresias.AIOff = true end end --BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception))) @@ -336,7 +337,7 @@ function TIRESIAS:_InitGroups() } end if grp.Tiresias and (not grp.Tiresias.exception == true) then - if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then + if grp.Tiresias and grp.Tiresias.invisible == false then grp:SetCommandInvisible(true) grp.Tiresias.invisible = true end @@ -391,7 +392,9 @@ function TIRESIAS:_SwitchOnGroups(group,radius) if ground:CountAlive() > 0 then ground:ForEachGroupAlive( function(grp) - if grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then + local name = grp:GetName() + if grp:GetCoalition() ~= group:GetCoalition() + and grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then if grp.Tiresias.invisible == true then grp:SetCommandInvisible(false) grp.Tiresias.invisible = false @@ -407,7 +410,7 @@ function TIRESIAS:_SwitchOnGroups(group,radius) end --BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception))) else - BASE:E("TIRESIAS - This group has not been initialized or is an exception!") + BASE:T("TIRESIAS - This group "..tostring(name).. " has not been initialized or is an exception!") end end ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cab1d0899..29682074f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -6047,7 +6047,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol else - if #parking<#template.units and not airstart then + if parking and #parking<#template.units and not airstart then local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) self:_DebugMessage(text) return nil @@ -6089,7 +6089,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol terminal=parking[i].TerminalID end - if self.Debug then + if self.Debug and terminal then local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal) coord:MarkToAll(text) env.info(text) @@ -6732,7 +6732,7 @@ end -- @param Wrapper.Group#GROUP deadgroup Group of unit that died. -- @param #WAREHOUSE.Pendingitem request Request that needs to be updated. function WAREHOUSE:_UnitDead(deadunit, deadgroup, request) - self:F(self.lid.."FF unit dead "..deadunit:GetName()) + --self:F(self.lid.."FF unit dead "..deadunit:GetName()) -- Find opsgroup. local opsgroup=_DATABASE:FindOpsGroup(deadgroup) @@ -8122,9 +8122,11 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Debug output for occupied spots. if self.Debug then local coord=problem.coord --Core.Point#COORDINATE - local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) - self:I(self.lid..text) - coord:MarkToAll(string.format(text)) + if coord then + local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) + self:I(self.lid..text) + coord:MarkToAll(text) + end else self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) end @@ -8433,12 +8435,14 @@ function WAREHOUSE:_GetAttribute(group) local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute if group then + + local groupCat=group:GetCategory() ----------- --- Air --- ----------- -- Planes - local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") + local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") and groupCat==Group.Category.AIRPLANE local awacs=group:HasAttribute("AWACS") local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers")) local bomber=group:HasAttribute("Strategic bombers") @@ -8593,7 +8597,6 @@ end -- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed. -- @param #table queue The queue from which the item should be deleted. function WAREHOUSE:_DeleteQueueItem(qitem, queue) - self:F({qitem=qitem, queue=queue}) for i=1,#queue do local _item=queue[i] --#WAREHOUSE.Queueitem diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index d20732f20..b6f392426 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -48,7 +48,7 @@ do -- ZONE_CAPTURE_COALITION - --- @type ZONE_CAPTURE_COALITION + -- @type ZONE_CAPTURE_COALITION -- @field #string ClassName Name of the class. -- @field #number MarkBlue ID of blue F10 mark. -- @field #number MarkRed ID of red F10 mark. @@ -161,7 +161,7 @@ do -- ZONE_CAPTURE_COALITION -- The mission designer can use these values to alter the logic. -- For example: -- - -- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self + -- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To ) -- if From ~= "Empty" then -- -- Display a message @@ -172,7 +172,7 @@ do -- ZONE_CAPTURE_COALITION -- -- ## Example Event Handler. -- - -- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self + -- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To ) -- if From ~= To then -- local Coalition = self:GetCoalition() @@ -273,7 +273,7 @@ do -- ZONE_CAPTURE_COALITION -- Depending on the zone ownership, different messages are sent. -- Note the methods `ZoneCaptureCoalition:GetZoneName()`. -- - -- --- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self + -- -- @param Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterGuarded( From, Event, To ) -- if From ~= To then -- local Coalition = self:GetCoalition() @@ -294,7 +294,7 @@ do -- ZONE_CAPTURE_COALITION -- Next is the Event Handler when the **Empty** state transition is triggered. -- Now we smoke the ZoneCaptureCoalition with a green color, using `self:Smoke( SMOKECOLOR.Green )`. -- - -- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self + -- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterEmpty() -- self:Smoke( SMOKECOLOR.Green ) -- US_CC:MessageTypeToCoalition( string.format( "%s is unprotected, and can be captured!", ZoneCaptureCoalition:GetZoneName() ), MESSAGE.Type.Information ) @@ -304,7 +304,7 @@ do -- ZONE_CAPTURE_COALITION -- The next Event Handlers speak for itself. -- When the zone is Attacked, we smoke the zone white and send some messages to each coalition. -- - -- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self + -- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterAttacked() -- ZoneCaptureCoalition:Smoke( SMOKECOLOR.White ) -- local Coalition = self:GetCoalition() @@ -321,7 +321,7 @@ do -- ZONE_CAPTURE_COALITION -- When the zone is Captured, we send some victory or loss messages to the correct coalition. -- And we add some score. -- - -- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self + -- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterCaptured() -- local Coalition = self:GetCoalition() -- self:E({Coalition = Coalition}) @@ -641,7 +641,7 @@ do -- ZONE_CAPTURE_COALITION -- -- @usage -- -- For example, one could stop the monitoring when the zone was captured! - -- --- @param Functional.Protect#ZONE_CAPTURE_COALITION self + -- -- @param Functional.Protect#ZONE_CAPTURE_COALITION self -- function ZoneCaptureCoalition:OnEnterCaptured() -- local Coalition = self:GetCoalition() -- self:E({Coalition = Coalition}) diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua index 1044e7920..2d0b5a4b3 100644 --- a/Moose Development/Moose/Functional/ZoneGoal.lua +++ b/Moose Development/Moose/Functional/ZoneGoal.lua @@ -17,7 +17,7 @@ do -- Zone - --- @type ZONE_GOAL + -- @type ZONE_GOAL -- @field #string ClassName Name of the class. -- @field Core.Goal#GOAL Goal The goal object. -- @field #number SmokeTime Time stamp in seconds when the last smoke of the zone was triggered. @@ -178,7 +178,7 @@ do -- Zone end - --- @param #ZONE_GOAL self + -- @param #ZONE_GOAL self -- @param Core.Event#EVENTDATA EventData Event data table. function ZONE_GOAL:__Destroyed( EventData ) self:F( { "EventDead", EventData } ) diff --git a/Moose Development/Moose/Functional/ZoneGoalCargo.lua b/Moose Development/Moose/Functional/ZoneGoalCargo.lua index 7a19ed02a..4397f621c 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCargo.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCargo.lua @@ -21,7 +21,7 @@ do -- ZoneGoal - --- @type ZONE_GOAL_CARGO + -- @type ZONE_GOAL_CARGO -- @extends Functional.ZoneGoal#ZONE_GOAL @@ -55,7 +55,7 @@ do -- ZoneGoal ClassName = "ZONE_GOAL_CARGO", } - --- @field #table ZONE_GOAL_CARGO.States + -- @field #table ZONE_GOAL_CARGO.States ZONE_GOAL_CARGO.States = {} --- ZONE_GOAL_CARGO Constructor. diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua index 622843b10..82da20706 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -16,7 +16,7 @@ do -- ZoneGoal - --- @type ZONE_GOAL_COALITION + -- @type ZONE_GOAL_COALITION -- @field #string ClassName Name of the Class. -- @field #number Coalition The current coalition ID of the zone owner. -- @field #number PreviousCoalition The previous owner of the zone. @@ -48,7 +48,7 @@ do -- ZoneGoal ObjectCategories = nil, } - --- @field #table ZONE_GOAL_COALITION.States + -- @field #table ZONE_GOAL_COALITION.States ZONE_GOAL_COALITION.States = {} --- ZONE_GOAL_COALITION Constructor. diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 4762a10fb..771547060 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -10,7 +10,7 @@ _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.ScheduleDispatcher#SCHEDU _DATABASE = DATABASE:New() -- Core.Database#DATABASE --- Settings -_SETTINGS = SETTINGS:Set() +_SETTINGS = SETTINGS:Set() -- Core.Settings#SETTINGS _SETTINGS:SetPlayerMenuOn() --- Register cargos. diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index f79787862..f378568f9 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -1,8 +1,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Enums.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Utils.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Profiler.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Templates.lua' ) -__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/STTS.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/FiFo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Utilities/Socket.lua' ) @@ -49,6 +47,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Marker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Weapon.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Net.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Storage.lua' ) +__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/DynamicCargo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/Cargo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Cargo/CargoUnit.lua' ) @@ -84,6 +83,7 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Autolase.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ZoneGoalCargo.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' ) +__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ClientWatch.lua') __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' ) diff --git a/Moose Development/Moose/Modules_local.lua b/Moose Development/Moose/Modules_local.lua index abc01b2aa..15c6bcba5 100644 --- a/Moose Development/Moose/Modules_local.lua +++ b/Moose Development/Moose/Modules_local.lua @@ -1,9 +1,7 @@ __Moose.Include( 'Utilities\\Enums.lua' ) -__Moose.Include( 'Utilities\\Routines.lua' ) __Moose.Include( 'Utilities\\Utils.lua' ) __Moose.Include( 'Utilities\\Profiler.lua' ) -__Moose.Include( 'Utilities\\Templates.lua' ) -__Moose.Include( 'Utilities\\STTS.lua' ) +--__Moose.Include( 'Utilities\\STTS.lua' ) __Moose.Include( 'Utilities\\FiFo.lua' ) __Moose.Include( 'Utilities\\Socket.lua' ) @@ -17,11 +15,11 @@ __Moose.Include( 'Core\\Event.lua' ) __Moose.Include( 'Core\\Settings.lua' ) __Moose.Include( 'Core\\Menu.lua' ) __Moose.Include( 'Core\\Zone.lua' ) -__Moose.Include( 'Core\\Zone_Detection.lua' ) +__Moose.Include( 'Core\\Velocity.lua' ) __Moose.Include( 'Core\\Database.lua' ) __Moose.Include( 'Core\\Set.lua' ) __Moose.Include( 'Core\\Point.lua' ) -__Moose.Include( 'Core\\Velocity.lua' ) +__Moose.Include( 'Core\\Pathline.lua' ) __Moose.Include( 'Core\\Message.lua' ) __Moose.Include( 'Core\\Fsm.lua' ) __Moose.Include( 'Core\\Spawn.lua' ) @@ -46,6 +44,10 @@ __Moose.Include( 'Wrapper\\Static.lua' ) __Moose.Include( 'Wrapper\\Airbase.lua' ) __Moose.Include( 'Wrapper\\Scenery.lua' ) __Moose.Include( 'Wrapper\\Marker.lua' ) +__Moose.Include( 'Wrapper\\Net.lua' ) +__Moose.Include( 'Wrapper\\Weapon.lua' ) +__Moose.Include( 'Wrapper\\Storage.lua' ) +__Moose.Include( 'Wrapper\\DynamicCargo.lua' ) __Moose.Include( 'Cargo\\Cargo.lua' ) __Moose.Include( 'Cargo\\CargoUnit.lua' ) @@ -77,6 +79,10 @@ __Moose.Include( 'Functional\\Mantis.lua' ) __Moose.Include( 'Functional\\Shorad.lua' ) __Moose.Include( 'Functional\\Autolase.lua' ) __Moose.Include( 'Functional\\AICSAR.lua' ) +__Moose.Include( 'Functional\\AmmoTruck.lua' ) +__Moose.Include( 'Functional\\Tiresias.lua' ) +__Moose.Include( 'Functional\\Stratego.lua' ) +__Moose.Include( 'Functional\\ClientWatch.lua' ) __Moose.Include( 'Ops\\Airboss.lua' ) __Moose.Include( 'Ops\\RecoveryTanker.lua' ) @@ -107,6 +113,9 @@ __Moose.Include( 'Ops\\Awacs.lua' ) __Moose.Include( 'Ops\\PlayerTask.lua' ) __Moose.Include( 'Ops\\Operation.lua' ) __Moose.Include( 'Ops\\FlightControl.lua' ) +__Moose.Include( 'Ops\\PlayerRecce.lua' ) +__Moose.Include( 'Ops\\EasyGCICAP.lua' ) +__Moose.Include( 'Ops\\EasyA2G.lua' ) __Moose.Include( 'AI\\AI_Balancer.lua' ) __Moose.Include( 'AI\\AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 700cc74bd..70f4f0848 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -19,7 +19,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) +-- * SRS Simple-Text-To-Speech (MSRS) integration (no sound files necessary) -- -- === -- @@ -291,7 +291,7 @@ -- ## Nevada: Nellis AFB -- -- -- ATIS Nellis AFB on 270.10 MHz AM. --- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis_AFB, 270.1) +-- atisNellis=ATIS:New(AIRBASE.Nevada.Nellis, 270.1) -- atisNellis:SetRadioRelayUnitName("Radio Relay Nellis") -- atisNellis:SetActiveRunway("21L") -- atisNellis:SetTowerFrequencies({327.000, 132.550}) @@ -302,7 +302,7 @@ -- ## Persian Gulf: Abu Dhabi International Airport -- -- -- ATIS Abu Dhabi International on 125.1 MHz AM. --- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_International_Airport, 125.1) +-- atisAbuDhabi=ATIS:New(AIRBASE.PersianGulf.Abu_Dhabi_Intl, 125.1) -- atisAbuDhabi:SetRadioRelayUnitName("Radio Relay Abu Dhabi International Airport") -- atisAbuDhabi:SetMetricUnits() -- atisAbuDhabi:SetActiveRunway("L") @@ -498,6 +498,9 @@ ATIS.Alphabet = { -- @field #number Syria +5° (East). -- @field #number MarianaIslands +2° (East). -- @field #number SinaiMap +5° (East). +-- @field #number Kola +15° (East). +-- @field #number Afghanistan +3° (East). +-- @field #number Iraq +4.4° (East). ATIS.RunwayM2T = { Caucasus = 0, Nevada = 12, @@ -508,6 +511,9 @@ ATIS.RunwayM2T = { MarianaIslands = 2, Falklands = 12, SinaiMap = 5, + Kola = 15, + Afghanistan = 3, + Iraq=4.4 } --- Whether ICAO phraseology is used for ATIS broadcasts. @@ -521,6 +527,9 @@ ATIS.RunwayM2T = { -- @field #boolean MarianaIslands true. -- @field #boolean Falklands true. -- @field #boolean SinaiMap true. +-- @field #boolean Kola true. +-- @field #boolean Afghanistan true. +-- @field #boolean Iraq true. ATIS.ICAOPhraseology = { Caucasus = true, Nevada = false, @@ -531,6 +540,9 @@ ATIS.ICAOPhraseology = { MarianaIslands = true, Falklands = true, SinaiMap = true, + Kola = true, + Afghanistan = true, + Iraq = true, } --- Nav point data. @@ -619,83 +631,83 @@ ATIS.ICAOPhraseology = { -- @field #ATIS.Soundfile TACANChannel -- @field #ATIS.Soundfile VORFrequency ATIS.Sound = { - ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.99 }, - ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 0.99 }, - ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 0.99 }, - AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 3.00 }, - Airport = { filename = "Airport.ogg", duration = 0.66 }, - Altimeter = { filename = "Altimeter.ogg", duration = 0.68 }, - At = { filename = "At.ogg", duration = 0.41 }, - CloudBase = { filename = "CloudBase.ogg", duration = 0.82 }, - CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.61 }, - CloudsBroken = { filename = "CloudsBroken.ogg", duration = 1.07 }, - CloudsFew = { filename = "CloudsFew.ogg", duration = 0.99 }, - CloudsNo = { filename = "CloudsNo.ogg", duration = 1.01 }, - CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.35 }, - CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.83 }, - CloudsScattered = { filename = "CloudsScattered.ogg", duration = 1.18 }, - Decimal = { filename = "Decimal.ogg", duration = 0.54 }, - DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.27 }, - DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.23 }, - DewPoint = { filename = "DewPoint.ogg", duration = 0.65 }, - Dust = { filename = "Dust.ogg", duration = 0.54 }, - Elevation = { filename = "Elevation.ogg", duration = 0.78 }, - EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.15 }, - Feet = { filename = "Feet.ogg", duration = 0.45 }, - Fog = { filename = "Fog.ogg", duration = 0.47 }, - Gusting = { filename = "Gusting.ogg", duration = 0.55 }, - HectoPascal = { filename = "HectoPascal.ogg", duration = 1.15 }, - Hundred = { filename = "Hundred.ogg", duration = 0.47 }, - InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.16 }, - Information = { filename = "Information.ogg", duration = 0.85 }, - Kilometers = { filename = "Kilometers.ogg", duration = 0.78 }, - Knots = { filename = "Knots.ogg", duration = 0.59 }, - Left = { filename = "Left.ogg", duration = 0.54 }, - MegaHertz = { filename = "MegaHertz.ogg", duration = 0.87 }, - Meters = { filename = "Meters.ogg", duration = 0.59 }, - MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.14 }, - Miles = { filename = "Miles.ogg", duration = 0.60 }, - MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.53 }, - Minus = { filename = "Minus.ogg", duration = 0.64 }, - N0 = { filename = "N-0.ogg", duration = 0.55 }, - N1 = { filename = "N-1.ogg", duration = 0.41 }, - N2 = { filename = "N-2.ogg", duration = 0.37 }, - N3 = { filename = "N-3.ogg", duration = 0.41 }, - N4 = { filename = "N-4.ogg", duration = 0.37 }, - N5 = { filename = "N-5.ogg", duration = 0.43 }, - N6 = { filename = "N-6.ogg", duration = 0.55 }, - N7 = { filename = "N-7.ogg", duration = 0.43 }, - N8 = { filename = "N-8.ogg", duration = 0.38 }, - N9 = { filename = "N-9.ogg", duration = 0.55 }, - NauticalMiles = { filename = "NauticalMiles.ogg", duration = 1.04 }, - None = { filename = "None.ogg", duration = 0.43 }, - QFE = { filename = "QFE.ogg", duration = 0.63 }, - QNH = { filename = "QNH.ogg", duration = 0.71 }, - Rain = { filename = "Rain.ogg", duration = 0.41 }, - Right = { filename = "Right.ogg", duration = 0.44 }, - Snow = { filename = "Snow.ogg", duration = 0.48 }, - SnowStorm = { filename = "SnowStorm.ogg", duration = 0.82 }, - StatuteMiles = { filename = "StatuteMiles.ogg", duration = 1.15 }, - SunriseAt = { filename = "SunriseAt.ogg", duration = 0.92 }, - SunsetAt = { filename = "SunsetAt.ogg", duration = 0.95 }, - Temperature = { filename = "Temperature.ogg", duration = 0.64 }, - Thousand = { filename = "Thousand.ogg", duration = 0.55 }, - ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.81 }, - TimeLocal = { filename = "TimeLocal.ogg", duration = 0.90 }, - TimeZulu = { filename = "TimeZulu.ogg", duration = 0.86 }, - TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.19 }, - Visibilty = { filename = "Visibility.ogg", duration = 0.79 }, - WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.07 }, - WindFrom = { filename = "WindFrom.ogg", duration = 0.60 }, + ActiveRunway = { filename = "ActiveRunway.ogg", duration = 0.85 }, + ActiveRunwayDeparture = { filename = "ActiveRunwayDeparture.ogg", duration = 1.50 }, + ActiveRunwayArrival = { filename = "ActiveRunwayArrival.ogg", duration = 1.38 }, + AdviceOnInitial = { filename = "AdviceOnInitial.ogg", duration = 2.98 }, + Airport = { filename = "Airport.ogg", duration = 0.55 }, + Altimeter = { filename = "Altimeter.ogg", duration = 0.91 }, + At = { filename = "At.ogg", duration = 0.32 }, + CloudBase = { filename = "CloudBase.ogg", duration = 0.69 }, + CloudCeiling = { filename = "CloudCeiling.ogg", duration = 0.53 }, + CloudsBroken = { filename = "CloudsBroken.ogg", duration = 0.81 }, + CloudsFew = { filename = "CloudsFew.ogg", duration = 0.74 }, + CloudsNo = { filename = "CloudsNo.ogg", duration = 0.69}, + CloudsNotAvailable = { filename = "CloudsNotAvailable.ogg", duration = 2.64 }, + CloudsOvercast = { filename = "CloudsOvercast.ogg", duration = 0.82 }, + CloudsScattered = { filename = "CloudsScattered.ogg", duration = 0.89 }, + Decimal = { filename = "Decimal.ogg", duration = 0.71 }, + DegreesCelsius = { filename = "DegreesCelsius.ogg", duration = 1.08 }, + DegreesFahrenheit = { filename = "DegreesFahrenheit.ogg", duration = 1.07 }, + DewPoint = { filename = "DewPoint.ogg", duration = 0.59 }, + Dust = { filename = "Dust.ogg", duration = 0.37 }, + Elevation = { filename = "Elevation.ogg", duration = 0.92 }, + EndOfInformation = { filename = "EndOfInformation.ogg", duration = 1.24 }, + Feet = { filename = "Feet.ogg", duration = 0.34 }, + Fog = { filename = "Fog.ogg", duration = 0.41 }, + Gusting = { filename = "Gusting.ogg", duration = 0.58 }, + HectoPascal = { filename = "HectoPascal.ogg", duration = 0.92 }, + Hundred = { filename = "Hundred.ogg", duration = 0.53 }, ILSFrequency = { filename = "ILSFrequency.ogg", duration = 1.30 }, - InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.56 }, - OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.59 }, - RunwayLength = { filename = "RunwayLength.ogg", duration = 0.91 }, - VORFrequency = { filename = "VORFrequency.ogg", duration = 1.38 }, - TACANChannel = { filename = "TACANChannel.ogg", duration = 0.88 }, - PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.18 }, - RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.14 }, - Zulu = { filename = "Zulu.ogg", duration = 0.62 }, + InchesOfMercury = { filename = "InchesOfMercury.ogg", duration = 1.26 }, + Information = { filename = "Information.ogg", duration = 0.99 }, + InnerNDBFrequency = { filename = "InnerNDBFrequency.ogg", duration = 1.69 }, + Kilometers = { filename = "Kilometers.ogg", duration = 0.93 }, + Knots = { filename = "Knots.ogg", duration = 0.46 }, + Left = { filename = "Left.ogg", duration = 0.41 }, + MegaHertz = { filename = "MegaHertz.ogg", duration = 0.83 }, + Meters = { filename = "Meters.ogg", duration = 0.55 }, + MetersPerSecond = { filename = "MetersPerSecond.ogg", duration = 1.03 }, + Miles = { filename = "Miles.ogg", duration = 0.44 }, + MillimetersOfMercury = { filename = "MillimetersOfMercury.ogg", duration = 1.59 }, + Minus = { filename = "Minus.ogg", duration = 0.55 }, + N0 = { filename = "N-0.ogg", duration = 0.52 }, + N1 = { filename = "N-1.ogg", duration = 0.35 }, + N2 = { filename = "N-2.ogg", duration = 0.41 }, + N3 = { filename = "N-3.ogg", duration = 0.34 }, + N4 = { filename = "N-4.ogg", duration = 0.37 }, + N5 = { filename = "N-5.ogg", duration = 0.40 }, + N6 = { filename = "N-6.ogg", duration = 0.46 }, + N7 = { filename = "N-7.ogg", duration = 0.52 }, + N8 = { filename = "N-8.ogg", duration = 0.36 }, + N9 = { filename = "N-9.ogg", duration = 0.51 }, + NauticalMiles = { filename = "NauticalMiles.ogg", duration = 0.93 }, + None = { filename = "None.ogg", duration = 0.33 }, + OuterNDBFrequency = { filename = "OuterNDBFrequency.ogg", duration = 1.70 }, + PRMGChannel = { filename = "PRMGChannel.ogg", duration = 1.27 }, + QFE = { filename = "QFE.ogg", duration = 0.90 }, + QNH = { filename = "QNH.ogg", duration = 0.94 }, + Rain = { filename = "Rain.ogg", duration = 0.35 }, + Right = { filename = "Right.ogg", duration = 0.31 }, + RSBNChannel = { filename = "RSBNChannel.ogg", duration = 1.26 }, + RunwayLength = { filename = "RunwayLength.ogg", duration = 0.81 }, + Snow = { filename = "Snow.ogg", duration = 0.40 }, + SnowStorm = { filename = "SnowStorm.ogg", duration = 0.73 }, + StatuteMiles = { filename = "StatuteMiles.ogg", duration = 0.90 }, + SunriseAt = { filename = "SunriseAt.ogg", duration = 0.82 }, + SunsetAt = { filename = "SunsetAt.ogg", duration = 0.87 }, + TACANChannel = { filename = "TACANChannel.ogg", duration = 0.81 }, + Temperature = { filename = "Temperature.ogg", duration = 0.70 }, + Thousand = { filename = "Thousand.ogg", duration = 0.58 }, + ThunderStorm = { filename = "ThunderStorm.ogg", duration = 0.79 }, + TimeLocal = { filename = "TimeLocal.ogg", duration = 0.83 }, + TimeZulu = { filename = "TimeZulu.ogg", duration = 0.83 }, + TowerFrequency = { filename = "TowerFrequency.ogg", duration = 1.05 }, + Visibilty = { filename = "Visibility.ogg", duration = 1.16 }, + VORFrequency = { filename = "VORFrequency.ogg", duration = 1.28 }, + WeatherPhenomena = { filename = "WeatherPhenomena.ogg", duration = 1.09 }, + WindFrom = { filename = "WindFrom.ogg", duration = 0.63 }, + Zulu = { filename = "Zulu.ogg", duration = 0.51 }, } --- @@ -882,6 +894,66 @@ ATIS.Messages = { FARP = "Farp", DELIMITER = "Punto", -- decimal delimiter }, + -- French messages thanks to @Wojtech and Bing + FR = { + HOURS = "Heures", + TIME = "Temps", + NOCLOUDINFO = "Informations sur la couverture nuageuse non disponibles", + OVERCAST = "Ciel couvert", + BROKEN = "Nuages fragmentés", + SCATTERED = "Nuages épars", + FEWCLOUDS = "Nuages rares", + NOCLOUDS = "Clair", + AIRPORT = "Aéroport", + INFORMATION ="Information", + SUNRISEAT = "Levé du soleil à %s heure locale", + SUNSETAT = "Couché du soleil à %s heure locale", + WINDFROMMS = "Vent du %s pour %s mètres par seconde", + WINDFROMKNOTS = "Vent du %s pour %s noeuds", + GUSTING = "Rafale de vent", + VISIKM = "Visibilité %s kilomètres", + VISISM = "Visibilité %s Miles", + RAIN = "Pluie", + TSTORM = "Orage", + SNOW = "Neige", + SSTROM = "Tempête de neige", + FOG = "Brouillard", + DUST = "Poussière", + PHENOMENA = "Phénomène météorologique", + CLOUDBASEM = "Couverture nuageuse de %s à %s mètres", + CLOUDBASEFT = "Couverture nuageuse de %s à %s pieds", + TEMPERATURE = "Température", + DEWPOINT = "Point de rosée", + ALTIMETER = "Altimètre", + ACTIVERUN = "Décollages piste", + ACTIVELANDING = "Atterrissages piste", + LEFT = "Gauche", + RIGHT = "Droite", + RWYLENGTH = "Longueur de piste", + METERS = "Mètre", + FEET = "Pieds", + ELEVATION = "Hauteur", + TOWERFREQ = "Fréquences de la tour", + ILSFREQ = "Fréquences ILS", + OUTERNDB = "Fréquences Outer NDB", + INNERNDB = "Fréquences Inner NDB", + VORFREQ = "Fréquences VOR", + VORFREQTTS = "Fréquences V O R", + TACANCH = "Canal TACAN %d", + RSBNCH = "Canal RSBN", + PRMGCH = "Canal PRMG", + ADVISE = "Informez le contrôle que vous avez copié l'information", + STATUTE = "Statute Miles", + DEGREES = "Degré celcius", + FAHRENHEIT = "Degré Fahrenheit", + INCHHG = "Pouces de mercure", + MMHG = "Millimètres de mercure", + HECTO = "Hectopascals", + METERSPER = "Mètres par seconde", + TACAN = "TAKAN", + FARP = "FARPE", + DELIMITER = "Décimal", -- decimal delimiter + } } --- @@ -894,7 +966,7 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "1.0.0" +ATIS.version = "1.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1061,7 +1133,7 @@ end -- @return #ATIS self function ATIS:_InitLocalization() self:T(self.lid.."_InitLocalization") - self.gettext = TEXTANDSOUND:New("AWACS","en") -- Core.TextAndSound#TEXTANDSOUND + self.gettext = TEXTANDSOUND:New("ATIS","en") -- Core.TextAndSound#TEXTANDSOUND self.locale = "en" for locale,table in pairs(self.Messages) do local Locale = string.lower(tostring(locale)) @@ -1975,17 +2047,28 @@ function ATIS:onafterBroadcast( From, Event, To ) local hours = self.gettext:GetEntry("HOURS",self.locale) 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 %s", sunrise[1], sunrise[2], hours ) + --self:I(sunrise) + local SUNRISE = "no time" + local NorthPolar = true + if tostring(sunrise) ~= "N/S" and tostring(sunrise) ~= "N/R" then + sunrise = UTILS.Split( sunrise, ":" ) + SUNRISE = string.format( "%s%s", sunrise[1], sunrise[2] ) + if self.useSRS then + SUNRISE = string.format( "%s %s %s", sunrise[1], sunrise[2], hours ) + end + NorthPolar = false 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 %s", sunset[1], sunset[2], hours ) + --self:I(sunset) + local SUNSET = "no time" + if tostring(sunset) ~= "N/S" and tostring(sunset) ~= "N/R" then + sunset = UTILS.Split( sunset, ":" ) + SUNSET = string.format( "%s%s", sunset[1], sunset[2] ) + if self.useSRS then + SUNSET = string.format( "%s %s %s", sunset[1], sunset[2], hours ) + end + NorthPolar = false end --------------------------------- @@ -2012,34 +2095,32 @@ function ATIS:onafterBroadcast( From, Event, To ) --------------- -- Get mission weather info. Most of this is static. - local clouds, visibility, turbulence, fog, dust, static = self:GetMissionWeather() - - -- Check that fog is actually "thick" enough to reach the airport. If an airport is in the mountains, fog might not affect it as it is measured from sea level. - if fog and fog.thickness < height + 25 then - fog = nil - end - - -- Dust only up to 1500 ft = 457 m ASL. - if dust and height + 25 > UTILS.FeetToMeters( 1500 ) then - dust = nil - end + local clouds, visibility, turbulence, dustdens, static = self:GetMissionWeather() + + local dust=false + local fog=false ------------------ --- Visibility --- ------------------ - -- Get min visibility. - local visibilitymin = visibility - - if fog then - if fog.visibility < visibilitymin then - visibilitymin = fog.visibility + if dustdens then + + -- Dust only up to 1500 ft = 457 m ASL. + if UTILS.FeetToMeters( 1500 )> height+25 then + dust=true + visibility=math.min(visibility, dustdens) end - end - - if dust then - if dust < visibilitymin then - visibilitymin = dust + + else -- As of DCS 2.9.10.3948 (December 2024), fog and dust are mutually exclusive! + + -- Get current fog visibility and thickness + local fvis=world.weather.getFogVisibilityDistance() + local fheight=world.weather.getFogThickness() + + if fvis>0 and fheight>height+25 then + fog=true + visibility=math.min(visibility, fvis) end end @@ -2047,7 +2128,7 @@ function ATIS:onafterBroadcast( From, Event, To ) if self.metric then -- Visibility in km. - local reportedviz = UTILS.Round( visibilitymin / 1000 ) + local reportedviz = UTILS.Round( visibility / 1000 ) -- max reported visibility 9999 m if reportedviz > 10 then reportedviz = 10 @@ -2055,7 +2136,7 @@ function ATIS:onafterBroadcast( From, Event, To ) VISIBILITY = string.format( "%d", reportedviz ) else -- max reported visibility 10 NM - local reportedviz = UTILS.Round( UTILS.MetersToSM( visibilitymin ) ) + local reportedviz = UTILS.Round( UTILS.MetersToSM( visibility ) ) if reportedviz > 10 then reportedviz = 10 end @@ -2069,7 +2150,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local cloudbase = clouds.base local cloudceil = clouds.base + clouds.thickness local clouddens = clouds.density - + -- Cloud preset (DCS 2.7) local cloudspreset = clouds.preset or "Nothing" @@ -2100,6 +2181,39 @@ function ATIS:onafterBroadcast( From, Event, To ) else precepitation = 3 -- snow end + elseif cloudspreset:find( "RainyPreset4" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset5" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + elseif cloudspreset:find( "RainyPreset6" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end + -- NEWRAINPRESET4 + elseif cloudspreset:find( "NEWRAINPRESET4" ) then + -- Overcast + Rain + clouddens = 5 + if temperature > 5 then + precepitation = 1 -- rain + else + precepitation = 3 -- snow + end elseif cloudspreset:find( "RainyPreset" ) then -- Overcast + Rain clouddens = 9 @@ -2294,7 +2408,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunrise = self.gettext:GetEntry("SUNRISEAT",self.locale) --subtitle = string.format( "Sunrise at %s local time", SUNRISE ) subtitle = string.format( sunrise, SUNRISE ) - if not self.useSRS then + if not self.useSRS and NorthPolar == false then self:Transmission( self.Sound.SunriseAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNRISE, nil, 0.2 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) @@ -2305,7 +2419,7 @@ function ATIS:onafterBroadcast( From, Event, To ) local sunset = self.gettext:GetEntry("SUNSETAT",self.locale) --subtitle = string.format( "Sunset at %s local time", SUNSET ) subtitle = string.format( sunset, SUNSET ) - if not self.useSRS then + if not self.useSRS and NorthPolar == false then self:Transmission( self.Sound.SunsetAt, 0.5, subtitle ) self.radioqueue:Number2Transmission( SUNSET, nil, 0.5 ) self:Transmission( self.Sound.TimeLocal, 0.2 ) @@ -2631,7 +2745,7 @@ function ATIS:onafterBroadcast( From, Event, To ) if not self.ATISforFARPs then -- Active runway. local subtitle = "" - if runwayLanding then + if runwayLanding and runwayLanding ~= runwayTakeoff then local actrun = self.gettext:GetEntry("ACTIVELANDING",self.locale) @@ -3249,28 +3363,13 @@ function ATIS:GetMissionWeather() dust = weather.dust_density end - -- Fog - --[[ - ["enable_fog"] = false, - ["fog"] = - { - ["thickness"] = 0, - ["visibility"] = 25, - }, -- end of ["fog"] - ]] - local fog = nil - if weather.enable_fog == true then - fog = weather.fog - end - self:T( "FF weather:" ) self:T( { clouds = clouds } ) self:T( { visibility = visibility } ) self:T( { turbulence = turbulence } ) - self:T( { fog = fog } ) self:T( { dust = dust } ) self:T( { static = static } ) - return clouds, visibility, turbulence, fog, dust, static + return clouds, visibility, turbulence, dust, static end --- Get thousands of a number. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 1d2188ba4..f181b02ea 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -187,7 +187,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.9.5" +AIRWING.version="0.9.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -1041,6 +1041,9 @@ function AIRWING:onafterStatus(From, Event, To) -- Check Recon missions. self:CheckRECON() + + -- Display tactival overview. + self:_TacticalOverview() ---------------- -- Transport --- @@ -1362,16 +1365,20 @@ function AIRWING:CheckRescuhelo() local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) - local name=self.airbase:GetName() - - local carrier=UNIT:FindByName(name) - - for i=1,self.nflightsRescueHelo-N do - - local mission=AUFTRAG:NewRESCUEHELO(carrier) - - self:AddMission(mission) - + if self.airbase then + + local name=self.airbase:GetName() + + local carrier=UNIT:FindByName(name) + + for i=1,self.nflightsRescueHelo-N do + + local mission=AUFTRAG:NewRESCUEHELO(carrier) + + self:AddMission(mission) + + end + end return self diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 244e252ed..0eadc412c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3615,7 +3615,7 @@ function AIRBOSS:onafterStart( From, Event, To ) -- Handle events. self:HandleEvent( EVENTS.Birth ) - self:HandleEvent( EVENTS.Land ) + self:HandleEvent( EVENTS.RunwayTouch ) self:HandleEvent( EVENTS.EngineShutdown ) self:HandleEvent( EVENTS.Takeoff ) self:HandleEvent( EVENTS.Crash ) @@ -3623,6 +3623,7 @@ function AIRBOSS:onafterStart( From, Event, To ) self:HandleEvent( EVENTS.PlayerLeaveUnit, self._PlayerLeft ) self:HandleEvent( EVENTS.MissionEnd ) self:HandleEvent( EVENTS.RemoveUnit ) + self:HandleEvent( EVENTS.UnitLost, self.OnEventRemoveUnit ) -- self.StatusScheduler=SCHEDULER:New(self) -- self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) @@ -4380,7 +4381,7 @@ function AIRBOSS:onafterStop( From, Event, To ) -- Unhandle events. self:UnHandleEvent( EVENTS.Birth ) - self:UnHandleEvent( EVENTS.Land ) + self:UnHandleEvent( EVENTS.RunwayTouch ) self:UnHandleEvent( EVENTS.EngineShutdown ) self:UnHandleEvent( EVENTS.Takeoff ) self:UnHandleEvent( EVENTS.Crash ) @@ -8290,7 +8291,7 @@ end --- Airboss event handler for event land. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData -function AIRBOSS:OnEventLand( EventData ) +function AIRBOSS:OnEventRunwayTouch( EventData ) self:F3( { eventland = EventData } ) -- Nil checks. @@ -14679,7 +14680,7 @@ function AIRBOSS:_GetPlayerUnitAndName( _unitName ) -- Get DCS unit from its name. local DCSunit = Unit.getByName( _unitName ) - if DCSunit then + if DCSunit and DCSunit.getPlayerName then -- Get player name if any. local playername = DCSunit:getPlayerName() @@ -15650,7 +15651,7 @@ function AIRBOSS:_Number2Sound( playerData, sender, number, delay ) end -- Split string into characters. - local numbers = _split( number ) + local numbers = _split( tostring(number) ) local wait = 0 for i = 1, #numbers do @@ -15718,7 +15719,7 @@ function AIRBOSS:_Number2Radio( radio, number, delay, interval, pilotcall ) end -- Split string into characters. - local numbers = _split( number ) + local numbers = _split( tostring(number) ) local wait = 0 for i = 1, #numbers do @@ -18086,7 +18087,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) self:_GetZoneArcIn( case ):FlareZone( FLARECOLOR.White, 45 ) text = text .. "\n* arc turn in with WHITE flares" self:_GetZoneArcOut( case ):FlareZone( FLARECOLOR.White, 45 ) - text = text .. "\n* arc trun out with WHITE flares" + text = text .. "\n* arc turn out with WHITE flares" end end @@ -18138,7 +18139,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare ) self:_GetZoneArcIn( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) text = text .. "\n* arc turn in with BLUE smoke" self:_GetZoneArcOut( case ):SmokeZone( SMOKECOLOR.Blue, 45 ) - text = text .. "\n* arc trun out with BLUE smoke" + text = text .. "\n* arc turn out with BLUE smoke" end end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 62f3dfec4..e5c822ed3 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -68,7 +68,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="1.0.1" +ARMYGROUP.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -403,6 +403,7 @@ function ARMYGROUP:New(group) self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) + self:HandleEvent(EVENTS.UnitLost, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. @@ -2048,114 +2049,70 @@ 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`. +-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous. -- @return #ARMYGROUP self function ARMYGROUP:_InitGroup(Template, Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0) else - -- First check if group was already initialized. - if self.groupinitialized then - self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") - return - end - - -- Get template of group. - local template=Template or self:_GetTemplate() - - -- Ground are always AI. - self.isAI=true - - -- Is (template) group late activated. - self.isLateActivated=template.lateActivation - - -- Ground groups cannot be uncontrolled. - self.isUncontrolled=false - - -- Max speed in km/h. - self.speedMax=self.group:GetSpeedMax() - - -- Is group mobile? - if self.speedMax and self.speedMax>3.6 then - self.isMobile=true - else - self.isMobile=false - self.speedMax = 0 - end - - -- Cruise speed in km/h - self.speedCruise=self.speedMax*0.7 - - -- Group ammo. - self.ammo=self:GetAmmoTot() - - -- Radio parameters from template. - self.radio.On=false -- Radio is always OFF for ground. - self.radio.Freq=133 - self.radio.Modu=radio.modulation.AM - - -- Set default radio. - self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - - -- Get current formation from first waypoint. - self.option.Formation=template.route.points[1].action - - -- Set default formation to "on road". - self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad - + -- First check if group was already initialized. if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end - - self:T(self.lid.."FF Initializing Group") - + -- Get template of group. local template=Template or self:_GetTemplate() - + -- Ground are always AI. self.isAI=true - + -- Is (template) group late activated. self.isLateActivated=template.lateActivation - + -- Ground groups cannot be uncontrolled. self.isUncontrolled=false - + -- Max speed in km/h. self.speedMax=self.group:GetSpeedMax() - + -- Is group mobile? - if self.speedMax>3.6 then + if self.speedMax and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false + self.speedMax = 0 end - + -- Cruise speed in km/h self.speedCruise=self.speedMax*0.7 - + -- Group ammo. self.ammo=self:GetAmmoTot() - + -- Radio parameters from template. self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 self.radio.Modu=radio.modulation.AM - + -- Set default radio. self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - + -- Get current formation from first waypoint. self.option.Formation=template.route.points[1].action - + -- Set default formation to "on road". self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad -- Default TACAN off. - self:SetDefaultTACAN(nil, nil, nil, nil, true) - self.tacan=UTILS.DeepCopy(self.tacanDefault) + if not self.tacanDefault then + self:SetDefaultTACAN(nil, nil, nil, nil, true) + end + if not self.tacan then + self.tacan=UTILS.DeepCopy(self.tacanDefault) + end -- Units of the group. local units=self.group:GetUnits() @@ -2176,7 +2133,6 @@ function ARMYGROUP:_InitGroup(Template, Delay) self:_AddElementByName(unitname) end - -- Init done. self.groupinitialized=true end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 1ae498766..98d42aa37 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -93,6 +93,7 @@ -- @field #number engageWeaponType Weapon type used. -- @field #number engageWeaponExpend How many weapons are used. -- @field #boolean engageAsGroup Group attack. +-- @field #number engageLength Length of engage (carpet or strafing) in meters. -- @field #number engageMaxDistance Max engage distance. -- @field #number refuelSystem Refuel type (boom or probe) for TANKER missions. -- @@ -161,6 +162,7 @@ -- @field #number missionRange Mission range in meters. Used by LEGION classes (AIRWING, BRIGADE, ...). -- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. -- @field Core.Point#COORDINATE missionEgressCoord Mission egress waypoint coordinate. +-- @field Core.Point#COORDINATE missionIngressCoord Mission Ingress waypoint coordinate. -- @field #number missionWaypointRadius Random radius in meters. -- @field #boolean legionReturn If `true`, assets return to their legion (default). If `false`, they will stay alive. -- @@ -239,6 +241,10 @@ -- ## Bombing Carpet -- -- A carpet bombing mission can be created with the @{#AUFTRAG.NewBOMBCARPET}() function. +-- +-- ## Strafing +-- +-- A strafing mission can be created with the @{#AUFTRAG.NewSTRAFING}() function. -- -- ## CAP -- @@ -445,6 +451,7 @@ _AUFTRAGSNR=0 -- @field #string CAPTUREZONE Capture zone mission. -- @field #string NOTHING Nothing. -- @field #string PATROLRACETRACK Patrol Racetrack. +-- @field #string STRAFING Strafing run. AUFTRAG.Type={ ANTISHIP="Anti Ship", AWACS="AWACS", @@ -491,6 +498,7 @@ AUFTRAG.Type={ CAPTUREZONE="Capture Zone", NOTHING="Nothing", PATROLRACETRACK="Patrol Racetrack", + STRAFING="Strafing", } --- Special task description. @@ -1062,8 +1070,10 @@ end -- @param #number Time Time in seconds to stay. Default 300 seconds. -- @param #number Speed Speed in knots to fly to the target coordinate. Default 150kn. -- @param #number MissionAlt Altitude to fly towards the mission in feet AGL. Default 1000ft. +-- @param #boolean CombatLanding (Optional) If true, set the Combat Landing option. +-- @param #number DirectionAfterLand (Optional) Heading after landing in degrees. -- @return #AUFTRAG self -function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time, Speed, MissionAlt) +function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time, Speed, MissionAlt, CombatLanding, DirectionAfterLand) local mission=AUFTRAG:New(AUFTRAG.Type.LANDATCOORDINATE) @@ -1071,6 +1081,8 @@ function AUFTRAG:NewLANDATCOORDINATE(Coordinate, OuterRadius, InnerRadius, Time, mission.stayTime = Time or 300 mission.stayAt = Coordinate + mission.combatLand = CombatLanding + mission.directionAfter = DirectionAfterLand self:SetMissionSpeed(Speed or 150) self:SetMissionAltitude(MissionAlt or 1000) @@ -1707,15 +1719,16 @@ end -- @param #AUFTRAG self -- @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. +-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. -- @return #AUFTRAG self -function AUFTRAG:NewSTRIKE(Target, Altitude) +function AUFTRAG:NewSTRIKE(Target, Altitude, EngageWeaponType) local mission=AUFTRAG:New(AUFTRAG.Type.STRIKE) mission:_TargetFromObject(Target) -- DCS Task options: - mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) @@ -1734,18 +1747,20 @@ function AUFTRAG:NewSTRIKE(Target, Altitude) end --- **[AIR]** Create a BOMBING mission. Flight will drop bombs a specified coordinate. +-- See [DCS task bombing](https://wiki.hoggitworld.com/view/DCS_task_bombing). -- @param #AUFTRAG self -- @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. +-- @param #number EngageWeaponType Which weapon to use. Defaults to auto, ie ENUMS.WeaponFlag.Auto. See ENUMS.WeaponFlag for options. -- @return #AUFTRAG self -function AUFTRAG:NewBOMBING(Target, Altitude) +function AUFTRAG:NewBOMBING(Target, Altitude, EngageWeaponType) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) mission:_TargetFromObject(Target) -- DCS task options: - mission.engageWeaponType=ENUMS.WeaponFlag.Auto + mission.engageWeaponType=EngageWeaponType or ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) @@ -1767,9 +1782,47 @@ function AUFTRAG:NewBOMBING(Target, Altitude) return mission end +--- **[AIR]** Create a STRAFING mission. Assigns a point on the ground for which the AI will do a strafing run with guns or rockets. +-- See [DCS task strafing](https://wiki.hoggitworld.com/view/DCS_task_strafing). +-- @param #AUFTRAG self +-- @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 1000 ft. +-- @param #number Length The total length of the strafing target in meters. Default `nil`. +-- @return #AUFTRAG self +function AUFTRAG:NewSTRAFING(Target, Altitude, Length) + + local mission=AUFTRAG:New(AUFTRAG.Type.STRAFING) + + mission:_TargetFromObject(Target) + + -- DCS task options: + mission.engageWeaponType=805337088 -- Corresponds to guns/cannons (805306368) + any rocket (30720). This is the default when selecting this task in the ME. + mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 1000) + mission.engageLength=Length + + -- Mission options: + mission.missionTask=ENUMS.MissionTask.GROUNDATTACK + mission.missionAltitude=mission.engageAltitude*0.8 + mission.missionFraction=0.5 + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionROT=ENUMS.ROT.NoReaction -- No reaction is better. + + -- Evaluate result after 5 min. We might need time until the bombs have dropped and targets have been detroyed. + mission.dTevaluate=5*60 + + mission.categories={AUFTRAG.Category.AIRCRAFT} + + -- Get DCS task. + mission.DCStask=mission:GetDCSMissionTask() + + return mission +end + + --- **[AIR]** Create a BOMBRUNWAY mission. -- @param #AUFTRAG self --- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these to not have a runway. +-- @param Wrapper.Airbase#AIRBASE Airdrome The airbase to bomb. This must be an airdrome (not a FARP or ship) as these do not have a runway. -- @param #number Altitude Engage altitude in feet. Default 25000 ft. -- @return #AUFTRAG self function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) @@ -1821,7 +1874,7 @@ function AUFTRAG:NewBOMBCARPET(Target, Altitude, CarpetLength) mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) - mission.engageCarpetLength=CarpetLength or 500 + mission.engageLength=CarpetLength or 500 mission.engageAsGroup=false -- Looks like this must be false or the task is not executed. It is not available in the ME anyway but in the task of the mission file. mission.engageDirection=nil -- This is also not available in the ME. @@ -2105,7 +2158,11 @@ end ]] ---- **[GROUND, NAVAL]** Create an ARTY mission. +--- **[GROUND, NAVAL]** Create an ARTY mission ("Fire at point" task). +-- +-- If the group has more than one weapon type supporting the "Fire at point" task, the employed weapon type can be set via the `AUFTRAG:SetWeaponType()` function. +-- +-- **Note** that it is recommended to set the weapon range via the `OPSGROUP:AddWeaponRange()` function as this cannot be retrieved from the DCS API. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Target Center of the firing solution. -- @param #number Nshots Number of shots to be fired. Default `#nil`. @@ -2128,6 +2185,7 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius, Altitude) mission.optionAlarm=0 mission.missionFraction=0.0 + mission.missionWaypointRadius=0.0 -- Evaluate after 8 min. mission.dTevaluate=8*60 @@ -2252,7 +2310,7 @@ function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation) params.formation=Formation or "Off Road" params.zone=mission:GetObjective() params.altitude=mission.missionAltitude - params.speed=mission.missionSpeed + params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil mission.DCStask.params=params @@ -2302,7 +2360,7 @@ function AUFTRAG:NewGROUNDATTACK(Target, Speed, Formation) mission.DCStask=mission:GetDCSMissionTask() - mission.DCStask.params.speed=Speed + mission.DCStask.params.speed=mission.missionSpeed and UTILS.KmphToMps(mission.missionSpeed) or nil mission.DCStask.params.formation=Formation or ENUMS.Formation.Vehicle.Vee return mission @@ -2611,6 +2669,8 @@ function AUFTRAG:NewFromTarget(Target, MissionType) mission=self:NewBOMBING(Target, Altitude) elseif MissionType==AUFTRAG.Type.BOMBRUNWAY then mission=self:NewBOMBRUNWAY(Target, Altitude) + elseif MissionType==AUFTRAG.Type.STRAFING then + mission=self:NewSTRAFING(Target, Altitude) elseif MissionType==AUFTRAG.Type.CAS then mission=self:NewCAS(ZONE_RADIUS:New(Target:GetName(),Target:GetVec2(),1000), Altitude, Speed, Target:GetAverageCoordinate(), Heading, Leg, TargetTypes) elseif MissionType==AUFTRAG.Type.CASENHANCED then @@ -3429,7 +3489,7 @@ end --- Set Rules of Engagement (ROE) for this mission. -- @param #AUFTRAG self --- @param #string roe Mission ROE. +-- @param #number roe Mission ROE, e.g. `ENUMS.ROE.ReturnFire` (whiche equals 3) -- @return #AUFTRAG self function AUFTRAG:SetROE(roe) @@ -3441,7 +3501,7 @@ end --- Set Reaction on Threat (ROT) for this mission. -- @param #AUFTRAG self --- @param #string rot Mission ROT. +-- @param #number rot Mission ROT, e.g. `ENUMS.ROT.NoReaction` (whiche equals 0) -- @return #AUFTRAG self function AUFTRAG:SetROT(rot) @@ -4605,6 +4665,16 @@ function AUFTRAG:SetGroupWaypointCoordinate(opsgroup, coordinate) return self end +--- [Air] Set mission (ingress) waypoint coordinate for FLIGHT group. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE coordinate Waypoint Coordinate. +-- @return #AUFTRAG self +function AUFTRAG:SetIngressCoordinate(coordinate) + self.missionIngressCoord = coordinate + self.missionIngressCoordAlt = UTILS.MetersToFeet(coordinate.y) or 10000 + return self +end + --- Get mission (ingress) waypoint coordinate of OPS group -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group. @@ -5660,7 +5730,7 @@ function AUFTRAG:GetMissionTypesText(MissionTypes) return text end ---- Set the mission waypoint coordinate where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. +--- [NON-AIR] Set the mission waypoint coordinate from where the mission is executed. Note that altitude is set via `:SetMissionAltitude`. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Coordinate where the mission is executed. -- @return #AUFTRAG self @@ -5688,8 +5758,9 @@ end -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Egrees coordinate. -- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed. -- @return #AUFTRAG self -function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) +function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude, Speed) -- Obviously a zone was passed. We get the coordinate. if Coordinate:IsInstanceOf("ZONE_BASE") then @@ -5700,7 +5771,64 @@ function AUFTRAG:SetMissionEgressCoord(Coordinate, Altitude) if Altitude then self.missionEgressCoord.y=UTILS.FeetToMeters(Altitude) + self.missionEgressCoordAlt = UTILS.FeetToMeters(Altitude) end + + self.missionEgressCoordSpeed=Speed and Speed or nil + + return self +end + +--- [Air] Set the mission ingress coordinate. This is the coordinate where the assigned group will fly before the actual mission coordinate. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Ingrees coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint. Defaults to mission speed. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionIngressCoord(Coordinate, Altitude, Speed) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionIngressCoord=Coordinate + + if Altitude then + self.missionIngressCoord.y=UTILS.FeetToMeters(Altitude) + self.missionIngressCoordAlt = UTILS.FeetToMeters(Altitude or 10000) + end + + self.missionIngressCoordSpeed=Speed and Speed or nil + + return self +end + +--- [Air] Set the mission holding coordinate. This is the coordinate where the assigned group will fly before the actual mission execution starts. Do not forget to add a push condition, too! +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE Coordinate Holding coordinate. +-- @param #number Altitude (Optional) Altitude in feet. Default is y component of coordinate. +-- @param #number Speed (Optional) Speed in knots to reach this waypoint and hold there. Defaults to mission speed. +-- @param #number Duration (Optional) Duration in seconds on how long to hold, defaults to 15 minutes. Mission continues if either a push condition is met or the time is up. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionHoldingCoord(Coordinate, Altitude, Speed, Duration) + + -- Obviously a zone was passed. We get the coordinate. + if Coordinate:IsInstanceOf("ZONE_BASE") then + Coordinate=Coordinate:GetCoordinate() + end + + self.missionHoldingCoord=Coordinate + self.missionHoldingDuration=Duration or 900 + + if Altitude then + self.missionHoldingCoord.y=UTILS.FeetToMeters(Altitude) + self.missionHoldingCoordAlt = UTILS.FeetToMeters(Altitude or 10000) + end + + self.missionHoldingCoordSpeed=Speed and Speed or nil + + return self end --- Get the mission egress coordinate if this was defined. @@ -5710,6 +5838,20 @@ function AUFTRAG:GetMissionEgressCoord() return self.missionEgressCoord end +--- Get the mission ingress coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionIngressCoord() + return self.missionIngressCoord +end + +--- Get the mission holding coordinate if this was defined. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE Coordinate Coordinate or nil. +function AUFTRAG:GetMissionHoldingCoord() + return self.missionHoldingCoord +end + --- Get coordinate which was set as mission waypoint coordinate. -- @param #AUFTRAG self -- @return Core.Point#COORDINATE Coordinate where the mission is executed or `#nil`. @@ -5744,10 +5886,27 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes) end return coord end + + local coord=group:GetCoordinate() + + -- Check if an ingress or holding coord has been explicitly set. + if self.missionHoldingCoord then + coord=self.missionHoldingCoord + if self.missionHoldingCoorddAlt then + coord:SetAltitude(self.missionHoldingCoordAlt, true) + end + end + + if self.missionIngressCoord then + coord=self.missionIngressCoord + if self.missionIngressCoordAlt then + coord:SetAltitude(self.missionIngressCoordAlt, true) + end + end -- Create waypoint coordinate half way between us and the target. local waypointcoord=COORDINATE:New(0,0,0) - local coord=group:GetCoordinate() + if coord then waypointcoord=coord:GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) else @@ -5952,6 +6111,16 @@ function AUFTRAG:GetDCSMissionTask() local DCStask=CONTROLLABLE.TaskBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, Divebomb) table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.STRAFING then + + ---------------------- + -- STRAFING Mission -- + ---------------------- + + local DCStask=CONTROLLABLE.TaskStrafing(nil,self:GetTargetVec2(), self.engageQuantity, self.engageLength,self.engageWeaponType,self.engageWeaponExpend,self.engageDirection,self.engageAsGroup) + + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.BOMBRUNWAY then @@ -5969,7 +6138,7 @@ function AUFTRAG:GetDCSMissionTask() -- BOMBCARPET Mission -- ------------------------ - local DCStask=CONTROLLABLE.TaskCarpetBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.engageCarpetLength) + local DCStask=CONTROLLABLE.TaskCarpetBombing(nil, self:GetTargetVec2(), self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType, self.engageLength) table.insert(DCStasks, DCStask) @@ -6037,7 +6206,7 @@ function AUFTRAG:GetDCSMissionTask() local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude - param.speed=self.missionSpeed + param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil DCStask.params=param @@ -6117,7 +6286,7 @@ function AUFTRAG:GetDCSMissionTask() local param={} param.target=self.engageTarget param.altitude=self.missionAltitude - param.speed=self.missionSpeed + param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil param.lastindex=nil DCStask.params=param @@ -6290,7 +6459,7 @@ function AUFTRAG:GetDCSMissionTask() local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude - param.speed=self.missionSpeed + param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil DCStask.params=param @@ -6326,7 +6495,7 @@ function AUFTRAG:GetDCSMissionTask() local param={} param.zone=self:GetObjective() param.altitude=self.missionAltitude - param.speed=self.missionSpeed + param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil DCStask.params=param @@ -6346,7 +6515,7 @@ function AUFTRAG:GetDCSMissionTask() local param={} param.target=self:GetTargetData() param.action="Wedge" - param.speed=self.missionSpeed + param.speed=self.missionSpeed and UTILS.KmphToMps(self.missionSpeed) or nil DCStask.params=param @@ -6492,8 +6661,7 @@ function AUFTRAG:GetDCSMissionTask() local DCStask={} local Vec2 = self.stayAt:GetVec2() - local DCStask = CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime) - + local DCStask = CONTROLLABLE.TaskLandAtVec2(nil,Vec2,self.stayTime, self.combatLand, self.directionAfter) table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.ONGUARD or self.type==AUFTRAG.Type.ARMOREDGUARD then diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index ac62198d6..09aa3324b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -17,7 +17,7 @@ -- === -- -- ### Author: **applevangelist** --- @date Last Update Jan 2024 +-- @date Last Update Jan 2025 -- @module Ops.AWACS -- @image OPS_AWACS.jpg @@ -122,6 +122,7 @@ do -- @field #number TacticalModulation -- @field #number TacticalInterval -- @field Core.Set#SET_GROUP DetectionSet +-- @field #number MaxMissionRange -- @extends Core.Fsm#FSM @@ -183,7 +184,7 @@ do -- -- Add Escorts Squad (recommended, optional) -- --- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") +-- local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North") -- taking a template with 2 planes here, will result in a group of 2 escorts which can fly in formation escorting the AWACS. -- Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT}) -- Squad_Two:SetFuelLowRefuel(true) -- Squad_Two:SetFuelLowThreshold(0.3) @@ -231,8 +232,8 @@ do -- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock". -- -- The CAP station zone is called "Fremont". We will be on 255 AM. -- local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("Rock"),"Fremont",255,radio.modulation.AM ) --- -- set two escorts --- testawacs:SetEscort(2) +-- -- set one escort group; this example has two units in the template group, so they can fly a nice formation. +-- testawacs:SetEscort(1,ENUMS.Formation.FixedWing.FingerFour.Group,{x=-500,y=50,z=500},45) -- -- Callsign will be "Focus". We'll be a Angels 30, doing 300 knots, orbit leg to 88deg with a length of 25nm. -- testawacs:SetAwacsDetails(CALLSIGN.AWACS.Focus,1,30,300,88,25) -- -- Set up SRS on port 5010 - change the below to your path and port @@ -508,7 +509,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.64", -- #string + version = "0.2.71", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -605,6 +606,7 @@ AWACS = { TacticalModulation = radio.modulation.AM, TacticalInterval = 120, DetectionSet = nil, + MaxMissionRange = 125, } --- @@ -935,7 +937,7 @@ AWACS.TaskStatus = { --@field #boolean FromAI ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO-List 0.2.53 +-- TODO-List 0.2.54 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- -- DONE - WIP - Player tasking, VID @@ -1572,6 +1574,15 @@ function AWACS:SetLocale(Locale) return self end +--- [User] Set the max mission range flights can be away from their home base. +-- @param #AWACS self +-- @param #number NM Distance in nautical miles +-- @return #AWACS self +function AWACS:SetMaxMissionRange(NM) + self.MaxMissionRange = NM or 125 + return self +end + --- [User] Add additional frequency and modulation for AWACS SRS output. -- @param #AWACS self -- @param #number Frequency The frequency to add, e.g. 132.5 @@ -1762,7 +1773,7 @@ function AWACS:_EventHandler(EventData) end end - if Event.id == EVENTS.PlayerLeaveUnit then --player left unit + if Event.id == EVENTS.PlayerLeaveUnit and Event.IniGroupName then --player left unit -- check known player? self:T("Player group left unit: " .. Event.IniGroupName) self:T("Player name left: " .. Event.IniPlayerName) @@ -2158,9 +2169,12 @@ end --- [User] Set AWACS Escorts Template -- @param #AWACS self --- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts. +-- @param #number EscortNumber Number of fighther plane GROUPs to accompany this AWACS. 0 or nil means no escorts. If you want >1 plane in an escort group, you can either set the respective squadron grouping to the desired number, or use a template for escorts with >1 unit. +-- @param #number Formation Formation the escort should take (if more than one plane), e.g. `ENUMS.Formation.FixedWing.FingerFour.Group`. Formation is used on GROUP level, multiple groups of one unit will NOT conform to this formation. +-- @param #table OffsetVector Offset the escorts should fly behind the AWACS, given as table, distance in meters, e.g. `{x=-500,y=0,z=500}` - 500m behind (negative value) and to the right (negative for left), no vertical separation (positive over, negative under the AWACS flight). For multiple groups, the vectors will be slightly changed to avoid collisions. +-- @param #number EscortEngageMaxDistance Escorts engage air targets max this NM away, defaults to 45NM. -- @return #AWACS self -function AWACS:SetEscort(EscortNumber) +function AWACS:SetEscort(EscortNumber,Formation,OffsetVector,EscortEngageMaxDistance) self:T(self.lid.."SetEscort") if EscortNumber and EscortNumber > 0 then self.HasEscorts = true @@ -2169,6 +2183,9 @@ function AWACS:SetEscort(EscortNumber) self.HasEscorts = false self.EscortNumber = 0 end + self.EscortFormation = Formation + self.OffsetVec = OffsetVector or {x=500,y=100,z=500} + self.EscortEngageMaxDistance = EscortEngageMaxDistance or 45 return self end @@ -2223,11 +2240,26 @@ function AWACS:_StartEscorts(Shiftchange) local group = AwacsFG:GetGroup() local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds + local OffsetX = 500 + local OffsetY = 500 + local OffsetZ = 500 + if self.OffsetVec then + OffsetX = self.OffsetVec.x or 500 + OffsetY = self.OffsetVec.y or 500 + OffsetZ = self.OffsetVec.z or 500 + end + for i=1,self.EscortNumber do - -- every - local escort = AUFTRAG:NewESCORT(group, {x= -100*((i + (i%2))/2), y=0, z=(100 + 100*((i + (i%2))/2))*(-1)^i},45,{"Air"}) - escort:SetRequiredAssets(1) + -- every + local escort = AUFTRAG:NewESCORT(group, {x= OffsetX*((i + (i%2))/2), y=OffsetY*((i + (i%2))/2), z=(OffsetZ + OffsetZ*((i + (i%2))/2))*(-1)^i},self.EscortEngageMaxDistance,{"Air"}) + --local escort = AUFTRAG:NewESCORT(group,self.OffsetVec,self.EscortEngageMaxDistance,{"Air"}) + --escort:SetRequiredAssets(self.EscortNumber) escort:SetTime(nil,timeonstation) + if self.Escortformation then + escort:SetFormation(self.Escortformation) + end + escort:SetMissionRange(self.MaxMissionRange) + self.AirWing:AddMission(escort) self.CatchAllMissions[#self.CatchAllMissions+1] = escort @@ -2434,7 +2466,7 @@ function AWACS:_GetCallSign(Group,GID, IsPlayer) local callsign = "Ghost 1" if Group and Group:IsAlive() then - callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations) + callsign = Group:GetCustomCallSign(self.callsignshort,self.keepnumber,self.callsignTranslations,self.callsignCustomFunc,self.callsignCustomArgs) end return callsign end @@ -2443,10 +2475,12 @@ end -- @param #AWACS self -- @param #boolean ShortCallsign If true, only call out the major flight number -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name as-is, no amendments or numbers. --- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized +-- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized. -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #AWACS self -function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.callsignshort = false else @@ -2454,6 +2488,8 @@ function AWACS:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) end self.keepnumber = Keepnumber or false self.callsignTranslations = CallsignTranslations + self.callsignCustomFunc = CallsignCustomFunc + self.callsignCustomArgs = arg or {} return self end @@ -3626,7 +3662,7 @@ function AWACS:_CheckIn(Group) managedgroup.LastTasking = timer.getTime() GID = managedgroup.GID - self.ManagedGrps[self.ManagedGrpID]=managedgroup + self.ManagedGrps[self.ManagedGrpID]=managedgroup local alphacheckbulls = self:_ToStringBULLS(Group:GetCoordinate()) local alphacheckbullstts = self:_ToStringBULLS(Group:GetCoordinate(),false,true) @@ -3893,6 +3929,12 @@ function AWACS:_SetClientMenus() checkin = checkin, } self.clientmenus:Push(menus,cgrpname) + -- catch errors - when this entry is built we should NOT have a managed entry + local GID,hasentry = self:_GetManagedGrpID(cgrp) + if hasentry then + -- this user is checked in but has the check in entry ... not good. + self:_CheckOut(cgrp,GID,true) + end end end else @@ -5585,6 +5627,12 @@ function AWACS:_ThreatRangeCall(GID,Contact) local grptxt = self.gettext:GetEntry("GROUP",self.locale) local thrt = self.gettext:GetEntry("THREAT",self.locale) local text = string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt, thrt, BRATExt) + -- DONE MS TTS - fix spelling out B-R-A in this case + if string.find(text,"BRAA",1,true) then + text = string.gsub(text,"BRAA","brah") + elseif string.find(text,"BRA",1,true) then + text = string.gsub(text,"BRA","brah") + end if IsSub == false then self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) end @@ -5733,7 +5781,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) local intercept = AUFTRAG:NewINTERCEPT(Target.Target) intercept:SetWeaponExpend(AI.Task.WeaponExpend.ALL) intercept:SetWeaponType(ENUMS.WeaponFlag.Auto) - + intercept:SetMissionRange(self.MaxMissionRange) -- TODO -- now this is going to be interesting... -- Check if the target left the "hot" area or is dead already @@ -5781,6 +5829,7 @@ function AWACS:_AssignPilotToTarget(Pilots,Targets) AnchorSpeed = UTILS.KnotsToAltKIAS(AnchorSpeed,Angels) local Anchor = self.AnchorStacks:ReadByPointer(Pilot.AnchorStackNo) -- #AWACS.AnchorData local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels,AnchorSpeed,Anchor.StationZoneCoordinate,0,15,{}) + capauftrag:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) Pilot.FlightGroup:AddMission(capauftrag) @@ -5898,6 +5947,8 @@ function AWACS:onafterStart(From, Event, To) -- set up the AWACS and let it orbit local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg) + mission:SetMissionRange(self.MaxMissionRange) + mission:SetRequiredAttribute({ GROUP.Attribute.AIR_AWACS }) -- prefered plane type, thanks to Heart8reaker local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) self.CatchAllMissions[#self.CatchAllMissions+1] = mission @@ -6040,6 +6091,7 @@ function AWACS:_CheckAwacsStatus() end end end + -------------------------------- -- AWACS -------------------------------- @@ -6188,12 +6240,13 @@ function AWACS:_CheckAwacsStatus() report:Add("====================") + local RESMission -- Check for replacement mission - if any if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG - ESmission = self.EscortMissionReplacement[i] - local esstatus = ESmission:GetState() - local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp) - local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds + RESMission = self.EscortMissionReplacement[i] + local esstatus = RESMission:GetState() + local RESMissiontime = (timer.getTime() - self.EscortsTimeStamp) + local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - RESMissiontime),0) -- seconds ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0) @@ -6201,7 +6254,7 @@ function AWACS:_CheckAwacsStatus() report:Add(string.format("Auftrag Status: %s",esstatus)) report:Add(string.format("TOS Left: %d min",ESTOSLeft)) - local OpsGroups = ESmission:GetOpsGroups() + local OpsGroups = RESMission:GetOpsGroups() local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP if OpsGroup then local OpsName = OpsGroup:GetName() or "Unknown" @@ -6213,13 +6266,13 @@ function AWACS:_CheckAwacsStatus() report:Add("***** Cannot obtain (yet) this missions OpsGroup!") end - if ESmission:IsExecuting() then + if RESMission and RESMission:IsExecuting() then -- make the actual change in the queue self.ShiftChangeEscortsFlag = false self.ShiftChangeEscortsRequested = false -- cancel old mission if ESmission and ESmission:IsNotOver() then - ESmission:Cancel() + ESmission:__Cancel(1) end self.EscortMission[i] = self.EscortMissionReplacement[i] self.EscortMissionReplacement[i] = nil @@ -6471,6 +6524,7 @@ function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo if auftragtype == AUFTRAG.Type.ALERT5 then -- all correct local capauftrag = AUFTRAG:NewCAP(Anchor.StationZone,Angels*1000,AnchorSpeed,Anchor.StationZone:GetCoordinate(),0,15,{}) + capauftrag:SetMissionRange(self.MaxMissionRange) capauftrag:SetTime(nil,((self.CAPTimeOnStation*3600)+(15*60))) capauftrag:AddAsset(managedgroup.FlightGroup) self.CatchAllMissions[#self.CatchAllMissions+1] = capauftrag @@ -6834,7 +6888,8 @@ function AWACS:onafterAwacsShiftChange(From,Event,To) self.CatchAllMissions[#self.CatchAllMissions+1] = mission local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600 mission:SetTime(nil,timeonstation) - + mission:SetMissionRange(self.MaxMissionRange) + AwacsAW:AddMission(mission) self.AwacsMissionReplacement = mission diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 76e442df3..b8382f136 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -491,6 +491,9 @@ function BRIGADE:onafterStatus(From, Event, To) -- Info --- ----------- + -- Display tactival overview. + self:_TacticalOverview() + -- General info: if self.verbose>=1 then diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 7b716ed5c..e7805f25a 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -31,7 +31,7 @@ -- @image OPS_CSAR.jpg --- --- Last Update April 2024 +-- Last Update Jan 2025 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -41,6 +41,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. -- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups. +-- @field Core.Set#SET_GROUP UserSetGroup Set of CSAR heli groups as designed by the mission designer (if any set). -- @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) @@ -91,7 +92,7 @@ -- mycsar.immortalcrew = true -- Set to true to make wounded crew immortal. -- mycsar.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- mycsar.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. --- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- mycsar.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. Will also try to add ZONE and STATIC objects with this prefix once at startup. -- mycsar.max_units = 6 -- max number of pilots that can be carried if #CSAR.AircraftType is undefined. -- mycsar.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. -- mycsar.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. @@ -116,8 +117,17 @@ -- mycsar.topmenuname = "CSAR" -- set the menu entry name -- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default -- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each +-- mycsar.AllowIRStrobe = false -- Allow a menu item to request an IR strobe to find a downed pilot at night (requires NVGs to see it). +-- mycsar.IRStrobeRuntime = 300 -- If an IR Strobe is activated, it runs for 300 seconds (5 mins). +-- +-- ## 2.1 Create own SET_GROUP to manage CTLD Pilot groups +-- +-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. +-- -- Needs to be set before starting the CSAR instance. +-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart() +-- mycsar:SetOwnSetPilotGroups(myset) -- --- ## 2.1 SRS Features and Other Features +-- ## 2.2 SRS Features and Other Features -- -- mycsar.useSRS = false -- Set true to use FF\'s SRS integration -- mycsar.SRSPath = "C:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) @@ -136,6 +146,7 @@ -- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat -- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases. -- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane +-- mycsar.CreateRadioBeacons = true -- set to false to disallow creating ADF radio beacons. -- -- ## 3. Results -- @@ -256,6 +267,10 @@ CSAR = { topmenuname = "CSAR", ADFRadioPwr = 1000, PilotWeight = 80, + CreateRadioBeacons = true, + UserSetGroup = nil, + AllowIRStrobe = false, + IRStrobeRuntime = 300, } --- Downed pilots info. @@ -272,6 +287,7 @@ CSAR = { -- @field #number timestamp Timestamp for approach process. -- @field #boolean alive Group is alive or dead/rescued. -- @field #boolean wetfeet Group is spawned over (deep) water. +-- @field #string BeaconName Name of radio beacon - if any. --- All slot / Limit settings -- @type CSAR.AircraftType @@ -293,10 +309,11 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH58D"] = 2 +CSAR.AircraftType["CH-47Fbl1"] = 31 --- CSAR class version. -- @field #string version -CSAR.version="1.0.24" +CSAR.version="1.0.30" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -455,6 +472,9 @@ function CSAR:New(Coalition, Template, Alias) -- added 1.0.16 self.PilotWeight = 80 + + -- Own SET_GROUP if any + self.UserSetGroup = nil -- WARNING - here\'ll be dragons -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua @@ -633,7 +653,7 @@ end -- @param #string Playername Name of Player (if applicable) -- @param #boolean Wetfeet Ejected over water -- @return #CSAR self. -function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet) +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet,BeaconName) self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) -- create new entry @@ -641,7 +661,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.desc = Description or "" DownedPilot.frequency = Frequency or 0 DownedPilot.index = self.downedpilotcounter - DownedPilot.name = Groupname or "" + DownedPilot.name = Groupname or Playername or "" DownedPilot.originalUnit = OriginalUnit or "" DownedPilot.player = Playername or "" DownedPilot.side = Side or 0 @@ -650,6 +670,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript DownedPilot.timestamp = 0 DownedPilot.alive = true DownedPilot.wetfeet = Wetfeet or false + DownedPilot.BeaconName = BeaconName -- Add Pilot local PilotTable = self.downedPilots @@ -736,7 +757,6 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet) :NewWithAlias(template,alias) :InitCoalition(coalition) :InitCountry(country) - --:InitAIOnOff(pilotcacontrol) :InitDelayOff() :SpawnFromCoordinate(point) @@ -789,6 +809,8 @@ end -- @param #boolean noMessage -- @param #string _description Description -- @param #boolean forcedesc Use the description only for the pilot track entry +-- @return Wrapper.Group#GROUP PilotInField Pilot GROUP object +-- @return #string AliasName Alias display name 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}) @@ -818,8 +840,18 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end end + local BeaconName + + if _playerName then + BeaconName = _playerName..math.random(1,10000) + elseif _unitName then + BeaconName = _unitName..math.random(1,10000) + else + BeaconName = "Ghost-1-1"..math.random(1,10000) + end + if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0 - self:_AddBeaconToGroup(_spawnedGroup, _freq) + self:_AddBeaconToGroup(_spawnedGroup, _freq, BeaconName) end self:_AddSpecialOptions(_spawnedGroup) @@ -844,11 +876,11 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla local _GroupName = _spawnedGroup:GetName() or _alias - self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet) + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet,BeaconName) self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage, _playerName) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc. - return self + return _spawnedGroup, _alias 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. @@ -962,7 +994,6 @@ end -- @param Core.Point#COORDINATE Point -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. --- @param #boolean addBeacon (optional) 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. @@ -1792,9 +1823,6 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid end _text = string.gsub(_text,"km"," kilometer") _text = string.gsub(_text,"nm"," nautical miles") - --self.msrs:SetVoice(self.SRSVoice) - --self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1) - --self:I("Voice = "..self.SRSVoice) self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord) end return self @@ -1803,8 +1831,9 @@ end --- (Internal) Function to get string of a group\'s position. -- @param #CSAR self -- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @param Wrapper.Unit#UNIT _Unit Requesting helo pilot unit -- @return #string Coordinates as Text -function CSAR:_GetPositionOfWounded(_woundedGroup) +function CSAR:_GetPositionOfWounded(_woundedGroup,_Unit) self:T(self.lid .. " _GetPositionOfWounded") local _coordinate = _woundedGroup:GetCoordinate() local _coordinatesText = "None" @@ -1819,6 +1848,26 @@ function CSAR:_GetPositionOfWounded(_woundedGroup) _coordinatesText = _coordinate:ToStringBULLS(self.coalition) end end + if _Unit and _Unit:GetPlayerName() then + local playername = _Unit:GetPlayerName() + if playername then + local settings = _DATABASE:GetPlayerSettings(playername) or _SETTINGS + if settings then + self:T("Get Settings ok!") + if settings:IsA2G_MGRS() then + _coordinatesText = _coordinate:ToStringMGRS(settings) + elseif settings:IsA2G_LL_DMS() then + _coordinatesText = _coordinate:ToStringLLDMS(settings) + elseif settings:IsA2G_LL_DDM() then + _coordinatesText = _coordinate:ToStringLLDDM(settings) + elseif settings:IsA2G_BR() then + -- attention this is the distance from the ASKING unit to target, not from RECCE to target! + local startcoordinate = _Unit:GetCoordinate() + _coordinatesText = _coordinate:ToStringBR(startcoordinate,settings) + end + end + end + end return _coordinatesText end @@ -1844,22 +1893,26 @@ function CSAR:_DisplayActiveSAR(_unitName) self:T({Table=_value}) local _woundedGroup = _value.group if _woundedGroup and _value.alive then - local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup,_heli) local _helicoord = _heli:GetCoordinate() local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) local distancetext = "" - if _SETTINGS:IsImperial() then + local settings = _SETTINGS + if _heli:GetPlayerName() then + settings = _DATABASE:GetPlayerSettings(_heli:GetPlayerName()) or _SETTINGS + end + if settings:IsImperial() then distancetext = string.format("%.1fnm",UTILS.MetersToNM(_distance)) else distancetext = string.format("%.1fkm", _distance/1000.0) end - if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) - else - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) - end + if _value.frequency == 0 or self.CreateRadioBeacons == false then--shagrat insert CASEVAC without Frequency + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) }) + else + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) + end end end @@ -1940,7 +1993,7 @@ function CSAR:_SignalFlare(_unitName) else _distance = string.format("%.1fkm",_closest.distance/1000) end - local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + local _msg = string.format("%s - Firing signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) local _coord = _closest.pilot:GetCoordinate() @@ -1974,7 +2027,7 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda end - self:F("Voice = "..voice) + --self:F("Voice = "..voice) self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate) end if ToScreen == true or ToScreen == nil then @@ -1988,6 +2041,41 @@ function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen) return self end +---(Internal) Request IR Strobe at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_ReqIRStrobe( _unitName ) + self:T(self.lid .. " _ReqIRStrobe") + local _heli = self:_GetSARHeli(_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 > 0 and _closest.distance < smokedist then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _distance = string.format("%.1fkm",_closest.distance/1000) + if _SETTINGS:IsImperial() then + _distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.1fkm",_closest.distance/1000) + end + local _msg = string.format("%s - IR Strobe active at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true) + _closest.pilot:NewIRMarker(true,self.IRStrobeRuntime or 300) + else + local _distance = string.format("%.1fkm",smokedist/1000) + 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, false, false, true) + end + return self +end + ---(Internal) Request smoke at closest downed pilot. --@param #CSAR self --@param #string _unitName Name of the helicopter @@ -2111,12 +2199,12 @@ function CSAR:_AddMedevacMenuItem() local coalition = self.coalition local allheligroupset = self.allheligroupset -- Core.Set#SET_GROUP local _allHeliGroups = allheligroupset:GetSetObjects() - -- rebuild units table local _UnitList = {} 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 + local _unit = _group:GetFirstUnitAlive() -- Asume that there is only one unit in the flight for players + if _unit then + --self:T("Unitname ".._unit:GetName().." IsAlive "..tostring(_unit:IsAlive()).." IsPlayer "..tostring(_unit:IsPlayer())) if _unit:IsAlive() and _unit:IsPlayer() then local unitName = _unit:GetName() _UnitList[unitName] = unitName @@ -2139,7 +2227,12 @@ function CSAR:_AddMedevacMenuItem() local _rootMenu1 = MENU_GROUP_COMMAND:New(_group,"List Active CSAR",_rootPath, self._DisplayActiveSAR,self,_unitName) local _rootMenu2 = MENU_GROUP_COMMAND:New(_group,"Check Onboard",_rootPath, self._CheckOnboard,self,_unitName) local _rootMenu3 = MENU_GROUP_COMMAND:New(_group,"Request Signal Flare",_rootPath, self._SignalFlare,self,_unitName) - local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName):Refresh() + local _rootMenu4 = MENU_GROUP_COMMAND:New(_group,"Request Smoke",_rootPath, self._Reqsmoke,self,_unitName) + if self.AllowIRStrobe then + local _rootMenu5 = MENU_GROUP_COMMAND:New(_group,"Request IR Strobe",_rootPath, self._ReqIRStrobe,self,_unitName):Refresh() + else + _rootMenu4:Refresh() + end end end end @@ -2230,9 +2323,13 @@ end -- @param #CSAR self -- @param Wrapper.Group#GROUP _group Group #GROUP object. -- @param #number _freq Frequency to use -function CSAR:_AddBeaconToGroup(_group, _freq) +-- @param #string _name Beacon Name to use +-- @return #CSAR self +function CSAR:_AddBeaconToGroup(_group, _freq, _name) self:T(self.lid .. " _AddBeaconToGroup") + if self.CreateRadioBeacons == false then return end local _group = _group + if _group == nil then --return frequency to pool of available for _i, _current in ipairs(self.UsedVHFFrequencies) do @@ -2247,22 +2344,24 @@ function CSAR:_AddBeaconToGroup(_group, _freq) if _group:IsAlive() then local _radioUnit = _group:GetUnit(1) if _radioUnit then - local name = _radioUnit:GetName() + local name = _radioUnit:GetName() local Frequency = _freq -- Freq in Hertz local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight + trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,_name) -- Beacon in MP only runs for exactly 30secs straight end end + return self end --- (Internal) Helper function to (re-)add beacon to downed pilot. -- @param #CSAR self --- @param #table _args Arguments +-- @return #CSAR self function CSAR:_RefreshRadioBeacons() self:T(self.lid .. " _RefreshRadioBeacons") + if self.CreateRadioBeacons == false then return end if self:_CountActiveDownedPilots() > 0 then local PilotTable = self.downedPilots for _,_pilot in pairs (PilotTable) do @@ -2270,8 +2369,10 @@ function CSAR:_RefreshRadioBeacons() local pilot = _pilot -- #CSAR.DownedPilot local group = pilot.group local frequency = pilot.frequency or 0 -- thanks to @Thrud + local bname = pilot.BeaconName or pilot.name..math.random(1,100000) + trigger.action.stopRadioTransmission(bname) if group and group:IsAlive() and frequency > 0 then - self:_AddBeaconToGroup(group,frequency) + self:_AddBeaconToGroup(group,frequency,bname) end end end @@ -2308,6 +2409,16 @@ function CSAR:_ReachedPilotLimit() end end + --- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. + -- Needs to be set before starting the CSAR instance. + -- @param #CSAR self + -- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CSAR pilot groups. + -- @return #CSAR self + function CSAR:SetOwnSetPilotGroups(Set) + self.UserSetGroup = Set + return self + end + ------------------------------ --- FSM internal Functions --- ------------------------------ @@ -2329,7 +2440,9 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) - if self.allowbronco then + if self.UserSetGroup then + self.allheligroupset = self.UserSetGroup + elseif self.allowbronco then local prefixes = self.csarPrefix or {} self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart() elseif self.useprefix then @@ -2338,7 +2451,24 @@ function CSAR:onafterStart(From, Event, To) else self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() end - self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also? + + self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() + + local staticmashes = SET_STATIC:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterOnce() + local zonemashes = SET_ZONE:New():FilterPrefixes(self.mashprefix):FilterOnce() + + if staticmashes:Count() > 0 then + for _,_mash in pairs(staticmashes.Set) do + self.mash:AddObject(_mash) + end + end + + if zonemashes:Count() > 0 then + for _,_mash in pairs(zonemashes.Set) do + self.mash:AddObject(_mash) + end + end + if not self.coordinate then local csarhq = self.mash:GetRandom() if csarhq then diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 037446ea4..cd0bc8403 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,12 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update April 2024 +-- Last Update April 2025 + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +-- TODO CTLD_CARGO +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ do @@ -46,6 +51,12 @@ do -- @field #string Subcategory Sub-category name. -- @field #boolean DontShowInMenu Show this item in menu or not. -- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item. +-- @field #table ResourceMap Resource Map information table if it has been set for static cargo items. +-- @field #string StaticShape Individual shape if set. +-- @field #string StaticType Individual type if set. +-- @field #string StaticCategory Individual static category if set. +-- @field #list<#string> TypeNames Table of unit types able to pick this cargo up. +-- @field #number Stock0 Initial stock, if any given. -- @extends Core.Base#BASE --- @@ -63,6 +74,7 @@ CTLD_CARGO = { HasBeenDropped = false, PerCrateMass = 0, Stock = nil, + Stock0 = nil, Mark = nil, DontShowInMenu = false, Location = nil, @@ -77,6 +89,7 @@ CTLD_CARGO = { -- @field #string REPAIR -- @field #string ENGINEERS -- @field #string STATIC + -- @field #string GCLOADABLE CTLD_CARGO.Enum = { VEHICLE = "Vehicle", -- #string vehicles TROOPS = "Troops", -- #string troops @@ -85,6 +98,7 @@ CTLD_CARGO = { REPAIR = "Repair", -- #string repair ENGINEERS = "Engineers", -- #string engineers STATIC = "Static", -- #string statics + GCLOADABLE = "GC_Loadable", -- #string dynamiccargo } --- Function to create new CTLD_CARGO object. @@ -119,17 +133,87 @@ CTLD_CARGO = { self.HasBeenDropped = Dropped or false --#boolean self.PerCrateMass = PerCrateMass or 0 -- #number self.Stock = Stock or nil --#number + self.Stock0 = Stock or nil --#number self.Mark = nil self.Subcategory = Subcategory or "Other" self.DontShowInMenu = DontShowInMenu or false + self.ResourceMap = nil + self.StaticType = "container_cargo" -- "container_cargo" + self.StaticShape = nil + self.TypeNames = nil + self.StaticCategory = "Cargos" if type(Location) == "string" then Location = ZONE:New(Location) end self.Location = Location return self end - - --- Query Location. + + --- Add specific static type and shape to this CARGO. + -- @param #CTLD_CARGO self + -- @param #string TypeName + -- @param #string ShapeName + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticTypeAndShape(Category,TypeName,ShapeName) + self.StaticCategory = Category or "Cargos" + self.StaticType = TypeName or "container_cargo" + self.StaticShape = ShapeName + return self + end + + --- Get the specific static type and shape from this CARGO if set. + -- @param #CTLD_CARGO self + -- @return #string Category + -- @return #string TypeName + -- @return #string ShapeName + function CTLD_CARGO:GetStaticTypeAndShape() + return self.StaticCategory, self.StaticType, self.StaticShape + end + + --- Add specific unit types to this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param #string UnitTypes Unit type name, can also be a #list<#string> table of unit type names. + -- @return #CTLD_CARGO self + function CTLD_CARGO:AddUnitTypeName(UnitTypes) + if not self.TypeNames then self.TypeNames = {} end + if type(UnitTypes) ~= "table" then UnitTypes = {UnitTypes} end + for _,_singletype in pairs(UnitTypes or {}) do + self.TypeNames[_singletype]=_singletype + end + return self + end + + --- Check if a specific unit can carry this CARGO (restrict what types can pick this up). + -- @param #CTLD_CARGO self + -- @param Wrapper.Unit#UNIT Unit + -- @return #boolean Outcome + function CTLD_CARGO:UnitCanCarry(Unit) + if self.TypeNames == nil then return true end + local typename = Unit:GetTypeName() or "none" + if self.TypeNames[typename] then + return true + else + return false + end + end + + --- Add Resource Map information table + -- @param #CTLD_CARGO self + -- @param #table ResourceMap + -- @return #CTLD_CARGO self + function CTLD_CARGO:SetStaticResourceMap(ResourceMap) + self.ResourceMap = ResourceMap + return self + end + + --- Get Resource Map information table + -- @param #CTLD_CARGO self + -- @return #table ResourceMap + function CTLD_CARGO:GetStaticResourceMap() + return self.ResourceMap + end + + --- Query Location. -- @param #CTLD_CARGO self -- @return Core.Zone#ZONE location or `nil` if not set function CTLD_CARGO:GetLocation() @@ -240,7 +324,7 @@ CTLD_CARGO = { --- Get Stock. -- @param #CTLD_CARGO self - -- @return #number Stock + -- @return #number Stock or -1 if unlimited. function CTLD_CARGO:GetStock() if self.Stock then return self.Stock @@ -249,6 +333,28 @@ CTLD_CARGO = { end end + --- Get Stock0. + -- @param #CTLD_CARGO self + -- @return #number Stock0 or -1 if unlimited. + function CTLD_CARGO:GetStock0() + if self.Stock0 then + return self.Stock0 + else + return -1 + end + end + + --- Get relative Stock. + -- @param #CTLD_CARGO self + -- @return #number Stock Percentage like 75, or -1 if unlimited. + function CTLD_CARGO:GetRelativeStock() + if self.Stock and self.Stock0 then + return math.floor((self.Stock/self.Stock0)*100) + else + return -1 + end + end + --- Add Stock. -- @param #CTLD_CARGO self -- @param #number Number to add, none if nil. @@ -724,9 +830,9 @@ do -- my_ctld.CrateDistance = 35 -- List and Load crates in this radius only. -- my_ctld.PackDistance = 35 -- Pack crates in this radius only -- my_ctld.dropcratesanywhere = false -- Option to allow crates to be dropped anywhere. --- my_ctld.dropAsCargoCrate = false -- Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. --- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load. --- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load. +-- my_ctld.dropAsCargoCrate = false -- Hercules only: Parachuted herc cargo is not unpacked automatically but placed as crate to be unpacked. Needs a cargo with the same name defined like the cargo that was dropped. +-- my_ctld.maximumHoverHeight = 15 -- Hover max this high to load in meters. +-- my_ctld.minimumHoverHeight = 4 -- Hover min this low to load in meters. -- 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). @@ -752,10 +858,47 @@ do -- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. -- my_ctld.RadioSound = "beacon.ogg" -- -- this sound will be hearable if you tune in the beacon frequency. Add the sound file to your miz. -- my_ctld.RadioSoundFC3 = "beacon.ogg" -- this sound will be hearable by FC3 users (actually all UHF radios); change to something like "beaconsilent.ogg" and add the sound file to your miz if you don't want to annoy FC3 pilots. --- --- ## 2.1 User functions +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook +-- my_ctld.TroopUnloadDistGround = 5 -- If hovering, spawn dropped troops this far away in meters from the helo +-- my_ctld.TroopUnloadDistHover = 1.5 -- If grounded, spawn dropped troops this far away in meters from the helo +-- my_ctld.TroopUnloadDistGroundHerc = 25 -- On the ground, unload troops this far behind the Hercules +-- my_ctld.TroopUnloadDistGroundHook = 15 -- On the ground, unload troops this far behind the Chinook +-- my_ctld.TroopUnloadDistHoverHook = 5 -- When hovering, unload troops this far behind the Chinook +-- my_ctld.showstockinmenuitems = false -- When set to true, the menu lines will also show the remaining items in stock (that is, if you set any), downside is that the menu for all will be build every 30 seconds anew. -- --- ### 2.1.1 Adjust or add chopper unit-type capabilities +-- ## 2.1 CH-47 Chinook support +-- +-- The Chinook comes with the option to use the ground crew menu to load and unload cargo into the Helicopter itself for better immersion. As well, it can sling-load cargo from ground. The cargo you can actually **create** +-- from this menu is limited to contain items from the airbase or FARP's resources warehouse and can take a number of shapes (static shapes in the category of cargo) independent of their contents. If you unload this +-- kind of cargo with the ground crew, the contents will be "absorbed" into the airbase or FARP you landed at, and the cargo static will be removed after ca 2 mins. +-- +-- ## 2.1.1 Moose CTLD created crate cargo +-- +-- Given the correct shape, Moose created cargo can theoretically be either loaded with the ground crew or via the F10 CTLD menu. **It is strongly stated to avoid using shapes with +-- CTLD which can be Ground Crew loaded.** +-- Static shapes loadable *into* the Chinook and thus to **be avoided for CTLD** are at the time of writing: +-- +-- * Ammo box (type "ammo_crate") +-- * M117 bomb crate (type name "m117_cargo") +-- * Dual shell fuel barrels (type name "barrels") +-- * UH-1H net (type name "uh1h_cargo") +-- +-- All other kinds of cargo can be sling-loaded. +-- +-- ## 2.1.2 Recommended settings +-- +-- my_ctld.basetype = "container_cargo" -- **DO NOT** change this to a base type which could also be loaded by F8/GC to avoid logic problems! +-- my_ctld.forcehoverload = false -- no hover autoload, leads to cargo complications with ground crew created cargo items +-- my_ctld.pilotmustopendoors = true -- crew must open back loading door 50% (horizontal) or more - watch out for NOT adding a back door gunner! +-- my_ctld.enableslingload = true -- will set cargo items as sling-loadable. +-- my_ctld.enableChinookGCLoading = true -- this will effectively suppress the crate load and drop for CTLD_CARGO.Enum.STATIC types for CTLD for the Chinook. +-- my_ctld.movecratesbeforebuild = true -- leave as is at the pain of building crate still **inside** of the Hook. +-- my_ctld.nobuildinloadzones = true -- don't build where you load. +-- my_ctld.ChinookTroopCircleRadius = 5 -- Radius for troops dropping in a nice circle. Adjust to your planned squad size for the Chinook. +-- +-- ## 2.2 User functions +-- +-- ### 2.2.1 Adjust or add chopper unit-type capabilities -- -- Use this function to adjust what a heli type can or cannot do: -- @@ -780,8 +923,11 @@ do -- ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats -- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, +-- ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, +-- ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, +-- ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 8000}, -- --- ### 2.1.2 Activate and deactivate zones +-- ### 2.2.2 Activate and deactivate zones -- -- Activate a zone: -- @@ -793,7 +939,7 @@ do -- -- Deactivate zone called Name of type #CTLD.CargoZoneType ZoneType: -- my_ctld:DeactivateZone(Name,CTLD.CargoZoneType.DROP) -- --- ## 2.1.3 Limit and manage available resources +-- ## 2.2.3 Limit and manage available resources -- -- When adding generic cargo types, you can effectively limit how many units can be dropped/build by the players, e.g. -- @@ -812,6 +958,13 @@ do -- -- Notes: -- Troops dropped back into a LOAD zone will effectively be added to the stock. Crates lost in e.g. a heli crash are just that - lost. +-- +-- ## 2.2.4 Create own SET_GROUP to manage CTLD Pilot groups +-- +-- -- Parameter: Set The SET_GROUP object created by the mission designer/user to represent the CTLD pilot groups. +-- -- Needs to be set before starting the CTLD instance. +-- local myset = SET_GROUP:New():FilterPrefixes("Helikopter"):FilterCoalitions("red"):FilterStart() +-- my_ctld:SetOwnSetPilotGroups(myset) -- -- ## 3. Events -- @@ -846,17 +999,25 @@ do -- -- 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) +-- function my_ctld:OnAfterTroopsExtracted(From, Event, To, Group, Unit, Troops, Troopname) -- ... your code here ... -- end -- -- ## 3.5 OnAfterCratesDropped -- --- This function is called when a player has deployed crates to a DROP zone: +-- This function is called when a player has deployed crates: -- -- function my_ctld:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- ... your code here ... -- end +-- +--- ## 3.6 OnAfterHelicopterLost +-- +-- This function is called when a player has deployed left a unit or crashed/died: +-- +-- function my_ctld:OnAfterHelicopterLost(From, Event, To, Unitname, Cargotable) +-- ... your code here ... +-- end -- -- ## 3.6 OnAfterCratesBuild, OnAfterCratesRepaired -- @@ -925,11 +1086,11 @@ do -- -- ## 4.7 List Inventory -- --- Lists invetory of available units to drop or build. +-- Lists inventory of available units to drop or build. -- --- ## 5. Support for Hercules mod by Anubis +-- ## 5. Support for fixed wings -- --- Basic support for the Hercules mod By Anubis has been build into CTLD - that is you can load/drop/build the same way and for the same objects as +-- Basic support for the Hercules mod By Anubis has been build into CTLD, as well as Bronco and Mosquito - that is you can load/drop/build the same way and for the same objects as -- the helicopters (main method). -- To cover objects and troops which can be loaded from the groud crew Rearm/Refuel menu (F8), you need to use @{#CTLD_HERCULES.New}() and link -- this object to your CTLD setup (alternative method). In this case, do **not** use the `Hercules_Cargo.lua` or `Hercules_Cargo_CTLD.lua` which are part of the mod @@ -941,13 +1102,13 @@ do -- -- 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 --- my_ctld.HercMaxSpeed = 77 -- 77mps or 270kph or 150kn +-- my_ctld.enableFixedWing = true +-- my_ctld.FixedMinAngels = 155 -- for troop/cargo drop via chute in meters, ca 470 ft +-- my_ctld.FixedMaxAngels = 2000 -- for troop/cargo drop via chute in meters, ca 6000 ft +-- my_ctld.FixedMaxSpeed = 77 -- 77mps or 270kph or 150kn -- --- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `HercMaxSpeed` and in the AGL bracket between --- `HercMinAngels` and `HercMaxAngels`! +-- Hint: you can **only** airdrop from the Hercules if you are "in parameters", i.e. at or below `FixedMaxSpeed` and in the AGLFixedMinAngelseen +-- `FixedMinAngels` and `FixedMaxAngels`! -- -- Also, the following options need to be set to `true`: -- @@ -955,9 +1116,9 @@ do -- -- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method, use either the above OR this method, NOT both!) -- --- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: +-- Taking another approach, integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: @@ -1031,103 +1192,71 @@ do -- -- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2) -- --- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template --- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter). --- --- The following code will build a FARP at the coordinate the FOB was dropped and built: +-- The following code will build a FARP at the coordinate the FOB was dropped and built (the UTILS function used below **does not** need a template for the statics): -- --- -- FARP Radio. First one has 130AM, next 131 and for forth --- local FARPFreq = 130 --- local FARPName = 1 -- numbers 1..10 --- --- local FARPClearnames = { --- [1]="London", --- [2]="Dallas", --- [3]="Paris", --- [4]="Moscow", --- [5]="Berlin", --- [6]="Rome", --- [7]="Madrid", --- [8]="Warsaw", --- [9]="Dublin", --- [10]="Perth", --- } --- --- function BuildAFARP(Coordinate) --- local coord = Coordinate -- Core.Point#COORDINATE --- --- local FarpName = ((FARPName-1)%10)+1 --- local FName = FARPClearnames[FarpName] --- --- FARPFreq = FARPFreq + 1 --- FARPName = FARPName + 1 +-- -- FARP Radio. First one has 130AM name London, next 131 name Dallas, and so forth. +-- local FARPFreq = 129 +-- local FARPName = 1 --numbers 1..10 +-- +-- local FARPClearnames = { +-- [1]="London", +-- [2]="Dallas", +-- [3]="Paris", +-- [4]="Moscow", +-- [5]="Berlin", +-- [6]="Rome", +-- [7]="Madrid", +-- [8]="Warsaw", +-- [9]="Dublin", +-- [10]="Perth", +-- } +-- +-- function BuildAFARP(Coordinate) +-- local coord = Coordinate --Core.Point#COORDINATE -- --- -- Create a SPAWNSTATIC object from a template static FARP object. --- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA) --- --- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency. --- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0) --- SpawnStaticFarp:InitDead(false) --- --- -- Spawn FARP --- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false) --- local Heading = 0 --- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName) --- --- -- ATC and services - put them 125m from the center of the zone towards North --- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals") --- FarpVehicles:InitHeading(180) --- local FarpVCoord = coord:Translate(125,0) --- FarpVehicles:SpawnFromCoordinate(FarpVCoord) --- --- -- We will put the rest of the statics in a nice circle around the center --- local base = 330 --- local delta = 30 --- --- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA) --- local sockcoord = coord:Translate(125,base) --- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName) --- base=base-delta --- --- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA) --- local fuelcoord = coord:Translate(125,base) --- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName) --- base=base-delta --- --- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA) --- local ammocoord = coord:Translate(125,base) --- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName) --- base=base-delta --- --- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA) --- local CommandCoord = coord:Translate(125,base) --- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName) --- base=base-delta --- --- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) --- local Tent1Coord = coord:Translate(125,base) --- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName) --- base=base-delta --- --- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) --- local Tent2Coord = coord:Translate(125,base) --- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName) --- --- -- add a loadzone to CTLD --- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) --- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue() --- end +-- local FarpNameNumber = ((FARPName-1)%10)+1 -- make sure 11 becomes 1 etc +-- local FName = FARPClearnames[FarpNameNumber] -- get clear namee -- --- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle) --- local name = Vehicle:GetName() --- if string.match(name,"FOB",1,true) then --- local Coord = Vehicle:GetCoordinate() --- Vehicle:Destroy(false) --- BuildAFARP(Coord) --- end --- end +-- FARPFreq = FARPFreq + 1 +-- FARPName = FARPName + 1 +-- +-- FName = FName .. " FAT COW "..tostring(FARPFreq).."AM" -- make name unique +-- +-- -- Get a Zone for loading +-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),150,false) +-- +-- -- Spawn a FARP with our little helper and fill it up with resources (10t fuel each type, 10 pieces of each known equipment) +-- UTILS.SpawnFARPAndFunctionalStatics(FName,Coordinate,ENUMS.FARPType.INVISIBLE,my_ctld.coalition,country.id.USA,FarpNameNumber,FARPFreq,radio.modulation.AM,nil,nil,nil,10,10) -- +-- -- add a loadzone to CTLD +-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue() +-- end +-- +-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle) +-- local name = Vehicle:GetName() +-- if string.find(name,"FOB",1,true) then +-- local Coord = Vehicle:GetCoordinate() +-- Vehicle:Destroy(false) +-- BuildAFARP(Coord) +-- end +-- end -- +-- ## 8. Transport crates and troops with CA (Combined Arms) trucks +-- +-- You can optionally also allow to CTLD with CA trucks and other vehicles: +-- +-- -- Create a SET_CLIENT to capture CA vehicles steered by players +-- local truckers = SET_CLIENT:New():HandleCASlots():FilterCoalitions("blue"):FilterPrefixes("Truck"):FilterStart() +-- -- Allow CA transport +-- my_ctld:AllowCATransport(true,truckers) +-- -- Set truck capability by typename +-- my_ctld:SetUnitCapabilities("M 818", true, true, 2, 12, 9, 4500) +-- -- Alternatively set truck capability with a UNIT object +-- local GazTruck = UNIT:FindByName("GazTruck-1-1") +-- my_ctld:SetUnitCapabilities(GazTruck, true, true, 2, 12, 9, 4500) +-- +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -1152,6 +1281,17 @@ CTLD = { wpZones = {}, dropOffZones = {}, pickupZones = {}, + DynamicCargo = {}, + ChinookTroopCircleRadius = 5, + TroopUnloadDistGround = 5, + TroopUnloadDistGroundHerc = 25, + TroopUnloadDistGroundHook = 15, + TroopUnloadDistHoverHook = 5, + TroopUnloadDistHover = 1.5, + UserSetGroup = nil, + LoadedGroupsTable = {}, + keeploadtable = true, + allowCATransport = false, } ------------------------------ @@ -1189,6 +1329,12 @@ CTLD.RadioModulation = { FM = 1, } +--- Loaded Cargo +-- @type CTLD.LoadedCargo +-- @field #number Troopsloaded +-- @field #number Cratesloaded +-- @field #table Cargo Table of #CTLD_CARGO objects + --- Zone Info. -- @type CTLD.CargoZone -- @field #string name Name of Zone. @@ -1251,11 +1397,22 @@ CTLD.UnitTypeCapabilities = { ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550}, ["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400}, + ["CH-47Fbl1"] = {type="CH-47Fbl1", crates=true, troops=true, cratelimit = 4, trooplimit = 31, length = 20, cargoweightlimit = 10800}, + ["MosquitoFBMkVI"] = {type="MosquitoFBMkVI", crates= true, troops=false, cratelimit = 2, trooplimit = 0, length = 13, cargoweightlimit = 1800}, + ["M 818"] = {type="M 818", crates= true, troops=true, cratelimit = 4, trooplimit = 12, length = 9, cargoweightlimit = 4500}, +} + +--- Allowed Fixed Wing Types +-- @type CTLD.FixedWingTypes +CTLD.FixedWingTypes = { + ["Hercules"] = "Hercules", + ["Bronco"] = "Bronco", + ["Mosquito"] = "Mosquito", } --- CTLD class version. -- @field #string version -CTLD.version="1.0.54" +CTLD.version="1.2.33" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1322,7 +1479,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. self:AddTransition("*", "CratesBuildStarted", "*") -- CTLD build event. self:AddTransition("*", "CratesRepairStarted", "*") -- CTLD repair event. - self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "HelicopterLost", "*") -- CTLD lost event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Loaded", "*") -- CTLD load event. self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -1393,10 +1552,11 @@ function CTLD:New(Coalition, Prefixes, Alias) self.troopdropzoneradius = 100 -- added support Hercules Mod - 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 + self.enableHercules = false -- deprecated + self.enableFixedWing = false + self.FixedMinAngels = 165 -- for troop/cargo drop via chute + self.FixedMaxAngels = 2000 -- for troop/cargo drop via chute + self.FixedMaxSpeed = 77 -- 280 kph or 150kn eq 77 mps -- message suppression self.suppressmessages = false @@ -1423,17 +1583,27 @@ function CTLD:New(Coalition, Prefixes, Alias) self.filepath = nil self.saveinterval = 600 self.eventoninject = true + self.keeploadtable = true + self.LoadedGroupsTable = {} -- sub categories self.usesubcats = false self.subcats = {} self.subcatsTroop = {} + self.showstockinmenuitems = false -- disallow building in loadzones self.nobuildinloadzones = true self.movecratesbeforebuild = true self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} + -- Chinook + self.enableChinookGCLoading = true + self.ChinookTroopCircleRadius = 5 + + -- User SET_GROUP + self.UserSetGroup = nil + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -1452,6 +1622,10 @@ function CTLD:New(Coalition, Prefixes, Alias) math.random() end + -- CA Transport + self.allowCATransport = false -- #boolean + self.CATransportSet = nil -- Core.Set#SET_CLIENT + self:_GenerateVHFrequencies() self:_GenerateUHFrequencies() self:_GenerateFMFrequencies() @@ -1524,7 +1698,8 @@ 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 troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnBeforeCratesPickedUp. @@ -1535,7 +1710,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 crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self --- FSM Function OnBeforeTroopsDeployed. @@ -1557,7 +1732,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 #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self --- FSM Function OnBeforeCratesBuild. @@ -1590,6 +1765,8 @@ 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 #string ZoneName Name of the Zone where the Troops have been RTB'd. + -- @param Core.Zone#ZONE_Radius ZoneObject of the Zone where the Troops have been RTB'd. --- FSM Function OnAfterTroopsPickedUp. -- @function [parent=#CTLD] OnAfterTroopsPickedUp @@ -1610,7 +1787,8 @@ 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 troops. + -- @param Wrapper.Group#GROUP Troops extracted. + -- @param #string Troopname Name of the extracted group. -- @return #CTLD self --- FSM Function OnAfterCratesPickedUp. @@ -1621,7 +1799,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 crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self --- FSM Function OnAfterTroopsDeployed. @@ -1643,7 +1821,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 #table Cargotable Table of #CTLD_CARGO objects dropped. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self --- FSM Function OnAfterCratesBuild. @@ -1716,6 +1894,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + + --- FSM Function OnBeforeHelicopterLost. + -- @function [parent=#CTLD] OnBeforeHelicopterLost + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param #string Unitname The name of the unit lost. + -- @param #table LostCargo Table of #CTLD_CARGO object which were aboard the helicopter/transportplane lost. Can be an empty table! + + --- FSM Function OnAfterHelicopterLost. + -- @function [parent=#CTLD] OnAfterHelicopterLost + -- @param #CTLD self + -- @param #string From State. + -- @param #string Event Trigger. + -- @param #string To State. + -- @param #string Unitname The name of the unit lost. + -- @param #table LostCargo Table of #CTLD_CARGO object which were aboard the helicopter/transportplane lost. Can be an empty table! --- FSM Function OnAfterLoad. -- @function [parent=#CTLD] OnAfterLoad @@ -1726,6 +1922,14 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. -- @param #string filename (Optional) File name for loading. Default is "CTLD__Persist.csv". + --- FSM Function OnAfterLoaded. + -- @function [parent=#CTLD] OnAfterLoaded + -- @param #CTLD self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #table LoadedGroups Table of loaded groups, each entry is a table with three values: Group, TimeStamp and CargoType. + --- FSM Function OnAfterSave. -- @function [parent=#CTLD] OnAfterSave -- @param #CTLD self @@ -1765,6 +1969,16 @@ function CTLD:_GetUnitCapabilities(Unit) return capabilities end +--- (User) Function to allow transport via Combined Arms Trucks. +-- @param #CTLD self +-- @param #boolean OnOff Switch on (true) or off (false). +-- @param Core.Set#SET_CLIENT ClientSet The CA handling client set for ground transport. +-- @return #CTLD self +function CTLD:AllowCATransport(OnOff,ClientSet) + self.allowCATransport = OnOff -- #boolean + self.CATransportSet = ClientSet -- Core.Set#SET_CLIENT + return self +end --- (Internal) Function to generate valid UHF Frequencies -- @param #CTLD self @@ -1840,18 +2054,130 @@ function CTLD:_EventHandler(EventData) self:_RefreshF10Menus() end -- Herc support - if self:IsHercules(_unit) and self.enableHercules then + if self:IsFixedWing(_unit) and self.enableFixedWing then + local unitname = event.IniUnitName or "none" + self.Loaded_Cargo[unitname] = nil + self:_RefreshF10Menus() + end + -- CA support + if _unit:IsGround() and self.allowCATransport then local unitname = event.IniUnitName or "none" self.Loaded_Cargo[unitname] = nil self:_RefreshF10Menus() end return - elseif event.id == EVENTS.PlayerLeaveUnit then + elseif event.id == EVENTS.Land or event.id == EVENTS.Takeoff then + local unitname = event.IniUnitName + if self.CtldUnits[unitname] then + local _group = event.IniGroup + local _unit = event.IniUnit + self:_RefreshLoadCratesMenu(_group, _unit) + end + elseif event.id == EVENTS.PlayerLeaveUnit or event.id == EVENTS.UnitLost then -- remove from pilot table local unitname = event.IniUnitName or "none" + if self.CtldUnits[unitname] then + local lostcargo = UTILS.DeepCopy(self.Loaded_Cargo[unitname] or {}) + self:__HelicopterLost(1,unitname,lostcargo) + end self.CtldUnits[unitname] = nil self.Loaded_Cargo[unitname] = nil self.MenusDone[unitname] = nil + --elseif event.id == EVENTS.NewDynamicCargo and event.IniObjectCategory == 6 and string.match(event.IniUnitName,".+|%d%d:%d%d|PKG%d+") then + elseif event.id == EVENTS.NewDynamicCargo then + self:T(self.lid.."GC New Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling NEW + -------------- + self.DynamicCargo[event.IniDynamicCargoName] = event.IniDynamicCargo + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoLoaded then + self:T(self.lid.."GC Loaded Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling LOADING + -------------- + local dcargo = event.IniDynamicCargo -- Wrapper.DynamicCargo#DYNAMICCARGO + -- get client/unit object + local client = CLIENT:FindByPlayerName(dcargo.Owner) + if client and client:IsAlive() then + -- add to unit load list + local unitname = client:GetName() or "none" + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + loaded.Cratesloaded = loaded.Cratesloaded+1 + table.insert(loaded.Cargo,dcargo) + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + local Group = client:GetGroup() + self:_SendMessage(string.format("Crate %s loaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + self:__CratesPickedUp(1, Group, client, dcargo) + end + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoUnloaded then + self:T(self.lid.."GC Unload Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling UNLOADING + -------------- + local dcargo = event.IniDynamicCargo -- Wrapper.DynamicCargo#DYNAMICCARGO + -- get client/unit object + local client = CLIENT:FindByPlayerName(dcargo.Owner) + if client and client:IsAlive() then + -- add to unit load list + local unitname = client:GetName() or "none" + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + loaded.Cratesloaded = loaded.Cratesloaded - 1 + if loaded.Cratesloaded < 0 then loaded.Cratesloaded = 0 end + -- TODO zap cargo from list + local Loaded = {} + for _,_item in pairs (loaded.Cargo or {}) do + self:T(self.lid.."UNLOAD checking: ".._item:GetName()) + self:T(self.lid.."UNLOAD state: ".. tostring(_item:WasDropped())) + if _item and _item:GetType() == CTLD_CARGO.Enum.GCLOADABLE and event.IniDynamicCargoName and event.IniDynamicCargoName ~= _item:GetName() and not _item:WasDropped() then + table.insert(Loaded,_item) + else + table.insert(Loaded,_item) + end + end + loaded.Cargo = nil + loaded.Cargo = Loaded + self.Loaded_Cargo[unitname] = nil + self.Loaded_Cargo[unitname] = loaded + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + self.Loaded_Cargo[unitname] = loaded + end + local Group = client:GetGroup() + self:_SendMessage(string.format("Crate %s unloaded by ground crew!",event.IniDynamicCargoName), 10, false, Group) + self:__CratesDropped(1,Group,client,{dcargo}) + end + --------------- + -- End new dynamic cargo system Handling + -------------- + elseif event.id == EVENTS.DynamicCargoRemoved then + self:T(self.lid.."GC Remove Event "..event.IniDynamicCargoName) + --------------- + -- New dynamic cargo system Handling REMOVE + -------------- + self.DynamicCargo[event.IniDynamicCargoName] = nil + --------------- + -- End new dynamic cargo system Handling + -------------- end return self end @@ -1902,6 +2228,22 @@ function CTLD:_FindCratesCargoObject(Name) return nil end +--- (User) Add a new fixed wing type to the list of allowed types. +-- @param #CTLD self +-- @param #string typename The typename to add. Can be handed as Wrapper.Unit#UNIT object. Do NOT forget to `myctld:SetUnitCapabilities()` for this type! +-- @return #CTLD self +function CTLD:AddAllowedFixedWingType(typename) + if type(typename) == "string" then + self.FixedWingTypes[typename] = typename + elseif typename and typename.ClassName and typename:IsInstanceOf("UNIT") then + local TypeName = typename:GetTypeName() or "none" + self.FixedWingTypes[TypeName] = TypeName + else + self:E(self.lid.."No valid typename or no UNIT handed!") + end + return self +end + --- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object @@ -2081,7 +2423,8 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:_SendMessage("Troops boarded!", 10, false, Group) + self:_SendMessage(string.format("%s boarded!", cgoname), 10, false, Group) + self:_RefreshDropTroopsMenu(Group,Unit) self:__TroopsPickedUp(1,Group, Unit, Cargotype) self:_UpdateUnitCargoMass(Unit) Cargotype:RemoveStock() @@ -2091,7 +2434,7 @@ end function CTLD:_FindRepairNearby(Group, Unit, Repairtype) self:T(self.lid .. " _FindRepairNearby") - --self:I({Group:GetName(),Unit:GetName(),Repairtype}) + --self:T({Group:GetName(),Unit:GetName(),Repairtype}) local unitcoord = Unit:GetCoordinate() -- find nearest group of deployed groups @@ -2109,7 +2452,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) end end - --self:I("Distance: ".. nearestDistance) + --self:T("Distance: ".. nearestDistance) -- found one and matching distance? if nearestGroup == nil or nearestDistance > self.EngineerSearch then @@ -2143,7 +2486,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) -- walk through generics and find matching type local Cargotype = nil for k,v in pairs(self.Cargo_Crates) do - --self:I({groupname,v.Templates,Repairtype}) + --self:T({groupname,v.Templates,Repairtype}) if matchstring(groupname,v.Templates) and matchstring(groupname,Repairtype) then Cargotype = v -- #CTLD_CARGO break @@ -2153,7 +2496,7 @@ function CTLD:_FindRepairNearby(Group, Unit, Repairtype) if Cargotype == nil then return nil, nil else - --self:I({groupname,Cargotype}) + --self:T({groupname,Cargotype}) return nearestGroup, Cargotype end @@ -2214,6 +2557,7 @@ end -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) + local hassecondaries = false if not grounded and not hoverload then self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) @@ -2237,6 +2581,7 @@ end local nearestGroup = nil local nearestGroupIndex = -1 local nearestDistance = 10000000 + local maxdistance = 0 local nearestList = {} local distancekeys = {} local extractdistance = self.CrateDistance * self.ExtractFactor @@ -2248,8 +2593,14 @@ end nearestGroup = v nearestGroupIndex = k nearestDistance = distance + if math.floor(distance) > maxdistance then maxdistance = math.floor(distance) end + if nearestList[math.floor(distance)] then + distance = maxdistance+1 + maxdistance = distance + end table.insert(nearestList, math.floor(distance), v) distancekeys[#distancekeys+1] = math.floor(distance) + --self:I(string.format("Adding group %s distance %dm",nearestGroup:GetName(),distance)) end end @@ -2302,14 +2653,16 @@ end nearestGroup.ExtractTime = timer.getTime() local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, Cargotype.CargoType, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass) self:T({cargotype=loadcargotype}) - local running = math.floor(nearestDistance / 4)+10 -- time run to helo plus boarding + local running = math.floor(nearestDistance / 4)+20 -- time run to helo plus boarding loaded.Troopsloaded = loaded.Troopsloaded + troopsize table.insert(loaded.Cargo,loadcargotype) self.Loaded_Cargo[unitname] = loaded - self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) - self:_SendMessage("Troops boarding!", 10, false, Group) + self:ScheduleOnce(running, self._SendMessage, self, string.format("%s boarded!", Cargotype.Name), 10, false, Group) + self:_SendMessage(string.format("%s boarding!", Cargotype.Name), 10, false, Group) + self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) - self:__TroopsExtracted(running,Group, Unit, nearestGroup) + local groupname = nearestGroup:GetName() + self:__TroopsExtracted(running,Group, Unit, nearestGroup, groupname) local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE local Point if coord then @@ -2317,26 +2670,32 @@ end local Angle = math.floor((heading+160)%360) Point = coord:Translate(8,Angle):GetVec2() if Point then - nearestGroup:RouteToVec2(Point,4) + nearestGroup:RouteToVec2(Point,5) end end -- clean up: - if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then + hassecondaries = false + if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then for _,_key in pairs (Cargotype.Templates) do table.insert(secondarygroups,_key) + hassecondaries = true end end - nearestGroup:Destroy(false,running) + local destroytimer = math.random(10,20) + --self:I("Destroying Group "..nearestGroup:GetName().." in "..destroytimer.." seconds!") + nearestGroup:Destroy(false,destroytimer) end end end -- clean up secondary groups - for _,_name in pairs(secondarygroups) do - for _,_group in pairs(nearestList) do - if _group and _group:IsAlive() then - local groupname = string.match(_group:GetName(), "(.+)-(.+)$") - if _name == groupname then - _group:Destroy(false,15) + if hassecondaries == true then + for _,_name in pairs(secondarygroups) do + for _,_group in pairs(nearestList) do + if _group and _group:IsAlive() then + local groupname = string.match(_group:GetName(), "(.+)-(.+)$") + if _name == groupname then + _group:Destroy(false,15) + end end end end @@ -2411,13 +2770,15 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities local canloadcratesno = capabilities.cratelimit local loaddist = self.CrateDistance or 35 - local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true) + local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true) 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 - local IsHerc = self:IsHercules(Unit) -- Herc + local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind + local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind + local IsTruck = Unit:IsGround() local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO local number = number or cargotype:GetCratesNeeded() --#number local cratesneeded = cargotype:GetCratesNeeded() --#number @@ -2439,44 +2800,46 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local rheading = 0 local angleOffNose = 0 local addon = 0 - if IsHerc then + if IsHerc or IsHook or IsTruck then -- spawn behind the Herc addon = 180 end + heading = (heading+addon)%360 + local row = 1 + local column = 1 + local initialdist = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates + local startpos = position:Translate(initialdist,heading) + if self.placeCratesAhead == true then + cratedistance = initialdist + end -- loop crates needed + local cratecoord = nil -- Core.Point#COORDINATE for i=1,number do local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) - if not self.placeCratesAhead then + if not self.placeCratesAhead or drop == true then cratedistance = (i-1)*2.5 + capabilities.length if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end -- altered heading logic -- DONE: right standard deviation? rheading = UTILS.RandomGaussian(0,30,-90,90,100) - rheading = math.fmod((heading + rheading + addon), 360) + rheading = math.fmod((heading + rheading), 360) + cratecoord = position:Translate(cratedistance,rheading) else - local initialSpacing = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates - local crateSpacing = 4 -- further spacing of remaining crates - local lateralSpacing = 4 -- lateral spacing of crates - local nrSideBySideCrates = 3 -- number of crates that are placed side-by-side - - if cratesneeded == 1 then - -- single crate needed spawns straight ahead - cratedistance = initialSpacing - rheading = heading - else - if (i - 1) % nrSideBySideCrates == 0 then - cratedistance = i == 1 and initialSpacing or cratedistance + crateSpacing - angleOffNose = math.ceil(math.deg(math.atan(lateralSpacing / cratedistance))) - rheading = heading - angleOffNose - else - rheading = rheading + angleOffNose - end + cratedistance = (row-1)*6 + rheading = 90 + row = row+1 + cratecoord = startpos:Translate(cratedistance,rheading) + if row > 4 then + row = 1 + startpos:Translate(6,heading,nil,true) end end - local cratecoord = position:Translate(cratedistance,rheading) - local cratevec2 = cratecoord:GetVec2() + + --local cratevec2 = cratecoord:GetVec2() self.CrateCounter = self.CrateCounter + 1 - local basetype = self.basetype or "container_cargo" + local CCat, CType, CShape = Cargo:GetStaticTypeAndShape() + local basetype = CType or self.basetype or "container_cargo" + CCat = CCat or "Cargos" if isstatic then basetype = cratetemplate end @@ -2489,17 +2852,31 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) dist = dist - (20 + math.random(1,10)) local width = width / 2 local Offy = math.random(-width,width) - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) :InitLinkToUnit(Ship,dist,Offy,0) - :Spawn(270,cratealias) + if CShape then + spawnstatic:InitShape(CShape) + end + if isstatic then + local map=cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) else - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCoordinate(cratecoord) :InitCargoMass(cgomass) :InitCargo(self.enableslingload) - :Spawn(270,cratealias) + if CShape then + spawnstatic:InitShape(CShape) + end + if isstatic then + local map=cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) end local templ = cargotype:GetTemplates() local sorte = cargotype:GetType() @@ -2508,11 +2885,25 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) local realcargo = nil if drop then --CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) -- #CTLD_CARGO + local map=cargotype:GetStaticResourceMap() + realcargo:SetStaticResourceMap(map) + local CCat, CType, CShape = cargotype:GetStaticTypeAndShape() + realcargo:SetStaticTypeAndShape(CCat,CType,CShape) + if cargotype.TypeNames then + realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) + end table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) + local map=cargotype:GetStaticResourceMap() + realcargo:SetStaticResourceMap(map) + if cargotype.TypeNames then + realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames) + end end + local CCat, CType, CShape = cargotype:GetStaticTypeAndShape() + realcargo:SetStaticTypeAndShape(CCat,CType,CShape) table.insert(self.Spawned_Cargo, realcargo) end if not (drop or pack) then @@ -2523,7 +2914,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack) text = string.format("Crates for %s have been dropped!",cratename) self:__CratesDropped(1, Group, Unit, droppedcargo) end - self:_SendMessage(text, 10, false, Group) + self:_SendMessage(text, 10, false, Group) + self:_RefreshLoadCratesMenu(Group, Unit) + return self end @@ -2532,8 +2925,9 @@ end -- @param Core.Zone#ZONE Zone Zone to spawn in. -- @param #CTLD_CARGO Cargo The cargo type to spawn. -- @param #boolean RandomCoord Randomize coordinate. +-- @param #boolean FromLoad Create only **one** crate per cargo type, as we are re-creating dropped crates that CTLD has saved prior. -- @return #CTLD self -function CTLD:InjectStatics(Zone, Cargo, RandomCoord) +function CTLD:InjectStatics(Zone, Cargo, RandomCoord, FromLoad) self:T(self.lid .. " InjectStatics") local cratecoord = Zone:GetCoordinate() if RandomCoord then @@ -2550,27 +2944,39 @@ function CTLD:InjectStatics(Zone, Cargo, RandomCoord) local cratename = cargotype:GetName() local cgotype = cargotype:GetType() local cgomass = cargotype:GetMass() - local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) - local isstatic = false - if cgotype == CTLD_CARGO.Enum.STATIC then - cratetemplate = cargotype:GetTemplates() - isstatic = true + local cratenumber = cargotype:GetCratesNeeded() or 1 + if FromLoad == true then cratenumber=1 end + for i=1,cratenumber do + local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000)) + local isstatic = false + if cgotype == CTLD_CARGO.Enum.STATIC then + cratetemplate = cargotype:GetTemplates() + isstatic = true + end + local CCat,CType,CShape = cargotype:GetStaticTypeAndShape() + local basetype = CType or self.basetype or "container_cargo" + CCat = CCat or "Cargos" + if isstatic then + basetype = cratetemplate + end + self.CrateCounter = self.CrateCounter + 1 + local spawnstatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) + :InitCargoMass(cgomass) + :InitCargo(self.enableslingload) + :InitCoordinate(cratecoord) + if CShape then + spawnstatic:InitShape(CShape) + end + if isstatic then + local map = cargotype:GetStaticResourceMap() + spawnstatic.TemplateStaticUnit.resourcePayload = map + end + self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias) + local templ = cargotype:GetTemplates() + local sorte = cargotype:GetType() + cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] + table.insert(self.Spawned_Cargo, cargotype) end - local basetype = self.basetype or "container_cargo" - if isstatic then - basetype = cratetemplate - end - self.CrateCounter = self.CrateCounter + 1 - self.Spawned_Crates[self.CrateCounter] = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) - :InitCargoMass(cgomass) - :InitCargo(self.enableslingload) - :InitCoordinate(cratecoord) - :Spawn(270,cratealias) - local templ = cargotype:GetTemplates() - local sorte = cargotype:GetType() - self.CargoCounter = self.CargoCounter + 1 - cargotype.Positionable = self.Spawned_Crates[self.CrateCounter] - table.insert(self.Spawned_Cargo, cargotype) return self end @@ -2583,7 +2989,7 @@ end function CTLD:InjectStaticFromTemplate(Zone, Template, Mass) self:T(self.lid .. " InjectStaticFromTemplate") local cargotype = self:GetStaticsCargoFromTemplate(Template,Mass) -- #CTLD_CARGO - self:InjectStatics(Zone,cargotype,true) + self:InjectStatics(Zone,cargotype,true,true) return self end @@ -2595,8 +3001,8 @@ end function CTLD:_ListCratesNearby( _group, _unit) self:T(self.lid .. " _ListCratesNearby") local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table - if number > 0 then + local crates,number,loadedbygc,indexgc = self:_FindCratesNearby(_group,_unit, finddist,true,true) -- #table + if number > 0 or indexgc > 0 then local text = REPORT:New("Crates Found Nearby:") text:Add("------------------------------------------------------------") for _,_entry in pairs (crates) do @@ -2613,6 +3019,19 @@ function CTLD:_ListCratesNearby( _group, _unit) text:Add(" N O N E") end text:Add("------------------------------------------------------------") + if indexgc > 0 then + text:Add("Probably ground crew loadable (F8)") + for _,_entry in pairs (loadedbygc) do + local entry = _entry -- #CTLD_CARGO + local name = entry:GetName() --#string + local dropped = entry:WasDropped() + if dropped then + text:Add(string.format("Dropped crate for %s, %dkg",name, entry.PerCrateMass)) + else + text:Add(string.format("Crate for %s, %dkg",name, entry.PerCrateMass)) + end + end + end self:_SendMessage(text:Text(), 30, true, _group) else self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) @@ -2625,31 +3044,32 @@ end -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @return #CTLD self -function CTLD:_RemoveCratesNearby( _group, _unit) - self:T(self.lid .. " _RemoveCratesNearby") - local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(_group,_unit, finddist,true) -- #table - if number > 0 then - local text = REPORT:New("Removing Crates Found Nearby:") +function CTLD:_RemoveCratesNearby(_group, _unit) + self:T(self.lid.." _RemoveCratesNearby") + local finddist=self.CrateDistance or 35 + local crates,number=self:_FindCratesNearby(_group,_unit,finddist,true,true) + if number>0 then + local removedIDs={} + local text=REPORT:New("Removing Crates Found Nearby:") text:Add("------------------------------------------------------------") - for _,_entry in pairs (crates) do - local entry = _entry -- #CTLD_CARGO - local name = entry:GetName() --#string - local dropped = entry:WasDropped() - if dropped then - text:Add(string.format("Crate for %s, %dkg removed",name, entry.PerCrateMass)) - else - text:Add(string.format("Crate for %s, %dkg removed",name, entry.PerCrateMass)) + for _,_entry in pairs(crates)do + local entry=_entry + local name=entry:GetName()or"none" + text:Add(string.format("Crate for %s, %dkg removed",name,entry.PerCrateMass)) + if entry:GetPositionable()then + entry:GetPositionable():Destroy(false) end - entry:GetPositionable():Destroy(false) + table.insert(removedIDs,entry:GetID()) end - if text:GetCount() == 1 then - text:Add(" N O N E") + if text:GetCount()==1 then + text:Add(" N O N E") end text:Add("------------------------------------------------------------") - self:_SendMessage(text:Text(), 30, true, _group) + self:_SendMessage(text:Text(),30,true,_group) + self:_CleanupTrackedCrates(removedIDs) + self:_RefreshLoadCratesMenu(_group,_unit) else - self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist), 10, false, _group) + self:_SendMessage(string.format("No (loadable) crates within %d meters!",finddist),10,false,_group) end return self end @@ -2686,21 +3106,27 @@ end -- @param Wrapper.Unit#UNIT _unit Unit -- @param #number _dist Distance -- @param #boolean _ignoreweight Find everything in range, ignore loadable weight --- @return #table Table of crates +-- @param #boolean ignoretype Find everything in range, ignore loadable type name +-- @return #table Crates Table of crates -- @return #number Number Number of crates found -function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) +-- @return #table CratesGC Table of crates possibly loaded by GC +-- @return #number NumberGC Number of crates possibly loaded by GC +function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype) self:T(self.lid .. " _FindCratesNearby") local finddist = _dist local location = _group:GetCoordinate() local existingcrates = self.Spawned_Cargo -- #table -- cycle local index = 0 + local indexg = 0 local found = {} + local LoadedbyGC = {} local loadedmass = 0 local unittype = "none" local capabilities = {} - local maxmass = 2000 + --local maxmass = 2000 local maxloadable = 2000 + local IsHook = self:IsHook(_unit) if not _ignoreweight then maxloadable = self:_GetMaxLoadableMass(_unit) end @@ -2708,20 +3134,32 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight) for _,_cargoobject in pairs (existingcrates) do local cargo = _cargoobject -- #CTLD_CARGO local static = cargo:GetPositionable() -- Wrapper.Static#STATIC -- crates - local staticid = cargo:GetID() local weight = cargo:GetMass() -- weight in kgs of this cargo + local staticid = cargo:GetID() self:T(self.lid .. " Found cargo mass: " .. weight) - if static and static:IsAlive() then - local staticpos = static:GetCoordinate() + if static and static:IsAlive() then --or cargoalive) then + local restricthooktononstatics = self.enableChinookGCLoading and IsHook + self:T(self.lid .. " restricthooktononstatics: " .. tostring(restricthooktononstatics)) + local cargoisstatic = cargo:GetType() == CTLD_CARGO.Enum.STATIC and true or false + self:T(self.lid .. " Cargo is static: " .. tostring(cargoisstatic)) + local restricted = cargoisstatic and restricthooktononstatics + self:T(self.lid .. " Loading restricted: " .. tostring(restricted)) + local staticpos = static:GetCoordinate() --or dcsunitpos + local cando = cargo:UnitCanCarry(_unit) + if ignoretype == true then cando = true end + self:T(self.lid .. " Unit can carry: " .. tostring(cando)) + --- Testing local distance = self:_GetDistance(location,staticpos) - if distance <= finddist and static and (weight <= maxloadable or _ignoreweight) then + self:T(self.lid .. string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable)) + if distance <= finddist and (weight <= maxloadable or _ignoreweight) and restricted == false and cando == true then index = index + 1 table.insert(found, staticid, cargo) maxloadable = maxloadable - weight end + end end - return found, index + return found, index, LoadedbyGC, indexg end --- (Internal) Function to get and load nearby crates. @@ -2733,16 +3171,21 @@ 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 unit = Unit -- Wrapper.Unit#UNIT local unitname = unit:GetName() - -- see if this heli can load crates + -- see if this heli can load crates local unittype = unit:GetTypeName() local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities - --local capabilities = self.UnitTypeCapabilities[unittype] -- #CTLD.UnitTypeCapabilities local cancrates = capabilities.crates -- #boolean local cratelimit = capabilities.cratelimit -- #number local grounded = not self:IsUnitInAir(Unit) local canhoverload = self:CanHoverLoad(Unit) + + -- Door check + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + if not self.debug then return self end + end --- cases ------------------------------- -- Chopper can\'t do crates - bark & return -- Chopper can do crates - @@ -2750,76 +3193,98 @@ function CTLD:_LoadCratesNearby(Group, Unit) -- --> hover or land if not forcedhover ----------------------------------------- if not cancrates then - self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) elseif self.forcehoverload and not canhoverload then - self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) + self:_SendMessage("Hover over the crates to pick them up!", 10, false, Group) elseif not grounded and not canhoverload then - self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) + self:_SendMessage("Land or hover over the crates to pick them up!", 10, false, Group) else - -- have we loaded stuff already? + -- have we loaded stuff already? local numberonboard = 0 - local massonboard = 0 - local loaded = {} + local loaded = {} if self.Loaded_Cargo[unitname] then - loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo - numberonboard = loaded.Cratesloaded or 0 - massonboard = self:_GetUnitCargoMass(Unit) + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 else - loaded = {} -- #CTLD.LoadedCargo + loaded = {} loaded.Troopsloaded = 0 loaded.Cratesloaded = 0 loaded.Cargo = {} end + -- get nearby crates - local finddist = self.CrateDistance or 35 - local nearcrates,number = self:_FindCratesNearby(Group,Unit,finddist,false) -- #table + local finddist = self.CrateDistance or 35 + local nearcrates, number = self:_FindCratesNearby(Group,Unit,finddist,false,false) self:T(self.lid .. " Crates found: " .. number) + if number == 0 and self.hoverautoloading then - return self -- exit + return self elseif number == 0 then - self:_SendMessage("Sorry no loadable crates nearby or max cargo weight reached!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, no loadable crates nearby or max cargo weight reached!", 10, false, Group) + return self elseif numberonboard == cratelimit then - self:_SendMessage("Sorry no fully loaded!", 10, false, Group) - return self -- exit + self:_SendMessage("Sorry, we are fully loaded!", 10, false, Group) + return self else - -- go through crates and load local capacity = cratelimit - numberonboard local crateidsloaded = {} - local loops = 0 - while loaded.Cratesloaded < cratelimit and loops < number do - loops = loops + 1 - local crateind = 0 - -- get crate with largest index - for _ind,_crate in pairs (nearcrates) do - if self.allowcratepickupagain then - if _crate:GetID() > crateind and _crate.Positionable ~= nil then - crateind = _crate:GetID() - end + local crateMap = {} + + for _, cObj in pairs(nearcrates) do + if not cObj:HasMoved() or self.allowcratepickupagain then + local cName = cObj:GetName() or "Unknown" + crateMap[cName] = crateMap[cName] or {} + table.insert(crateMap[cName], cObj) + end + end + for cName, crateList in pairs(crateMap) do + if capacity <= 0 then break end + + table.sort(crateList, function(a, b) return a:GetID() > b:GetID() end) + local needed = crateList[1]:GetCratesNeeded() or 1 + local totalFound = #crateList + local loadedHere = 0 + + while loaded.Cratesloaded < cratelimit and loadedHere < totalFound do + loadedHere = loadedHere + 1 + local crate = crateList[loadedHere] + if crate and crate.Positionable then + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loaded.Cargo, crate) + table.insert(crateidsloaded, crate:GetID()) + -- destroy crate + crate:GetPositionable():Destroy(false) + crate.Positionable = nil else - if not _crate:HasMoved() and not _crate:WasDropped() and _crate:GetID() > crateind then - crateind = _crate:GetID() - end + loadedHere = loadedHere - 1 + break 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) - crate:SetWasDropped(false) - table.insert(loaded.Cargo, crate) - table.insert(crateidsloaded,crate:GetID()) - -- destroy crate - crate:GetPositionable():Destroy(false) - crate.Positionable = nil - self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) - table.remove(nearcrates,crate:GetID()) - self:__CratesPickedUp(1, Group, Unit, crate) + + capacity = cratelimit - loaded.Cratesloaded + if loadedHere > 0 then + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cName), 10, false, Group) + end end end self.Loaded_Cargo[unitname] = loaded - self:_UpdateUnitCargoMass(Unit) + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group, Unit) + self:_RefreshLoadCratesMenu(Group, Unit) -- clean up real world crates self:_CleanupTrackedCrates(crateidsloaded) end @@ -2827,7 +3292,11 @@ function CTLD:_LoadCratesNearby(Group, Unit) return self end + --- (Internal) Function to clean up tracked cargo crates +-- @param #CTLD self +-- @param #list crateIdsToRemove Table of IDs +-- @return self function CTLD:_CleanupTrackedCrates(crateIdsToRemove) local existingcrates = self.Spawned_Cargo -- #table local newexcrates = {} @@ -2872,9 +3341,13 @@ function CTLD:_GetUnitCargoMass(Unit) if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then loadedmass = loadedmass + (cargo.PerCrateMass * cargo:GetCratesNeeded()) end - if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and not cargo:WasDropped() then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then loadedmass = loadedmass + cargo.PerCrateMass end + if type == CTLD_CARGO.Enum.GCLOADABLE then + local mass = cargo:GetCargoWeight() + loadedmass = loadedmass+mass + end end end return loadedmass @@ -2920,6 +3393,9 @@ function CTLD:_ListCargo(Group, Unit) local loadedcargo = self.Loaded_Cargo[unitname] or {} -- #CTLD.LoadedCargo local loadedmass = self:_GetUnitCargoMass(Unit) -- #number local maxloadable = self:_GetMaxLoadableMass(Unit) + local finddist = self.CrateDistance or 35 + --local _,_,loadedgc,loadedno = self:_FindCratesNearby(Group,Unit,finddist,true) + if self.Loaded_Cargo[unitname] then local no_troops = loadedcargo.Troopsloaded or 0 local no_crates = loadedcargo.Cratesloaded or 0 @@ -2933,7 +3409,7 @@ function CTLD:_ListCargo(Group, Unit) local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Troop: %s size %d",cargo:GetName(),cargo:GetCratesNeeded())) + report:Add(string.format("Troop: %s size %d", cargo:GetName(), cargo:GetCratesNeeded())) end end if report:GetCount() == 4 then @@ -2942,27 +3418,51 @@ function CTLD:_ListCargo(Group, Unit) report:Add("------------------------------------------------------------") report:Add(" -- CRATES --") local cratecount = 0 - for _,_cargo in pairs(cargotable) do + local accumCrates = {} + for _,_cargo in pairs(cargotable or {}) do local cargo = _cargo -- #CTLD_CARGO local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) and (not cargo:WasDropped() or self.allowcratepickupagain) then - report:Add(string.format("Crate: %s size 1",cargo:GetName())) + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE) and (not cargo:WasDropped() or self.allowcratepickupagain) then + local cName = cargo:GetName() + local needed = cargo:GetCratesNeeded() or 1 + accumCrates[cName] = accumCrates[cName] or {count=0, needed=needed} + accumCrates[cName].count = accumCrates[cName].count + 1 + end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + report:Add(string.format("GC loaded Crate: %s size 1", cargo:GetName())) cratecount = cratecount + 1 end end + for cName, data in pairs(accumCrates) do + cratecount = cratecount + data.count + report:Add(string.format("Crate: %s %d/%d", cName, data.count, data.needed)) + end if cratecount == 0 then report:Add(" N O N E") end + --[[ + if loadedno > 0 then + report:Add("------------------------------------------------------------") + report:Add(" -- CRATES loaded via Ground Crew --") + for _,_cargo in pairs(loadedgc or {}) do + local cargo = _cargo -- #CTLD_CARGO + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS) then + report:Add(string.format("Crate: %s size 1",cargo:GetName())) + loadedmass = loadedmass + cargo:GetMass() + end + end + end + --]] report:Add("------------------------------------------------------------") report:Add("Total Mass: ".. loadedmass .. " kg. Loadable: "..maxloadable.." kg.") local text = report:Text() - self:_SendMessage(text, 30, true, Group) + self:_SendMessage(text, 30, true, Group) else - self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs",trooplimit,cratelimit,maxloadable), 10, false, Group) + self:_SendMessage(string.format("Nothing loaded!\nTroop limit: %d | Crate limit %d | Weight limit %d kgs", trooplimit, cratelimit, maxloadable), 10, false, Group) end return self end - --- (Internal) Function to list loaded cargo. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -3057,19 +3557,32 @@ function CTLD:_ListInventory(Group, Unit) return self end ---- (Internal) Function to check if a unit is a Hercules C-130. +--- (Internal) Function to check if a unit is an allowed fixed wing. -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit -- @return #boolean Outcome -function CTLD:IsHercules(Unit) - if Unit:GetTypeName() == "Hercules" or string.find(Unit:GetTypeName(),"Bronco") then +function CTLD:IsFixedWing(Unit) + local typename = Unit:GetTypeName() or "none" + for _,_name in pairs(self.FixedWingTypes or {}) do + if typename == _name or string.find(typename,_name,1,true) then + return true + end + end + return false +end + +--- (Internal) Function to check if a unit is a CH-47 +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit +-- @return #boolean Outcome +function CTLD:IsHook(Unit) + if Unit and string.find(Unit:GetTypeName(),"CH.47") then return true else return false end end - --- (Internal) Function to set troops positions of a template to a nice circle -- @param #CTLD self -- @param Core.Point#COORDINATE Coordinate Start coordinate to use @@ -3082,8 +3595,8 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) local template = _DATABASE:GetGroupTemplate(Template) --UTILS.PrintTableToLog(template) local numbertroops = #template.units - local slightshift = math.abs(math.random(0,200)/100) - local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270)%360)) + local slightshift = math.abs(math.random(1,500)/100) + local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270+math.random(1,10))%360)) for i=1,360,math.floor(360/numbertroops) do local phead = ((Heading+270+i)%360) local post = newcenter:Translate(Radius,phead) @@ -3121,8 +3634,9 @@ 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 + local IsHerc = self:IsFixedWing(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end @@ -3157,21 +3671,30 @@ function CTLD:_UnloadTroops(Group, Unit) randomcoord = Group:GetCoordinate() -- slightly left from us local Angle = (heading+270)%360 - local offset = hoverunload and 1.5 or 5 + if IsHerc or IsHook then Angle = (heading+180)%360 end + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if IsHerc then offset = self.TroopUnloadDistGroundHerc or 25 end + if IsHook then + offset = self.TroopUnloadDistGroundHook or 15 + if hoverunload and self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 + end + end randomcoord:Translate(offset,Angle,nil,true) end local tempcount = 0 + local ishook = self:IsHook(Unit) + if ishook then tempcount = self.ChinookTroopCircleRadius or 5 end -- 10m circle for the Chinook for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 tempcount = tempcount+1 local alias = string.format("%s-%d", _template, math.random(1,100000)) - local rad = 2.5+tempcount + local rad = 2.5+(tempcount*2) local Positions = self:_GetUnitPositions(randomcoord,rad,heading,_template) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) - --:InitRandomizeUnits(true,20,2) - --:InitHeading(heading) :InitDelayOff() :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord:GetVec2()) self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter],type) end -- template loop @@ -3189,7 +3712,7 @@ function CTLD:_UnloadTroops(Group, Unit) end -- cargotable loop else -- droppingatbase self:_SendMessage("Troops have returned to base!", 10, false, Group) - self:__TroopsRTB(1, Group, Unit) + self:__TroopsRTB(1, Group, Unit, zonename, zone) end -- cleanup load list local loaded = {} -- #CTLD.LoadedCargo @@ -3223,6 +3746,7 @@ function CTLD:_UnloadTroops(Group, Unit) end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded + self:_RefreshDropTroopsMenu(Group,Unit) self:_UpdateUnitCargoMass(Unit) else if IsHerc then @@ -3251,10 +3775,16 @@ function CTLD:_UnloadCrates(Group, Unit) end end end + -- Door check + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, 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 - local IsHerc = self:IsHercules(Unit) - if IsHerc then + local IsHerc = self:IsFixedWing(Unit) + local IsHook = self:IsHook(Unit) + if IsHerc and (not IsHook) then -- no hover but airdrop here hoverunload = self:IsCorrectFlightParameters(Unit) end @@ -3269,7 +3799,7 @@ function CTLD:_UnloadCrates(Group, Unit) 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 type ~= CTLD_CARGO.Enum.ENGINEERS and (not cargo:WasDropped() or self.allowcratepickupagain) then + if type ~= CTLD_CARGO.Enum.TROOPS and type ~= CTLD_CARGO.Enum.ENGINEERS and type ~= CTLD_CARGO.Enum.GCLOADABLE and (not cargo:WasDropped() or self.allowcratepickupagain) then -- unload crates self:_GetCrates(Group, Unit, cargo, 1, true) cargo:SetWasDropped(true) @@ -3290,11 +3820,16 @@ function CTLD:_UnloadCrates(Group, Unit) table.insert(loaded.Cargo,_cargo) loaded.Troopsloaded = loaded.Troopsloaded + size end + if type == CTLD_CARGO.Enum.GCLOADABLE and not cargo:WasDropped() then + table.insert(loaded.Cargo,_cargo) + loaded.Cratesloaded = loaded.Cratesloaded + size + end end self.Loaded_Cargo[unitname] = nil self.Loaded_Cargo[unitname] = loaded self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group,Unit) else if IsHerc then self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) @@ -3313,7 +3848,7 @@ end function CTLD:_BuildCrates(Group, Unit,Engineering) self:T(self.lid .. " _BuildCrates") -- avoid users trying to build from flying Hercs - if self:IsHercules(Unit) and self.enableHercules and not Engineering then + if self:IsFixedWing(Unit) and self.enableFixedWing and not Engineering then local speed = Unit:GetVelocityKMH() if speed > 1 then self:_SendMessage("You need to land / stop to build something, Pilot!", 10, false, Group) @@ -3330,7 +3865,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) end -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -3447,6 +3982,7 @@ function CTLD:_PackCratesNearby(Group, Unit) if (_entry.Templates[1] == _Template.GroupName) then -- check if the #CTLD_CARGO matches the template name _Group:Destroy() -- if a match is found destroy the Wrapper.Group#GROUP near the player self:_GetCrates(Group, Unit, _entry, nil, false, true) -- spawn the appropriate crates near the player + self:_RefreshLoadCratesMenu(Group,Unit) -- call the refresher to show the crates in the menu return self end end @@ -3465,7 +4001,7 @@ function CTLD:_RepairCrates(Group, Unit, Engineering) self:T(self.lid .. " _RepairCrates") -- get nearby crates local finddist = self.CrateDistance or 35 - local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true) -- #table + local crates,number = self:_FindCratesNearby(Group,Unit,finddist,true,true) -- #table local buildables = {} local foundbuilds = false local canbuild = false @@ -3582,10 +4118,12 @@ function CTLD:_BuildObjectFromCrates(Group,Unit,Build,Repair,RepairLocation) self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) --:InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) :SpawnFromVec2(randomcoord) end if Repair then @@ -3609,6 +4147,7 @@ function CTLD:_MoveGroupToZone(Group) local groupcoord = Group:GetCoordinate() -- Get closest zone of type local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) + self:T({canmove=outcome, name=name, zone=zone, dist=distance,max=self.movetroopsdistance}) if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then -- yes, we can ;) local groupname = Group:GetName() @@ -3665,26 +4204,39 @@ end -- @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 + + -- 1) Gather all the pilot groups from our Set + local PlayerSet = self.PilotGroups + local PlayerTable = PlayerSet:GetSetObjects() + + -- 2) Rebuild the self.CtldUnits table local _UnitList = {} - for _key, _group in pairs (PlayerTable) do - 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() and _unit:IsPlayer() then - if _unit:IsHelicopter() or (self:IsHercules(_unit) and self.enableHercules) then --ensure no stupid unit entries here - local unitName = _unit:GetName() - _UnitList[unitName] = unitName - else - local unitName = _unit:GetName() - _UnitList[unitName] = nil - end - end -- end isAlive - end -- end if _unit - end -- end for - self.CtldUnits = _UnitList + for _, groupObj in pairs(PlayerTable) do + local firstUnit = groupObj:GetFirstUnitAlive() + if firstUnit then + if firstUnit:IsPlayer() then + if firstUnit:IsHelicopter() or (self.enableFixedWing and self:IsFixedWing(firstUnit)) then + local _unit = firstUnit:GetName() + _UnitList[_unit] = _unit + end + end + end + end + -- 3) CA Units + if self.allowCATransport and self.CATransportSet then + for _,_clientobj in pairs(self.CATransportSet.Set) do + local client = _clientobj -- Wrapper.Client#CLIENT + if client:IsGround() then + local cname = client:GetName() + self:T(self.lid.."Adding: "..cname) + _UnitList[cname] = cname + end + end + end + + self.CtldUnits = _UnitList + -- subcats? if self.usesubcats then for _id,_cargo in pairs(self.Cargo_Crates) do @@ -3706,168 +4258,788 @@ function CTLD:_RefreshF10Menus() end end end - - -- build unit menus + local menucount = 0 - local menus = {} + 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 (not self.MenusDone[_unitName]) or (self.showstockinmenuitems == true) then + self:T(self.lid.."Menu not done yet for ".._unitName) + local _unit = UNIT:FindByName(_unitName) + if not _unit and self.allowCATransport then + _unit = CLIENT:FindByName(_unitName) + end + if _unit and _unit:IsAlive() then + local _group = _unit:GetGroup() if _group then - -- get chopper capabilities - local unittype = _unit:GetTypeName() - local capabilities = self:_GetUnitCapabilities(_unit) -- #CTLD.UnitTypeCapabilities - local cantroops = capabilities.troops - local cancrates = capabilities.crates - -- top menu - local topmenu = MENU_GROUP:New(_group,"CTLD",nil) + self:T(self.lid.."Unit and Group exist") + local capabilities = self:_GetUnitCapabilities(_unit) + local cantroops = capabilities.troops + local cancrates = capabilities.crates + local unittype = _unit:GetTypeName() + local isHook = self:IsHook(_unit) + local nohookswitch = true + --local nohookswitch = not (isHook and self.enableChinookGCLoading) + -- Clear old topmenu if it existed + if _group.CTLDTopmenu then + _group.CTLDTopmenu:Remove() + _group.CTLDTopmenu = nil + end local toptroops = nil local topcrates = nil + local topmenu = MENU_GROUP:New(_group, "CTLD", nil) + _group.CTLDTopmenu = topmenu + if cantroops then - toptroops = MENU_GROUP:New(_group,"Manage Troops",topmenu) - end - if cancrates then - topcrates = MENU_GROUP:New(_group,"Manage Crates",topmenu) - end - local listmenu = MENU_GROUP_COMMAND:New(_group,"List boarded cargo",topmenu, self._ListCargo, self, _group, _unit) - local invtry = MENU_GROUP_COMMAND:New(_group,"Inventory",topmenu, self._ListInventory, self, _group, _unit) - local rbcns = MENU_GROUP_COMMAND:New(_group,"List active zone beacons",topmenu, self._ListRadioBeacons, self, _group, _unit) - local smoketopmenu = MENU_GROUP:New(_group,"Smokes, Flares, Beacons",topmenu) - local smokemenu = MENU_GROUP_COMMAND:New(_group,"Smoke zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) - local smokeself = MENU_GROUP:New(_group,"Drop smoke now",smoketopmenu) - local smokeselfred = MENU_GROUP_COMMAND:New(_group,"Red smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Red) - local smokeselfblue = MENU_GROUP_COMMAND:New(_group,"Blue smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Blue) - local smokeselfgreen = MENU_GROUP_COMMAND:New(_group,"Green smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Green) - local smokeselforange = MENU_GROUP_COMMAND:New(_group,"Orange smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.Orange) - local smokeselfwhite = MENU_GROUP_COMMAND:New(_group,"White smoke",smokeself, self.SmokePositionNow, self, _unit, false,SMOKECOLOR.White) - local flaremenu = MENU_GROUP_COMMAND:New(_group,"Flare zones nearby",smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) - local flareself = MENU_GROUP_COMMAND:New(_group,"Fire flare now",smoketopmenu, self.SmokePositionNow, self, _unit, true) - local beaconself = MENU_GROUP_COMMAND:New(_group,"Drop beacon now",smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() - -- sub menus - -- sub menu troops management - if cantroops then - local troopsmenu = MENU_GROUP:New(_group,"Load troops",toptroops) - if self.usesubcats then - local subcatmenus = {} - for _name,_entry in pairs(self.subcatsTroop) do - subcatmenus[_name] = MENU_GROUP:New(_group,_name,troopsmenu) - end - for _,_entry in pairs(self.Cargo_Troops) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - if not noshow then - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) - end - end - else - for _,_entry in pairs(self.Cargo_Troops) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - if not noshow then - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) - end - end - 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 - -- 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) - local packmenu = MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) - local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + local toptroops = MENU_GROUP:New(_group, "Manage Troops", topmenu) + local troopsmenu = MENU_GROUP:New(_group, "Load troops", toptroops) + _group.MyTopTroopsMenu = toptroops if self.usesubcats then local subcatmenus = {} - for _name,_entry in pairs(self.subcats) do - subcatmenus[_name] = MENU_GROUP:New(_group,_name,cratesmenu) + for catName, _ in pairs(self.subcatsTroop) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, troopsmenu) end - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - local zone = entry.Location - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) - end - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - local subcat = entry.Subcategory - local noshow = entry.DontShowInMenu - local zone = entry.Location - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, subcatmenus[cargoObj.Subcategory], self._LoadTroops, self, _group, _unit, cargoObj) end end else - for _,_entry in pairs(self.Cargo_Crates) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - local zone = entry.Location - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) - end - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - local noshow = entry.DontShowInMenu - local zone = entry.Location - if not noshow then - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - if zone then - menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0) - end - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + for _, cargoObj in pairs(self.Cargo_Troops) do + if not cargoObj.DontShowInMenu then + local stock = cargoObj:GetStock() + local menutext = cargoObj.Name + if (stock >= 0) and (self.showstockinmenuitems == true) then menutext = menutext.." ["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, menutext, troopsmenu, self._LoadTroops, self, _group, _unit, cargoObj) + end end end - listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) - local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) - local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) + local dropTroopsMenu=MENU_GROUP:New(_group,"Drop Troops",toptroops):Refresh() + MENU_GROUP_COMMAND:New(_group,"Drop ALL troops",dropTroopsMenu,self._UnloadTroops,self,_group,_unit):Refresh() + MENU_GROUP_COMMAND:New(_group,"Extract troops",toptroops,self._ExtractTroops,self,_group,_unit):Refresh() + local uName=_unit:GetName() + local loadedData=self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + for i,cargoObj in ipairs(loadedData.Cargo) do + if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then + local name=cargoObj:GetName() or "Unknown" + local needed=cargoObj:GetCratesNeeded() or 1 + local cID=cargoObj:GetID() + local line=string.format("Drop: %s",name,needed,cID) + MENU_GROUP_COMMAND:New(_group,line,dropTroopsMenu,self._UnloadSingleTroopByID,self,_group,_unit,cID):Refresh() + end + end + end + end + if cancrates then + local topcrates = MENU_GROUP:New(_group, "Manage Crates", topmenu) + _group.MyTopCratesMenu = topcrates + + -- Build the “Get Crates” sub-menu items + local cratesmenu = MENU_GROUP:New(_group, "Get Crates", topcrates) + if self.usesubcats then + local subcatmenus = {} + for catName, _ in pairs(self.subcats) do + subcatmenus[catName] = MENU_GROUP:New(_group, catName, cratesmenu) + end + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, subcatmenus[cargoObj.Subcategory], self._GetCrates, self, _group, _unit, cargoObj) + end + end + else + for _, cargoObj in pairs(self.Cargo_Crates) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + for _, cargoObj in pairs(self.Cargo_Statics) do + if not cargoObj.DontShowInMenu then + local txt = string.format("Crate %s (%dkg)", cargoObj.Name, cargoObj.PerCrateMass or 0) + if cargoObj.Location then txt = txt.."[R]" end + local stock = cargoObj:GetStock() + if stock >= 0 and self.showstockinmenuitems then txt = txt.."["..stock.."]" end + MENU_GROUP_COMMAND:New(_group, txt, cratesmenu, self._GetCrates, self, _group, _unit, cargoObj) + end + end + end + + local loadCratesMenu=MENU_GROUP:New(_group,"Load Crates",topcrates) + _group.MyLoadCratesMenu=loadCratesMenu + MENU_GROUP_COMMAND:New(_group,"Load ALL",loadCratesMenu,self._LoadCratesNearby,self,_group,_unit) + MENU_GROUP_COMMAND:New(_group,"Show loadable crates",loadCratesMenu,self._RefreshLoadCratesMenu,self,_group,_unit) + + local dropCratesMenu=MENU_GROUP:New(_group,"Drop Crates",topcrates) + topcrates.DropCratesMenu=dropCratesMenu + if not self.nobuildmenu then - 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() - else - unloadmenu:Refresh() + MENU_GROUP_COMMAND:New(_group, "Build crates", topcrates, self._BuildCrates, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Repair", topcrates, self._RepairCrates, self, _group, _unit):Refresh() + end + + local removecratesmenu = MENU_GROUP:New(_group, "Remove crates", topcrates) + MENU_GROUP_COMMAND:New(_group, "Remove crates nearby", removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + + MENU_GROUP_COMMAND:New(_group, "Pack crates", topcrates, self._PackCratesNearby, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List crates nearby", topcrates, self._ListCratesNearby, self, _group, _unit) + + local uName = _unit:GetName() + local loadedData = self.Loaded_Cargo[uName] + if loadedData and loadedData.Cargo then + local cargoByName = {} + for _, cgo in pairs(loadedData.Cargo) do + if cgo and (not cgo:WasDropped()) then + local cname = cgo:GetName() + local cneeded = cgo:GetCratesNeeded() + cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded } + cargoByName[cname].count = cargoByName[cname].count + 1 + end + end + for name, info in pairs(cargoByName) do + local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed) + MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name) + end end end - if self:IsHercules(_unit) then - local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() + + + + ----------------------------------------------------- + -- Misc sub‐menus + ----------------------------------------------------- + MENU_GROUP_COMMAND:New(_group, "List boarded cargo", topmenu, self._ListCargo, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "Inventory", topmenu, self._ListInventory, self, _group, _unit) + MENU_GROUP_COMMAND:New(_group, "List active zone beacons", topmenu, self._ListRadioBeacons, self, _group, _unit) + + local smoketopmenu = MENU_GROUP:New(_group, "Smokes, Flares, Beacons", topmenu) + MENU_GROUP_COMMAND:New(_group, "Smoke zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, false) + local smokeself = MENU_GROUP:New(_group, "Drop smoke now", smoketopmenu) + MENU_GROUP_COMMAND:New(_group, "Red smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Red) + MENU_GROUP_COMMAND:New(_group, "Blue smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Blue) + MENU_GROUP_COMMAND:New(_group, "Green smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Green) + MENU_GROUP_COMMAND:New(_group, "Orange smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.Orange) + MENU_GROUP_COMMAND:New(_group, "White smoke", smokeself, self.SmokePositionNow, self, _unit, false, SMOKECOLOR.White) + + MENU_GROUP_COMMAND:New(_group, "Flare zones nearby", smoketopmenu, self.SmokeZoneNearBy, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Fire flare now", smoketopmenu, self.SmokePositionNow, self, _unit, true) + MENU_GROUP_COMMAND:New(_group, "Drop beacon now", smoketopmenu, self.DropBeaconNow, self, _unit):Refresh() + + if self:IsFixedWing(_unit) then + 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() + MENU_GROUP_COMMAND:New(_group, "Show hover parameters", topmenu, self._ShowHoverParams, self, _group, _unit):Refresh() end + + -- Mark we built the menu self.MenusDone[_unitName] = true - end -- end group - end -- end unit - else -- menu build check + self:_RefreshLoadCratesMenu(_group, _unit) + self:_RefreshDropCratesMenu(_group,_unit) + + end -- if _group + end -- if _unit + else self:T(self.lid .. " Menus already done for this group!") - end -- end menu build check - end -- end for + end + end -- for all pilot units + return self - end +end + +--- (Internal) Function to refresh the menu for load crates. Triggered from land/getcrate/pack and more +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @return #CTLD self +function CTLD:_RefreshLoadCratesMenu(Group, Unit) + if not Group.MyLoadCratesMenu then return end + Group.MyLoadCratesMenu:RemoveSubMenus() + + local d = self.CrateDistance or 35 + local nearby, n = self:_FindCratesNearby(Group, Unit, d, true, true) + if n == 0 then + MENU_GROUP_COMMAND:New(Group, "No crates found! Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + return + end + MENU_GROUP_COMMAND:New(Group, "Load ALL", Group.MyLoadCratesMenu, self._LoadCratesNearby, self, Group, Unit) + local cargoByName = {} + for _, crate in pairs(nearby) do + local cName = crate:GetName() + cargoByName[cName] = cargoByName[cName] or {} + table.insert(cargoByName[cName], crate) + end + + for cName, cList in pairs(cargoByName) do + local needed = cList[1]:GetCratesNeeded() or 1 + local found = #cList + + local line + if found >= needed then + line = string.format("Load %s", cName) + else + MENU_GROUP_COMMAND:New(Group, "Rescan?", Group.MyLoadCratesMenu, function() self:_RefreshLoadCratesMenu(Group, Unit) end) + line = string.format("Load %s (%d/%d)", cName, found, needed) + end + MENU_GROUP_COMMAND:New(Group, line, Group.MyLoadCratesMenu, self._LoadSingleCrateSet, self, Group, Unit, cName) + end +end + +--- +-- Loads exactly `CratesNeeded` crates for one cargoName in range. +-- If "Ammo Truck" needs 2 crates, we pick up 2 if available. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group +-- @param Wrapper.Unit#UNIT Unit +-- @param #string cargoName The cargo name, e.g. "Ammo Truck" +function CTLD:_LoadSingleCrateSet(Group, Unit, cargoName) + self:T(self.lid .. " _LoadSingleCrateSet cargoName=" .. (cargoName or "nil")) + + -- 1) Must be landed or hovering + local grounded = not self:IsUnitInAir(Unit) + local hover = self:CanHoverLoad(Unit) + if not grounded and not hover then + self:_SendMessage("You must land or hover to load crates!", 10, false, Group) + return self + end + + -- 2) Check door if required + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load cargo!", 10, false, Group) + return self + end + + -- 3) Find crates with `cargoName` in range + local finddist = self.CrateDistance or 35 + local cratesNearby, number = self:_FindCratesNearby(Group, Unit, finddist, false, false) + if number == 0 then + self:_SendMessage("No crates found in range!", 10, false, Group) + return self + end + + local matchingCrates = {} + local needed = nil + for _, crateObj in pairs(cratesNearby) do + if crateObj:GetName() == cargoName then + needed = needed or crateObj:GetCratesNeeded() + table.insert(matchingCrates, crateObj) + end + end + if not needed then + self:_SendMessage(string.format("No \"%s\" crates found in range!", cargoName), 10, false, Group) + return self + end + + local found = #matchingCrates + + -- 4) Check capacity + local unitName = Unit:GetName() + local loadedData = self.Loaded_Cargo[unitName] or { Troopsloaded=0, Cratesloaded=0, Cargo={} } + local capabilities = self:_GetUnitCapabilities(Unit) + local capacity = capabilities.cratelimit or 0 + if loadedData.Cratesloaded >= capacity then + self:_SendMessage("No more capacity to load crates!", 10, false, Group) + return self + end + + -- decide how many we can actually load + local spaceLeft = capacity - loadedData.Cratesloaded + local toLoad = math.min(found, needed, spaceLeft) + if toLoad < 1 then + self:_SendMessage("Cannot load crates: either none found or no capacity left.", 10, false, Group) + return self + end + + -- 5) Load exactly `toLoad` crates + local crateIDsLoaded = {} + for i = 1, toLoad do + local crate = matchingCrates[i] + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loadedData.Cargo, crate) + loadedData.Cratesloaded = loadedData.Cratesloaded + 1 + local stObj = crate:GetPositionable() + if stObj and stObj:IsAlive() then + stObj:Destroy(false) + end + table.insert(crateIDsLoaded, crate:GetID()) + end + self.Loaded_Cargo[unitName] = loadedData + self:_UpdateUnitCargoMass(Unit) + + -- 6) Remove them from self.Spawned_Cargo + local newSpawned = {} + for _, cObj in ipairs(self.Spawned_Cargo) do + local keep = true + for i=1, toLoad do + if matchingCrates[i] and cObj:GetID() == matchingCrates[i]:GetID() then + keep = false + break + end + end + if keep then + table.insert(newSpawned, cObj) + end + end + self.Spawned_Cargo = newSpawned + + -- 7) Show final message, including a special note if capacity is now reached + local loadedHere = toLoad + if loadedHere < needed and loadedData.Cratesloaded >= capacity then + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s. Cargo limit is now reached!", loadedHere, needed, cargoName), 10, false, Group) + else + local fullSets = math.floor(loadedHere / needed) + local leftover = loadedHere % needed + if needed > 1 then + if fullSets > 0 and leftover == 0 then + self:_SendMessage(string.format("Loaded %d %s.", fullSets, cargoName), 10, false, Group) + elseif fullSets > 0 and leftover > 0 then + self:_SendMessage(string.format("Loaded %d %s(s), with %d leftover crate(s).", fullSets, cargoName, leftover), 10, false, Group) + else + self:_SendMessage(string.format("Loaded only %d/%d crate(s) of %s.", loadedHere, needed, cargoName), 15, false, Group) + end + else + self:_SendMessage(string.format("Loaded %d %s(s).", loadedHere, cargoName), 10, false, Group) + end + end + + self:_RefreshLoadCratesMenu(Group, Unit) + self:_RefreshDropCratesMenu(Group, Unit) + return self +end + + +--- (Internal) Function to unload a single crate +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @param #string setIndex The name of the crate to unload +-- @return #CTLD self +function CTLD:_UnloadSingleCrateSet(Group, Unit, setIndex) + self:T(self.lid .. " _UnloadSingleCrateSet") + + -- Check if we are in a drop zone (unless we drop anywhere) + if not self.dropcratesanywhere then + 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) + if not self.debug then + return self + end + end + end + + -- Check if doors must be open + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to drop cargo!", 10, false, Group) + if not self.debug then return self end + end + + -- Check if the crate grouping data is available + local unitName = Unit:GetName() + if not self.CrateGroupList or not self.CrateGroupList[unitName] then + self:_SendMessage("No crate groups found for this unit!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Find the selected chunk/set by index + local chunk = self.CrateGroupList[unitName][setIndex] + if not chunk then + self:_SendMessage("No crate set found or index invalid!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check if the chunk is empty + if #chunk == 0 then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Check hover/airdrop/landed logic + local grounded = not self:IsUnitInAir(Unit) + local hoverunload = self:IsCorrectHover(Unit) + local isHerc = self:IsFixedWing(Unit) + local isHook = self:IsHook(Unit) + if isHerc and not isHook then + hoverunload = self:IsCorrectFlightParameters(Unit) + end + if not grounded and not hoverunload then + if isHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + if not self.debug then return self end + return self + end + + -- Get the first crate from this set + local crateObj = chunk[1] + if not crateObj then + self:_SendMessage("No crate found in that set!", 10, false, Group) + if not self.debug then return self end + return self + end + + -- Perform the actual "drop" spawn + local needed = crateObj:GetCratesNeeded() or 1 + self:_GetCrates(Group, Unit, crateObj, #chunk, true) + + -- Mark all crates in the chunk as dropped + for _, cObj in ipairs(chunk) do + cObj:SetWasDropped(true) + cObj:SetHasMoved(true) + end + + -- Rebuild the cargo list to remove the dropped crates + local loadedData = self.Loaded_Cargo[unitName] + if loadedData and loadedData.Cargo then + local newList = {} + local newCratesCount = 0 + for _, cObj in ipairs(loadedData.Cargo) do + if not cObj:WasDropped() then + table.insert(newList, cObj) + local ct = cObj:GetType() + if ct ~= CTLD_CARGO.Enum.TROOPS and ct ~= CTLD_CARGO.Enum.ENGINEERS then + newCratesCount = newCratesCount + 1 + end + end + end + loadedData.Cargo = newList + loadedData.Cratesloaded = newCratesCount + self.Loaded_Cargo[unitName] = loadedData + end + + -- Update cargo mass, refresh menu + self:_UpdateUnitCargoMass(Unit) + self:_RefreshDropCratesMenu(Group, Unit) + self:_RefreshLoadCratesMenu(Group, Unit) + return self +end + +--- (Internal) Function to refresh the menu for a single unit after crates dropped. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @return #CTLD self +function CTLD:_RefreshDropCratesMenu(Group, Unit) + if not Group.CTLDTopmenu then return end + local topCrates = Group.MyTopCratesMenu + if not topCrates then return end + if topCrates.DropCratesMenu then + topCrates.DropCratesMenu:RemoveSubMenus() + else + topCrates.DropCratesMenu = MENU_GROUP:New(Group, "Drop Crates", topCrates) + end + + local dropCratesMenu = topCrates.DropCratesMenu + local loadedData = self.Loaded_Cargo[Unit:GetName()] + if not loadedData or not loadedData.Cargo then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + local cargoByName={} + local dropableCrates=0 + for _,cObj in ipairs(loadedData.Cargo) do + if cObj and not cObj:WasDropped() then + local cType=cObj:GetType() + if cType~=CTLD_CARGO.Enum.TROOPS and cType~=CTLD_CARGO.Enum.ENGINEERS and cType~=CTLD_CARGO.Enum.GCLOADABLE then + local name=cObj:GetName()or"Unknown" + cargoByName[name]=cargoByName[name]or{} + table.insert(cargoByName[name],cObj) + dropableCrates=dropableCrates+1 + end + end + end + + if dropableCrates==0 then + MENU_GROUP_COMMAND:New(Group,"No crates to drop!",dropCratesMenu,function() end) + return + end + + MENU_GROUP_COMMAND:New(Group,"Drop ALL crates",dropCratesMenu,self._UnloadCrates,self,Group,Unit) + self.CrateGroupList=self.CrateGroupList or{} + self.CrateGroupList[Unit:GetName()]={} + + local lineIndex=1 + for cName,list in pairs(cargoByName) do + local needed=list[1]:GetCratesNeeded() or 1 + table.sort(list,function(a,b)return a:GetID()=needed then + local chunk={} + for n=i,i+needed-1 do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s",lineIndex,cName) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=i+needed + else + local chunk={} + for n=i,#list do + table.insert(chunk,list[n]) + end + local label=string.format("%d. %s %d/%d",lineIndex,cName,left,needed) + table.insert(self.CrateGroupList[Unit:GetName()],chunk) + local setIndex=#self.CrateGroupList[Unit:GetName()] + MENU_GROUP_COMMAND:New(Group,label,dropCratesMenu,self._UnloadSingleCrateSet,self,Group,Unit,setIndex) + i=#list+1 + end + lineIndex=lineIndex+1 + end + end +end + +--- (Internal) Function to unload a single Troop group by ID. +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The calling group. +-- @param Wrapper.Unit#UNIT Unit The calling unit. +-- @param #number chunkID the Cargo ID +-- @return #CTLD self +function CTLD:_UnloadSingleTroopByID(Group, Unit, chunkID) + self:T(self.lid .. " _UnloadSingleTroopByID chunkID=" .. tostring(chunkID)) + + local droppingatbase = false + local inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD) + if not inzone then + inzone, zonename, zone, distance = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP) + end + if inzone then + droppingatbase = true + end + + if self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to unload troops!", 10, false, Group) + if not self.debug then return self end + end + + local hoverunload = self:IsCorrectHover(Unit) + local isHerc = self:IsFixedWing(Unit) + local isHook = self:IsHook(Unit) + if isHerc and not isHook then + hoverunload = self:IsCorrectFlightParameters(Unit) + end + local grounded = not self:IsUnitInAir(Unit) + local unitName = Unit:GetName() + + if self.Loaded_Cargo[unitName] and (grounded or hoverunload) then + if not droppingatbase or self.debug then + if not self.TroopsIDToChunk or not self.TroopsIDToChunk[chunkID] then + self:_SendMessage(string.format("No troop cargo chunk found for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + local chunk = self.TroopsIDToChunk[chunkID] + if not chunk or #chunk == 0 then + self:_SendMessage(string.format("Troop chunk is empty for ID %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + -- Drop ONLY the FIRST cargo in that chunk + local foundCargo = chunk[1] + if not foundCargo then + self:_SendMessage(string.format("No troop cargo at chunk %d!", chunkID), 10, false, Group) + if not self.debug then return self end + return self + end + + local cType = foundCargo:GetType() + local name = foundCargo:GetName() or "none" + local tmpl = foundCargo:GetTemplates() or {} + local zoneradius = self.troopdropzoneradius or 100 + local factor = 1 + if isHerc then + factor = foundCargo:GetCratesNeeded() or 1 + 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) + local heading = Group:GetHeading() or 0 + + if grounded or hoverunload then + randomcoord = Group:GetCoordinate() + local Angle = (heading + 270) % 360 + if isHerc or isHook then + Angle = (heading + 180) % 360 + end + local offset = hoverunload and self.TroopUnloadDistHover or self.TroopUnloadDistGround + if isHerc then + offset = self.TroopUnloadDistGroundHerc or 25 + end + if isHook then + offset = self.TroopUnloadDistGroundHook or 15 + if hoverunload and self.TroopUnloadDistHoverHook then + offset = self.TroopUnloadDistHoverHook or 5 + end + end + randomcoord:Translate(offset, Angle, nil, true) + end + + local tempcount = 0 + if isHook then + tempcount = self.ChinookTroopCircleRadius or 5 + end + for _, _template in pairs(tmpl) do + self.TroopCounter = self.TroopCounter + 1 + tempcount = tempcount + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + local rad = 2.5 + (tempcount * 2) + local Positions = self:_GetUnitPositions(randomcoord, rad, heading, _template) + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template, alias) + :InitDelayOff() + :InitSetUnitAbsolutePositions(Positions) + :OnSpawnGroup(function(grp) grp.spawntime = timer.getTime() end) + :SpawnFromVec2(randomcoord:GetVec2()) + self:__TroopsDeployed(1, Group, Unit, self.DroppedTroops[self.TroopCounter], cType) + end + + foundCargo:SetWasDropped(true) + if cType == CTLD_CARGO.Enum.ENGINEERS then + self.Engineers = self.Engineers + 1 + self:_SendMessage(string.format("Dropped Engineers %s into action!", name), 10, false, Group) + else + self:_SendMessage(string.format("Dropped Troops %s into action!", name), 10, false, Group) + end + + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil + end + + else + -- Return to base logic, remove ONLY the first cargo + self:_SendMessage("Troops have returned to base!", 10, false, Group) + self:__TroopsRTB(1, Group, Unit, zonename, zone) + + if self.TroopsIDToChunk and self.TroopsIDToChunk[chunkID] then + local chunk = self.TroopsIDToChunk[chunkID] + if #chunk > 0 then + local firstObj = chunk[1] + local cName = firstObj:GetName() + local gentroops = self.Cargo_Troops + for _id, _troop in pairs(gentroops) do + if _troop.Name == cName then + local st = _troop:GetStock() + if st and tonumber(st) >= 0 then + _troop:AddStock() + end + end + end + firstObj:SetWasDropped(true) + table.remove(chunk, 1) + if #chunk == 0 then + self.TroopsIDToChunk[chunkID] = nil + end + end + end + end + + local cargoList = self.Loaded_Cargo[unitName].Cargo + for i = #cargoList, 1, -1 do + if cargoList[i]:WasDropped() then + table.remove(cargoList, i) + end + end + local troopsLoaded = 0 + local cratesLoaded = 0 + for _, cargo in ipairs(cargoList) do + local cT = cargo:GetType() + if cT == CTLD_CARGO.Enum.TROOPS or cT == CTLD_CARGO.Enum.ENGINEERS then + troopsLoaded = troopsLoaded + 1 + else + cratesLoaded = cratesLoaded + 1 + end + end + self.Loaded_Cargo[unitName].Troopsloaded = troopsLoaded + self.Loaded_Cargo[unitName].Cratesloaded = cratesLoaded + self:_RefreshDropTroopsMenu(Group, Unit) + else + local isHerc = self:IsFixedWing(Unit) + if isHerc then + self:_SendMessage("Nothing loaded or not within airdrop parameters!", 10, false, Group) + else + self:_SendMessage("Nothing loaded or not hovering within parameters!", 10, false, Group) + end + end + return self +end + +--- (Internal) Function to refresh menu for troops on drop for a specific unit +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The requesting group. +-- @param Wrapper.Unit#UNIT Unit The requesting unit. +-- @return #CTLD self +function CTLD:_RefreshDropTroopsMenu(Group, Unit) + local theGroup = Group + local theUnit = Unit + if not theGroup.CTLDTopmenu then return end + local topTroops = theGroup.MyTopTroopsMenu + if not topTroops then return end + if topTroops.DropTroopsMenu then + topTroops.DropTroopsMenu:Remove() + end + local dropTroopsMenu = MENU_GROUP:New(theGroup, "Drop Troops", topTroops) + topTroops.DropTroopsMenu = dropTroopsMenu + MENU_GROUP_COMMAND:New(theGroup, "Drop ALL troops", dropTroopsMenu, self._UnloadTroops, self, theGroup, theUnit) + + local loadedData = self.Loaded_Cargo[theUnit:GetName()] + if not loadedData or not loadedData.Cargo then return end + + -- Gather troop cargo by name + local troopsByName = {} + for _, cargoObj in ipairs(loadedData.Cargo) do + if cargoObj + and (cargoObj:GetType() == CTLD_CARGO.Enum.TROOPS or cargoObj:GetType() == CTLD_CARGO.Enum.ENGINEERS) + and not cargoObj:WasDropped() + then + local name = cargoObj:GetName() or "Unknown" + troopsByName[name] = troopsByName[name] or {} + table.insert(troopsByName[name], cargoObj) + end + end + + self.TroopsIDToChunk = self.TroopsIDToChunk or {} + + for tName, objList in pairs(troopsByName) do + table.sort(objList, function(a,b) return a:GetID() < b:GetID() end) + local count = #objList + + local chunkID = objList[1]:GetID() + self.TroopsIDToChunk[chunkID] = objList + + local label = string.format("Drop %s (%d)", tName, count) + MENU_GROUP_COMMAND:New(theGroup, label, dropTroopsMenu, self._UnloadSingleTroopByID, self, theGroup, theUnit, chunkID) + end +end --- [Internal] Function to check if a template exists in the mission. -- @param #CTLD self @@ -3897,7 +5069,7 @@ end -- @param #number PerTroopMass Mass in kg of each soldier -- @param #number Stock Number of groups in stock. Nil for unlimited. -- @param #string SubCategory Name of sub-category (optional). -function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory) +function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,SubCategory) self:T(self.lid .. " AddTroopsCargo") self:T({Name,Templates,Type,NoTroops,PerTroopMass,Stock}) if not self:_CheckTemplates(Templates) then @@ -3908,6 +5080,7 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,Sub -- Troops are directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory) table.insert(self.Cargo_Troops,cargo) + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -3922,7 +5095,12 @@ end -- @param #string SubCategory Name of sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. -function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH58D"}. +-- @param #string Category Static category name (optional). If set, spawn cargo crate with an alternate category type, e.g. "Cargos". +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape, e.g. "iso_container". +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape, e.g. "iso_container_cargo". +-- @return #CTLD self +function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) self:T(self.lid .. " AddCratesCargo") if not self:_CheckTemplates(Templates) then self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) @@ -3931,7 +5109,15 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) + if UnitTypes then + cargo:AddUnitTypeName(UnitTypes) + end + cargo:SetStaticTypeAndShape("Cargos",self.basetype) + if TypeName then + cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) + end table.insert(self.Cargo_Crates,cargo) + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end return self end @@ -3943,15 +5129,23 @@ end -- @param #string SubCategory Name of sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. +-- @return #CTLD_CARGO CargoObject function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location) self:T(self.lid .. " AddStaticsCargo") self.CargoCounter = self.CargoCounter + 1 local type = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() + local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) + local ResourceMap = nil + if unittemplate and unittemplate.resourcePayload then + ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) + end -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) + cargo:SetStaticResourceMap(ResourceMap) table.insert(self.Cargo_Statics,cargo) - return self + if SubCategory and self.usesubcats ~= true then self.usesubcats=true end + return cargo end --- User function - Get a *generic* static-type loadable as #CTLD_CARGO object. @@ -3964,8 +5158,14 @@ function CTLD:GetStaticsCargoFromTemplate(Name,Mass) self.CargoCounter = self.CargoCounter + 1 local type = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() + local unittemplate = _DATABASE:GetStaticUnitTemplate(Name) + local ResourceMap = nil + if unittemplate and unittemplate.resourcePayload then + ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload) + end -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1) + cargo:SetStaticResourceMap(ResourceMap) --table.insert(self.Cargo_Statics,cargo) return cargo end @@ -3981,7 +5181,12 @@ end -- @param #string SubCategory Name of the sub-category (optional). -- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. -- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string. -function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) +-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH58D"} +-- @param #string Category Static category name (optional). If set, spawn cargo crate with an alternate category type, e.g. "Cargos". +-- @param #string TypeName Static type name (optional). If set, spawn cargo crate with an alternate type shape, e.g. "iso_container". +-- @param #string ShapeName Static shape name (optional). If set, spawn cargo crate with an alternate type sub-shape, e.g. "iso_container_cargo". +-- @return #CTLD self +function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,TypeName,ShapeName) self:T(self.lid .. " AddCratesRepair") if not self:_CheckTemplates(Template) then self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) @@ -3990,6 +5195,13 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su self.CargoCounter = self.CargoCounter + 1 -- Crates are not directly loadable local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) + if UnitTypes then + cargo:AddUnitTypeName(UnitTypes) + end + cargo:SetStaticTypeAndShape("cargos",self.basetype) + if TypeName then + cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName) + end table.insert(self.Cargo_Crates,cargo) return self end @@ -4035,6 +5247,8 @@ function CTLD:ActivateZone(Name,ZoneType,NewState) table = self.dropOffZones elseif ZoneType == CTLD.CargoZoneType.SHIP then table = self.shipZones + elseif ZoneType == CTLD.CargoZoneType.BEACON then + table = self.droppedBeacons else table = self.wpZones end @@ -4467,7 +5681,8 @@ function CTLD:IsUnitInZone(Unit,Zonetype) end local distance = self:_GetDistance(zonecoord,unitcoord) self:T("Distance Zone: "..distance) - if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then + self:T("Zone Active: "..tostring(active)) + if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and distance < maxdist then outcome = true maxdist = distance zoneret = zone @@ -4556,7 +5771,7 @@ function CTLD:SmokeZoneNearBy(Unit, Flare) local distance = UTILS.MetersToNM(self.smokedistance) self:_SendMessage(string.format("Negative, need to be closer than %dnm to a zone!",distance), 10, false, Group) end - return self + return self end --- User - Function to add/adjust unittype capabilities. @@ -4574,9 +5789,8 @@ end 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() + elseif type(Unittype) == "table" and Unittype.ClassName and Unittype:IsInstanceOf("UNIT") then + unittype = Unittype:GetTypeName() else return self end @@ -4600,6 +5814,16 @@ end return self end + --- User - Function to add onw SET_GROUP Set-up for pilot filtering and assignment. + -- Needs to be set before starting the CTLD instance. + -- @param #CTLD self + -- @param Core.Set#SET_GROUP Set The SET_GROUP object created by the mission designer/user to represent the CTLD pilot groups. + -- @return #CTLD self + function CTLD:SetOwnSetPilotGroups(Set) + self.UserSetGroup = Set + return self + end + --- [Deprecated] - Function to add/adjust unittype capabilities. Has been replaced with `SetUnitCapabilities()` - pls use the new one going forward! -- @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. @@ -4663,9 +5887,9 @@ end end local gheight = ucoord:GetLandHeight() local aheight = uheight - gheight -- height above ground - local minh = self.HercMinAngels-- 1500m - local maxh = self.HercMaxAngels -- 5000m - local maxspeed = self.HercMaxSpeed -- 77 mps + local minh = self.FixedMinAngels-- 1500m + local maxh = self.FixedMaxAngels -- 5000m + local maxspeed = self.FixedMaxSpeed -- 77 mps -- DONE: TEST - Speed test for Herc, should not be above 280kph/150kn local kmspeed = uspeed * 3.6 local knspeed = kmspeed / 1.86 @@ -4708,12 +5932,12 @@ end if not inhover then htxt = "false" end local text = "" if _SETTINGS:IsImperial() then - local minheight = UTILS.MetersToFeet(self.HercMinAngels) - local maxheight = UTILS.MetersToFeet(self.HercMaxAngels) + local minheight = UTILS.MetersToFeet(self.FixedMinAngels) + local maxheight = UTILS.MetersToFeet(self.FixedMaxAngels) 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 + local minheight = self.FixedMinAngels + local maxheight = self.FixedMaxAngels 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) @@ -4726,7 +5950,7 @@ end -- @return #boolean Outcome function CTLD:CanHoverLoad(Unit) self:T(self.lid .. " CanHoverLoad") - if self:IsHercules(Unit) then return false end + if self:IsFixedWing(Unit) then return false end local outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) and self:IsCorrectHover(Unit) if not outcome then outcome = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) --and self:IsCorrectHover(Unit) @@ -4741,7 +5965,7 @@ end function CTLD:IsUnitInAir(Unit) -- get speed and height local minheight = self.minimumHoverHeight - if self.enableHercules and self:IsHercules(Unit) then + if self.enableFixedWing and self:IsFixedWing(Unit) then minheight = 5.1 -- herc is 5m AGL on the ground end local uheight = Unit:GetHeight() @@ -4829,6 +6053,162 @@ end self.EngineersInField = engtable return self end + + --- User - Count both the stock and groups in the field for available cargo types. Counts only limited cargo items and only troops and vehicle/FOB crates! + -- @param #CTLD self + -- @param #boolean Restock If true, restock the cargo and troop items. + -- @param #number Threshold Percentage below which to restock, used in conjunction with Restock (must be true). Defaults to 75 (percent). + -- @return #table Table A table of contents with numbers. + -- @usage + -- The index is the unique cargo name. + -- Each entry in the returned table contains a table with the following entries: + -- + -- { + -- Stock0 -- number of original stock when the cargo entry was created. + -- Stock -- number of currently available stock. + -- StockR -- relative number of available stock, e.g. 75 (percent). + -- Infield -- number of groups alive in the field of this kind. + -- Inhelo -- number of troops/crates in any helo alive. Can be with decimals < 1 if e.g. you have cargo that need 4 crates, but you have 2 loaded. + -- Sum -- sum is stock + infield + inhelo. + -- GenericCargo -- this filed holds the generic CTLD_CARGO object which drives the available stock. Only populated if Restock is true. + -- } + function CTLD:_CountStockPlusInHeloPlusAliveGroups(Restock,Threshold) + local Troopstable = {} + for _id, _cargo in pairs(self.Cargo_Crates) do + local generic = _cargo + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + CratesInfield = 0, + Sum = generic:GetStock(), + } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end + end + end + for _id, _cargo in pairs(self.Cargo_Troops) do + local generic = _cargo + local genname = generic:GetName() + if generic and generic:GetStock0() > 0 and not Troopstable[genname] then + Troopstable[genname] = { + Stock0 = generic:GetStock0(), + Stock = generic:GetStock(), + StockR = generic:GetRelativeStock(), + Infield = 0, + Inhelo = 0, + CratesInfield = 0, + Sum = generic:GetStock(), + } + if Restock == true then + Troopstable[genname].GenericCargo = generic + end + end + end + for _index, _group in pairs(self.DroppedTroops) do + if _group and _group:IsAlive() then + self:T("Looking at " .. _group:GetName() .. " in the field") + local generic = self:GetGenericCargoObjectFromGroupName(_group:GetName()) + if generic then + local genname = generic:GetName() + self:T("Found Generic " .. genname .. " in the field. Adding.") + if generic:GetStock0() > 0 then + Troopstable[genname].Infield = Troopstable[genname].Infield + 1 + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + Troopstable[genname].Inhelo + end + else + self:E(self.lid .. "Group without Cargo Generic: " .. _group:GetName()) + end + end + end + for _unitname, _loaded in pairs(self.Loaded_Cargo) do + local _unit = UNIT:FindByName(_unitname) + if _unit and _unit:IsAlive() then + local unitname = _unit:GetName() + local loadedcargo = self.Loaded_Cargo[unitname].Cargo or {} + for _, _cgo in pairs(loadedcargo) do + local cargo = _cgo + local type = cargo.CargoType + local gname = cargo.Name + local gcargo = self:_FindCratesCargoObject(gname) or self:_FindTroopsCargoObject(gname) + self:T("Looking at " .. gname .. " in the helo - type = " .. type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS or type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + if gcargo and gcargo:GetStock0() > 0 then + self:T("Adding " .. gname .. " in the helo - type = " .. type) + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + 1 + end + if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + local counting = gcargo.CratesNeeded + local added = 1 + if counting > 1 then + added = added / counting + end + Troopstable[gname].Inhelo = Troopstable[gname].Inhelo + added + end + Troopstable[gname].Sum = Troopstable[gname].Infield + Troopstable[gname].Stock + Troopstable[gname].Inhelo + Troopstable[gname].CratesInfield + end + end + end + end + end + if self.Spawned_Cargo then + -- First pass: just add fractional amounts for each crate on the ground + for i = #self.Spawned_Cargo, 1, -1 do + local cargo = self.Spawned_Cargo[i] + if cargo and cargo:GetPositionable() and cargo:GetPositionable():IsAlive() then + local genname = cargo:GetName() + local gcargo = self:_FindCratesCargoObject(genname) + if Troopstable[genname] and gcargo and gcargo:GetStock0() > 0 then + local needed = gcargo.CratesNeeded or 1 + local added = 1 + if needed > 1 then + added = added / needed + end + Troopstable[genname].CratesInfield = Troopstable[genname].CratesInfield + added + Troopstable[genname].Sum = Troopstable[genname].Infield + Troopstable[genname].Stock + + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield + end + end + end + for i = #self.Spawned_Cargo, 1, -1 do + local cargo = self.Spawned_Cargo[i] + if cargo and cargo:GetPositionable() and cargo:GetPositionable():IsAlive() then + local genname = cargo:GetName() + if Troopstable[genname] then + if Troopstable[genname].Inhelo == 0 and Troopstable[genname].CratesInfield < 1 then + Troopstable[genname].CratesInfield = 0 + Troopstable[genname].Sum = Troopstable[genname].Stock + cargo:GetPositionable():Destroy(false) + table.remove(self.Spawned_Cargo, i) + local leftover = Troopstable[genname].Stock0 - (Troopstable[genname].Infield + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield) + if leftover < Troopstable[genname].Stock then + Troopstable[genname].Stock = leftover + end + Troopstable[genname].Sum = Troopstable[genname].Stock + Troopstable[genname].Infield + Troopstable[genname].Inhelo + Troopstable[genname].CratesInfield + end + end + end + end + end + if Restock == true then + local threshold = Threshold or 75 + for _name,_data in pairs(Troopstable) do + if _data.StockR and _data.StockR < threshold then + if _data.GenericCargo then + _data.GenericCargo:SetStock(_data.Stock0) -- refill to start level + end + end + end + end + return Troopstable + end + --- User - function to add stock of a certain troops type -- @param #CTLD self @@ -4843,6 +6223,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -4861,6 +6242,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -4879,6 +6261,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:AddStock(number) + break end end return self @@ -4897,6 +6280,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -4915,6 +6299,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -4933,6 +6318,7 @@ end for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO if _troop.Name == name then _troop:SetStock(number) + break end end return self @@ -4945,7 +6331,8 @@ end local Stock = {} local gentroops = self.Cargo_Crates for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -4957,11 +6344,33 @@ end local Stock = {} local gentroops = self.Cargo_Troops for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + --table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end + --- User - Query the cargo loaded from a specific unit + -- @param #CTLD self + -- @param Wrapper.Unit#UNIT Unit The (client) unit to query. + -- @return #number Troopsloaded + -- @return #number Cratesloaded + -- @return #table Cargo Table of #CTLD_CARGO objects + function CTLD:GetLoadedCargo(Unit) + local Troops = 0 + local Crates = 0 + local Cargo = {} + if Unit and Unit:IsAlive() then + local name = Unit:GetName() + if self.Loaded_Cargo[name] then + Troops = self.Loaded_Cargo[name].Troopsloaded or 0 + Crates = self.Loaded_Cargo[name].Cratesloaded or 0 + Cargo = self.Loaded_Cargo[name].Cargo or {} + end + end + return Troops, Crates, Cargo + end + --- User - function to get a table of statics cargo in stock -- @param #CTLD self -- @return #table Table Table of Stock, indexed by cargo type name @@ -4969,7 +6378,8 @@ end local Stock = {} local gentroops = self.Cargo_Statics for _id,_troop in pairs (gentroops) do -- #number, #CTLD_CARGO - table.insert(Stock,_troop.Name,_troop.Stock or -1) + Stock[_troop.Name] = _troop.Stock or -1 + -- table.insert(Stock,_troop.Name,_troop.Stock or -1) end return Stock end @@ -5028,6 +6438,38 @@ end return self end + --- (User) Get a generic #CTLD_CARGO entry from a group name, works for Troops and Vehicles, FOB, i.e. everything that is spawned as a GROUP object. + -- @param #CTLD self + -- @param #string GroupName The name to use for the search + -- @return #CTLD_CARGO The cargo object or nil if not found + function CTLD:GetGenericCargoObjectFromGroupName(GroupName) + local Cargotype = nil + local template = GroupName + if string.find(template,"#") then + template = string.gsub(GroupName,"#(%d+)$","") + end + template = string.gsub(template,"-(%d+)$","") + for k,v in pairs(self.Cargo_Troops) do + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == template then + Cargotype = v + break + end + end + if not Cargotype then + for k,v in pairs(self.Cargo_Crates) do -- #number, #CTLD_CARGO + local comparison = "" + if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end + if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then + Cargotype = v + break + end + end + end + return Cargotype + end + --- (Internal) Check on engineering teams -- @param #CTLD self -- @return #CTLD self @@ -5040,7 +6482,7 @@ end self:T(_engineers.lid .. _engineers:GetStatus()) if wrenches and wrenches:IsAlive() then if engineers:IsStatus("Running") or engineers:IsStatus("Searching") then - local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch,true) -- #table + local crates,number = self:_FindCratesNearby(wrenches,nil, self.EngineerSearch,true,true) -- #table engineers:Search(crates,number) elseif engineers:IsStatus("Moving") then engineers:Move() @@ -5065,6 +6507,7 @@ end -- @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! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5073,7 +6516,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND}) - function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO @@ -5081,14 +6524,18 @@ end local match = false local cgotbl = self.Cargo_Troops local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match, CargoObject, CargoName end local function Cruncher(group,typename,anzahl) @@ -5134,13 +6581,26 @@ end end end - if not IsTroopsMatch(cargo) then + local match,CargoObject,CargoName = IsTroopsMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Troops,cargo) end + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end + end + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then -- unload @@ -5152,12 +6612,14 @@ end if PreciseLocation then randomcoord = zone:GetCoordinate():GetVec2() end + local randompositions = not PreciseLocation 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) + :InitRandomizeUnits(randompositions,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) if self.movetroopstowpzone and type ~= CTLD_CARGO.Enum.ENGINEERS then self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) @@ -5175,6 +6637,12 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtable and TimeStamp ~= nil then + self:T2("Inserting: "..cargo.CargoType) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) + end + if self.eventoninject then self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter],type) end @@ -5182,13 +6650,14 @@ end return self end - --- (User) Pre-populate vehicles in the field. + --- (User) Pre-populate vehicles in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. -- @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! -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @param #string Structure (Optional) String object describing the current structure of the injected group; mainly for load/save to keep current state setup. + -- @param #number TimeStamp (Optional) Timestamp used internally on re-loading from disk. -- @return #CTLD self -- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -5197,7 +6666,7 @@ end -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) - function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure) + function CTLD:InjectVehicles(Zone,Cargo,Surfacetypes,PreciseLocation,Structure,TimeStamp) self:T(self.lid.." InjectVehicles") local cargo = Cargo -- #CTLD_CARGO @@ -5205,14 +6674,18 @@ end local match = false local cgotbl = self.Cargo_Crates local name = cargo:GetName() + local CargoObject + local CargoName for _,_cgo in pairs (cgotbl) do local cname = _cgo:GetName() if name == cname then match = true + CargoObject = _cgo + CargoName = cname break end end - return match + return match,CargoObject,CargoName end local function Cruncher(group,typename,anzahl) @@ -5258,13 +6731,26 @@ end end end - if not IsVehicMatch(cargo) then + local match,CargoObject,CargoName = IsVehicMatch(cargo) + + if not match then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter - cargo.Stock = 1 + --cargo.Stock = 1 table.insert(self.Cargo_Crates,cargo) end + if match and CargoObject then + local stock = CargoObject:GetStock() + if stock ~= -1 and stock ~= nil and stock == 0 then + -- stock empty + self:T(self.lid.."Stock of "..CargoName.." is empty. Cannot inject.") + return + else + CargoObject:RemoveStock(1) + end + end + local type = cargo:GetType() -- #CTLD_CARGO.Enum if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then -- unload @@ -5286,10 +6772,12 @@ end self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitRandomizeUnits(true,20,2) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) else -- don't random position of e.g. SAM units build as FOB self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) :InitDelayOff() + :OnSpawnGroup(function(grp,TimeStamp) grp.spawntime = TimeStamp or timer.getTime() end,TimeStamp) :SpawnFromVec2(randomcoord) end @@ -5297,6 +6785,12 @@ end BASE:ScheduleOnce(0.5,PostSpawn,{self.DroppedTroops[self.TroopCounter],Structure}) end + if self.keeploadtable and TimeStamp ~= nil then + self:T2("Inserting: "..cargo.CargoType) + local cargotype = type + table.insert(self.LoadedGroupsTable,{Group=self.DroppedTroops[self.TroopCounter], TimeStamp=TimeStamp, CargoType=cargotype, CargoName=name}) + end + if self.eventoninject then self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) end @@ -5318,9 +6812,12 @@ end function CTLD:onafterStart(From, Event, To) self:T({From, Event, To}) self:I(self.lid .. "Started ("..self.version..")") - if self.useprefix or self.enableHercules then + if self.enableHercules then self.enableFixedWing = true end + if self.UserSetGroup then + self.PilotGroups = self.UserSetGroup + elseif self.useprefix or self.enableFixedWing then local prefix = self.prefixes - if self.enableHercules then + if self.enableFixedWing then self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterStart() else self.PilotGroups = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefix):FilterCategories("helicopter"):FilterStart() @@ -5331,7 +6828,15 @@ end -- Events self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) + self:HandleEvent(EVENTS.UnitLost, self._EventHandler) + --self:HandleEvent(EVENTS.Birth, self._EventHandler) + self:HandleEvent(EVENTS.NewDynamicCargo, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoLoaded, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoUnloaded, self._EventHandler) + self:HandleEvent(EVENTS.DynamicCargoRemoved, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) self:__Status(-5) -- AutoSave @@ -5419,9 +6924,11 @@ end -- @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) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerLeaveUnit) + self:UnHandleEvent(EVENTS.UnitLost) + self:UnHandleEvent(EVENTS.Shot) return self end @@ -5446,7 +6953,7 @@ end -- @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 crate. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew loaded! -- @return #CTLD self function CTLD:onbeforeCratesPickedUp(From, Event, To, Group, Unit, Cargo) self:T({From, Event, To}) @@ -5461,9 +6968,33 @@ end -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. -- @param Wrapper.Group#GROUP Troops Troops #GROUP Object. + -- @param #string Groupname Name of the extracted #GROUP. -- @return #CTLD self - function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops) + function CTLD:onbeforeTroopsExtracted(From, Event, To, Group, Unit, Troops, Groupname) self:T({From, Event, To}) + if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then + local playername = Unit:GetPlayerName() + --local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0) + --local dropvec2 = dropcoord:GetVec2() + self.PlayerTaskQueue:ForEach( + function (Task) + local task = Task -- Ops.PlayerTask#PLAYERTASK + local subtype = task:GetSubType() + -- right subtype? + if Event == subtype and not task:IsDone() then + local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case .... + --self:T2({Name=Groupname,Property=task:GetProperty("ExtractName")}) + local okaygroup = string.find(Groupname,task:GetProperty("ExtractName"),1,true) + if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and okaygroup then + if task.Clients:HasUniqueID(playername) then + -- success + task:__Success(-1) + end + end + end + end + ) + end return self end @@ -5528,7 +7059,7 @@ end -- @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. + -- @param #table Cargotable Table of #CTLD_CARGO objects dropped. Can be a Wrapper.DynamicCargo#DYNAMICCARGO object, if ground crew unloaded! -- @return #CTLD self function CTLD:onbeforeCratesDropped(From, Event, To, Group, Unit, Cargotable) self:T({From, Event, To}) @@ -5594,8 +7125,10 @@ end -- @param #string To State. -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + -- @param #string ZoneName Name of the Zone where the Troops have been RTB'd. + -- @param Core.Zone#ZONE_Radius ZoneObject of the Zone where the Troops have been RTB'd. -- @return #CTLD self - function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit) + function CTLD:onbeforeTroopsRTB(From, Event, To, Group, Unit, ZoneName, ZoneObject) self:T({From, Event, To}) return self end @@ -5667,7 +7200,7 @@ end local statics = nil local statics = {} - self:T(self.lid.."Bulding Statics Table for Saving") + self:T(self.lid.."Building Statics Table for Saving") for _,_cargo in pairs (stcstable) do local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC @@ -5702,7 +7235,7 @@ end --local data = "LoadedData = {\n" - local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass,Structure,StaticCategory,StaticType,StaticShape,SpawnTime\n" local n = 0 for _,_grp in pairs(grouptable) do local group = _grp -- Wrapper.Group#GROUP @@ -5729,11 +7262,13 @@ end local cgotype = cargo.CargoType local cgoneed = cargo.CratesNeeded local cgomass = cargo.PerCrateMass + local scat,stype,sshape = cargo:GetStaticTypeAndShape() local structure = UTILS.GetCountPerTypeName(group) local strucdata = "" for typen,anzahl in pairs (structure) do strucdata = strucdata .. typen .. "=="..anzahl..";" end + local spawntime = group.spawntime or timer.getTime()+n if type(cgotemp) == "table" then local templates = "{" @@ -5745,8 +7280,8 @@ end end local location = group:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s\n" - ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata) + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,%s,%s,%s,%s,%f\n" + ,template,location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,strucdata,scat,stype,sshape or "none",spawntime) data = data .. txt end end @@ -5771,8 +7306,9 @@ end local cgomass = object.PerCrateMass local crateobj = object.Positionable local location = crateobj:GetVec3() - local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" - ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass) + local scat,stype,sshape = object:GetStaticTypeAndShape() + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d,'none',%s,%s,%s\n" + ,"STATIC",location.x,location.y,location.z,cgoname,cgotemp,cgotype,cgoneed,cgomass,scat,stype,sshape or "none") data = data .. txt end @@ -5898,57 +7434,67 @@ end -- remove header table.remove(loadeddata, 1) - + local n=0 for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure + -- 1=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass,10=Structure,11=StaticCategory,12=StaticType,13=StaticShape,14=Timestamp local groupname = dataset[1] local vec2 = {} vec2.x = tonumber(dataset[2]) vec2.y = tonumber(dataset[4]) local cargoname = dataset[5] + local cargotemplates = dataset[6] local cargotype = dataset[7] + local size = tonumber(dataset[8]) + local mass = tonumber(dataset[9]) + local StaticCategory = dataset[11] + local StaticType = dataset[12] + local StaticShape = dataset[13] + n=n+1 + local timestamp = tonumber(dataset[14]) or (timer.getTime()+n) + self:T2("TimeStamp = "..timestamp) if type(groupname) == "string" and groupname ~= "STATIC" then - local cargotemplates = dataset[6] cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") cargotemplates = UTILS.Split(cargotemplates,";") - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) local structure = nil - if dataset[10] then + if dataset[10] and dataset[10] ~= "none" then structure = dataset[10] structure = string.gsub(structure,",","") end -- inject at Vec2 local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then - local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure) + local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectvehicle:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) + self:InjectVehicles(dropzone,injectvehicle,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure) + self:InjectTroops(dropzone,injecttroops,self.surfacetypes,self.useprecisecoordloads,structure,timestamp) end elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then - local cargotemplates = dataset[6] - local size = tonumber(dataset[8]) - local mass = tonumber(dataset[9]) local dropzone = ZONE_RADIUS:New("DropZone",vec2,20) local injectstatic = nil if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then cargotemplates = string.gsub(cargotemplates,"{","") cargotemplates = string.gsub(cargotemplates,"}","") cargotemplates = UTILS.Split(cargotemplates,";") - injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) + elseif cargotype == CTLD_CARGO.Enum.STATIC or cargotype == CTLD_CARGO.Enum.REPAIR then + injectstatic = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) + injectstatic:SetStaticTypeAndShape(StaticCategory,StaticType,StaticShape) + local map=cargotype:GetStaticResourceMap() + injectstatic:SetStaticResourceMap(map) end if injectstatic then - self:InjectStatics(dropzone,injectstatic) + self:InjectStatics(dropzone,injectstatic,false,true) end end end - + if self.keeploadtable then -- keeploadtables + self:__Loaded(1,self.LoadedGroupsTable) + end return self end end -- end do @@ -6073,7 +7619,7 @@ CTLD_HERCULES.Types = { -- @usage -- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance: -- --- my_ctld.enableHercules = false -- avoid dual loading via CTLD F10 and F8 ground crew +-- my_ctld.enableFixedWing = false -- avoid dual loading via CTLD F10 and F8 ground crew -- local herccargo = CTLD_HERCULES:New("blue", "Hercules Test", my_ctld) -- -- You also need: @@ -6211,7 +7757,7 @@ end --- [Internal] Function to spawn a soldier group of 10 units -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -6234,7 +7780,7 @@ end --- [Internal] Function to spawn a group -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #number Cargo_Country @@ -6258,7 +7804,7 @@ end --- [Internal] Function to spawn static cargo -- @param #CTLD_HERCULES self -- @param Wrapper.Group#GROUP Cargo_Drop_initiator --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @param #string Cargo_Type_name -- @param #number CargoHeading -- @param #boolean dead @@ -6272,7 +7818,7 @@ function CTLD_HERCULES:Cargo_SpawnStatic(Cargo_Drop_initiator,Cargo_Drop_Positio local Zone = ZONE_RADIUS:New("Cargo Static " .. math.random(1,10000),position,100) if not dead then local injectstatic = CTLD_CARGO:New(nil,"Cargo Static Group "..math.random(1,10000),"iso_container",CTLD_CARGO.Enum.STATIC,true,false,1,nil,true,4500,1) - self.CTLD:InjectStatics(Zone,injectstatic,true) + self.CTLD:InjectStatics(Zone,injectstatic,true,true) end return self end @@ -6280,23 +7826,28 @@ end --- [Internal] Function to spawn cargo by type at position -- @param #CTLD_HERCULES self -- @param #string Cargo_Type_name --- @param Core.Point#POINT_VEC3 Cargo_Drop_Position +-- @param Core.Point#COORDINATE Cargo_Drop_Position -- @return #CTLD_HERCULES self function CTLD_HERCULES:Cargo_SpawnDroppedAsCargo(_name, _pos) - local theCargo = self.CTLD:_FindCratesCargoObject(_name) + local theCargo = self.CTLD:_FindCratesCargoObject(_name) -- #CTLD_CARGO if theCargo then self.CTLD.CrateCounter = self.CTLD.CrateCounter + 1 - self.CTLD.CargoCounter = self.CTLD.CargoCounter + 1 - - local basetype = self.CTLD.basetype or "container_cargo" - local theStatic = SPAWNSTATIC:NewFromType(basetype,"Cargos",self.cratecountry) + local CCat, CType, CShape = theCargo:GetStaticTypeAndShape() + local basetype = CType or self.CTLD.basetype or "container_cargo" + CCat = CCat or "Cargos" + local theStatic = SPAWNSTATIC:NewFromType(basetype,CCat,self.cratecountry) :InitCargoMass(theCargo.PerCrateMass) :InitCargo(self.CTLD.enableslingload) :InitCoordinate(_pos) - :Spawn(270,_name .. "-Container-".. math.random(1,100000)) + if CShape then + theStatic:InitShape(CShape) + end + theStatic:Spawn(270,_name .. "-Container-".. math.random(1,100000)) self.CTLD.Spawned_Crates[self.CTLD.CrateCounter] = theStatic local newCargo = CTLD_CARGO:New(self.CTLD.CargoCounter, theCargo.Name, theCargo.Templates, theCargo.CargoType, true, false, theCargo.CratesNeeded, self.CTLD.Spawned_Crates[self.CTLD.CrateCounter], true, theCargo.PerCrateMass, nil, theCargo.Subcategory) + local map=theCargo:GetStaticResourceMap() + newCargo:SetStaticResourceMap(map) table.insert(self.CTLD.Spawned_Cargo, newCargo) newCargo:SetWasDropped(true) @@ -6327,8 +7878,6 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct if offload_cargo == true or ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then - --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) - --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) else self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 0592046df..3e9f46561 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -34,6 +34,7 @@ -- @field Ops.Commander#COMMANDER commander Commander of assigned legions. -- @field #number Nsuccess Number of successful missions. -- @field #number Nfailure Number of failed mission. +-- @field #table assetNumbers Asset numbers. Each entry is a table of data type `#CHIEF.AssetNumber`. -- @extends Ops.Intel#INTEL --- *In preparing for battle I have always found that plans are useless, but planning is indispensable* -- Dwight D Eisenhower @@ -163,7 +164,7 @@ -- -- Will at a strategic zone with importance 2. -- --- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS and an ARTY mission are lauchned: +-- If the zone is currently owned by another coalition and enemy ground troops are present in the zone, a CAS and an ARTY mission are launched: -- -- * A mission of type `AUFTRAG.Type.CASENHANCED` is started if assets are available that can carry out this mission type. -- * A mission of type `AUFTRAG.Type.ARTY` is started provided assets are available. @@ -331,7 +332,7 @@ CHIEF.Strategy = { --- CHIEF class version. -- @field #string version -CHIEF.version="0.6.0" +CHIEF.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1284,8 +1285,10 @@ end -- -- Empty: -- --- * `AUFTRAG.Type.ONGURAD` with Nmin=1 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`. +-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`. +-- * `AUFTRAG.Type.ONGURAD` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_IFV`. -- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`. +-- * `AUFTRAG.Type.OPSTRANSPORT` with Nmin=0 and Nmax=1 assets, Attribute=`GROUP.Attribute.AIR_TRANSPORTHELO` or `GROUP.Attribute.GROUND_APC`. This asset is used to transport the infantry groups. -- -- Resources can be created with the @{#CHIEF.CreateResource} and @{#CHIEF.AddToResource} functions. -- @@ -3033,10 +3036,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) end -- Recruite infantry assets. + self:T(self.lid..string.format("Recruiting assets for zone %s", StratZone.opszone:GetName())) self:T(self.lid.."Missiontype="..MissionType) self:T({categories=Categories}) self:T({attributes=Attributes}) self:T({properties=Properties}) + + local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, RangeMax, nil, nil, nil, nil, Categories, Attributes, Properties) if recruited then @@ -3052,9 +3058,12 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) local TargetCoord = TargetZone:GetCoordinate() -- First check if we need a transportation. - local transport=nil + local transport=nil --Ops.OpsTransport#OPSTRANSPORT + local Ntransports=0 if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then + self:T(self.lid..string.format("Recruiting carrier assets: Nmin=%s, Nmax=%s", tostring(Resource.carrierNmin), tostring(Resource.carrierNmax))) + -- Filter only those assets that shall be transported. local cargoassets=CHIEF._FilterAssets(assets, Resource.Categories, Resource.Attributes, Resource.Properties) @@ -3064,6 +3073,10 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) recruited, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, cargoassets, Resource.carrierNmin, Resource.carrierNmax, TargetZone, nil, Resource.carrierCategories, Resource.carrierAttributes, Resource.carrierProperties) + Ntransports=transport~=nil and #transport.assets or 0 + + self:T(self.lid..string.format("Recruited %d transport carrier assets success=%s", Ntransports, tostring(recruited))) + end end @@ -3076,7 +3089,7 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) return false end - -- Debug messgage. + -- Debug message self:T2(self.lid..string.format("Recruited %d assets for mission %s", #assets, MissionType)) @@ -3224,10 +3237,13 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) -- Attach mission to ops zone. StratZone.opszone:_AddMission(self.coalition, MissionType, mission) + mission:SetName(string.format("Stratzone %s-%d", StratZone.opszone:GetName(), mission.auftragsnummer)) + -- Attach mission to resource. Resource.mission=mission - if transport then + -- Check if transport assets could be allocated. If carrier Nmin=0 and 0 assets could be allocated, transport would still be created but not usefull obviously + if transport and Ntransports>0 then -- Attach OPS transport to mission. mission.opstransport=transport -- Set ops zone to transport. diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index 0d25c9c5d..26f54e66d 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -88,7 +88,10 @@ COHORT = { --- COHORT class version. -- @field #string version -COHORT.version="0.3.5" +COHORT.version="0.3.6" + +--- Global variable to store the unique(!) cohort names +_COHORTNAMES={} ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -110,6 +113,17 @@ COHORT.version="0.3.5" -- @return #COHORT self function COHORT:New(TemplateGroupName, Ngroups, CohortName) + -- Name of the cohort. + local name=tostring(CohortName or TemplateGroupName) + + -- Cohort name has to be unique or we will get serious problems! + if UTILS.IsAnyInTable(_COHORTNAMES, name) then + env.error(string.format('ERROR: cannot create cohort "%s" because another cohort with that name already exists. Names must be unique!', name)) + return nil + else + table.insert(_COHORTNAMES, name) + end + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #COHORT @@ -117,7 +131,7 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName) self.templatename=TemplateGroupName -- Cohort name. - self.name=tostring(CohortName or TemplateGroupName) + self.name=name -- Set some string id for output to DCS.log file. self.lid=string.format("COHORT %s | ", self.name) @@ -577,7 +591,7 @@ function COHORT:AddAsset(Asset) return self end ---- Remove asset from chort. +--- Remove specific asset from chort. -- @param #COHORT self -- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset. -- @return #COHORT self @@ -609,6 +623,40 @@ function COHORT:DelGroup(GroupName) return self end +--- Remove assets from pool. Not that assets must not be spawned or already reserved or requested. +-- @param #COHORT self +-- @param #number N Number of assets to be removed. Default 1. +-- @return #COHORT self +function COHORT:RemoveAssets(N) + self:T2(self.lid..string.format("Remove %d assets of Cohort", N)) + + N=N or 1 + + local n=0 + for i=#self.assets,1,-1 do + local asset=self.assets[i] --Functional.Warehouse#WAREHOUSE.Assetitem + + self:T2(self.lid..string.format("Checking removing asset %s", asset.spawngroupname)) + if not (asset.requested or asset.spawned or asset.isReserved) then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + n=n+1 + else + self:T2(self.lid..string.format("Could NOT Remove asset %s", asset.spawngroupname)) + end + + if n>=N then + break + end + + end + + self:T(self.lid..string.format("Removed %d/%d assets. New asset count=%d", n, N, #self.assets)) + + return self +end + + --- Get name of the cohort. -- @param #COHORT self -- @return #string Name of the cohort. @@ -975,6 +1023,7 @@ function COHORT:CanMission(Mission) if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then -- Correct refueling system. + self:T(self.lid..string.format("INFO: Correct refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) else self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) return false diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 596eb5661..b7d30f298 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -1774,8 +1774,10 @@ function COMMANDER:RecruitAssetsForMission(Mission) MaxWeight=cohort.cargobayLimit end end - - self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight)) + + if MaxWeight then + self:T(self.lid..string.format("Largest cargo bay available=%.1f", MaxWeight)) + end end local legions=self.legions @@ -2165,4 +2167,4 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/EasyGCICAP.lua b/Moose Development/Moose/Ops/EasyGCICAP.lua index 06ae1d6ca..c5f0ebb0b 100644 --- a/Moose Development/Moose/Ops/EasyGCICAP.lua +++ b/Moose Development/Moose/Ops/EasyGCICAP.lua @@ -7,6 +7,7 @@ -- ------------------------------------------------------------------------- -- Date: September 2023 +-- Last Update: July 2024 ------------------------------------------------------------------------- -- --- **Ops** - Easy GCI & CAP Manager @@ -49,7 +50,7 @@ -- @field #number capleg -- @field #number maxinterceptsize -- @field #number missionrange --- @field #number noaltert5 +-- @field #number noalert5 -- @field #table ManagedAW -- @field #table ManagedSQ -- @field #table ManagedCP @@ -67,6 +68,7 @@ -- @field #table ReadyFlightGroups -- @field #boolean DespawnAfterLanding -- @field #boolean DespawnAfterHolding +-- @field #list ListOfAuftrag -- @extends Core.Fsm#FSM --- *“Airspeed, altitude, and brains. Two are always needed to successfully complete the flight.”* -- Unknown. @@ -95,7 +97,8 @@ -- -- ### Prerequisites -- --- You have to put a STATIC object on the airbase with the UNIT name according to the name of the airbase. E.g. for Kuitaisi this has to have the name Kutaisi. This object symbolizes the AirWing HQ. +-- You have to put a **STATIC WAREHOUSE** object on the airbase with the UNIT name according to the name of the airbase. **Do not put any other static type or it creates a conflict with the airbase name!** +-- E.g. for Kuitaisi this has to have the unit name Kutaisi. This object symbolizes the AirWing HQ. -- Next put a late activated template group for your CAP/GCI Squadron on the map. Last, put a zone on the map for the CAP operations, let's name it "Blue Zone 1". Size of the zone plays no role. -- Put an EW radar system on the map and name it aptly, like "Blue EWR". -- @@ -164,7 +167,7 @@ -- * @{#EASYGCICAP.SetDefaultCAPLeg}: Set the length of the CAP leg, default is 15 NM. -- * @{#EASYGCICAP.SetDefaultCAPGrouping}: Set how many planes will be spawned per mission (CVAP/GCI), defaults to 2. -- * @{#EASYGCICAP.SetDefaultMissionRange}: Set how many NM the planes can go from the home base, defaults to 100. --- * @{#EASYGCICAP.SetDefaultNumberAlter5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2. +-- * @{#EASYGCICAP.SetDefaultNumberAlert5Standby}: Set how many planes will be spawned on cold standby (Alert5), default 2. -- * @{#EASYGCICAP.SetDefaultEngageRange}: Set max engage range for CAP flights if they detect intruders, defaults to 50. -- * @{#EASYGCICAP.SetMaxAliveMissions}: Set max parallel missions can be done (CAP+GCI+Alert5+Tanker+AWACS), defaults to 8. -- * @{#EASYGCICAP.SetDefaultRepeatOnFailure}: Set max repeats on failure for intercepting/killing intruders, defaults to 3. @@ -194,7 +197,7 @@ EASYGCICAP = { capleg = 15, maxinterceptsize = 2, missionrange = 100, - noaltert5 = 4, + noalert5 = 4, ManagedAW = {}, ManagedSQ = {}, ManagedCP = {}, @@ -213,6 +216,7 @@ EASYGCICAP = { ReadyFlightGroups = {}, DespawnAfterLanding = false, DespawnAfterHolding = true, + ListOfAuftrag = {} } --- Internal Squadron data type @@ -248,7 +252,7 @@ EASYGCICAP = { --- EASYGCICAP class version. -- @field #string version -EASYGCICAP.version="0.1.11" +EASYGCICAP.version="0.1.17" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -265,7 +269,7 @@ EASYGCICAP.version="0.1.11" -- @param #string Alias A Name for this GCICAP -- @param #string AirbaseName Name of the Home Airbase -- @param #string Coalition Coalition, e.g. "blue" or "red" --- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR" +-- @param #string EWRName (Partial) group name of the EWR system of the coalition, e.g. "Red EWR", can be handed in as table of names, e.g.{"EWR","Radar","SAM"} -- @return #EASYGCICAP self function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- Inherit everything from FSM class. @@ -274,9 +278,10 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) -- defaults self.alias = Alias or AirbaseName.." CAP Wing" self.coalitionname = string.lower(Coalition) or "blue" - self.coalition = self.coaltitionname == "blue" and coalition.side.BLUE or coalition.side.RED + self.coalition = self.coalitionname == "blue" and coalition.side.BLUE or coalition.side.RED self.wings = {} - self.EWRName = EWRName or self.coalitionname.." EWR" + if type(EWRName) == "string" then EWRName = {EWRName} end + self.EWRName = EWRName --or self.coalitionname.." EWR" --self.CapZoneName = CapZoneName self.airbasename = AirbaseName self.airbase = AIRBASE:FindByName(self.airbasename) @@ -289,7 +294,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.capleg = 15 self.capgrouping = 2 self.missionrange = 100 - self.noaltert5 = 2 + self.noalert5 = 2 self.MaxAliveMissions = 8 self.engagerange = 50 self.repeatsonfailure = 3 @@ -298,6 +303,7 @@ function EASYGCICAP:New(Alias, AirbaseName, Coalition, EWRName) self.CapFormation = ENUMS.Formation.FixedWing.FingerFour.Group self.DespawnAfterLanding = false self.DespawnAfterHolding = true + self.ListOfAuftrag = {} -- Set some string id for output to DCS.log file. self.lid=string.format("EASYGCICAP %s | ", self.alias) @@ -342,9 +348,23 @@ function EASYGCICAP:SetTankerAndAWACSInvisible(Switch) return self end ---- Set Maximum of alive missions to stop airplanes spamming the map +--- Count alive missions in our internal stack. -- @param #EASYGCICAP self --- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Cap-Missions + Intercept-Missions + Alert5-Missionsm default is 6 +-- @return #number count +function EASYGCICAP:_CountAliveAuftrags() + local alive = 0 + for _,_auftrag in pairs(self.ListOfAuftrag) do + local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG + if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then + alive = alive + 1 + end + end + return alive +end + +--- Set Maximum of alive missions created by this instance to stop airplanes spamming the map +-- @param #EASYGCICAP self +-- @param #number Maxiumum Maxmimum number of parallel missions allowed. Count is Intercept-Missions + Alert5-Missions, default is 8 -- @return #EASYGCICAP self function EASYGCICAP:SetMaxAliveMissions(Maxiumum) self:T(self.lid.."SetMaxAliveMissions") @@ -436,9 +456,9 @@ end -- @param #EASYGCICAP self -- @param #number Airframes defaults to 2 -- @return #EASYGCICAP self -function EASYGCICAP:SetDefaultNumberAlter5Standby(Airframes) - self:T(self.lid.."SetDefaultNumberAlter5Standby") - self.noaltert5 = math.abs(Airframes) or 2 +function EASYGCICAP:SetDefaultNumberAlert5Standby(Airframes) + self:T(self.lid.."SetDefaultNumberAlert5Standby") + self.noalert5 = math.abs(Airframes) or 2 return self end @@ -447,7 +467,7 @@ end -- @param #number Range defaults to 50NM -- @return #EASYGCICAP self function EASYGCICAP:SetDefaultEngageRange(Range) - self:T(self.lid.."SetDefaultNumberAlter5Standby") + self:T(self.lid.."SetDefaultEngageRange") self.engagerange = Range or 50 return self end @@ -579,7 +599,7 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) local TankerInvisible = self.TankerInvisible - function CAP_Wing:OnAfterFlightOnMission(From, Event, To, Flightgroup, Mission) + function CAP_Wing:onbeforeFlightOnMission(From, Event, To, Flightgroup, Mission) local flightgroup = Flightgroup -- Ops.FlightGroup#FLIGHTGROUP if DespawnAfterLanding then flightgroup:SetDespawnAfterLanding() @@ -609,17 +629,18 @@ function EASYGCICAP:_AddAirwing(Airbasename, Alias) flightgroup:SetFuelLowRTB(true) Intel:AddAgent(flightgroup) if DespawnAfterHolding then - function flightgroup:OnAfterHolding(From,Event,To) + function flightgroup:onbeforeHolding(From,Event,To) self:Despawn(1,true) end end end - if self.noaltert5 > 0 then + if self.noalert5 > 0 then local alert = AUFTRAG:NewALERT5(AUFTRAG.Type.INTERCEPT) - alert:SetRequiredAssets(self.noaltert5) + alert:SetRequiredAssets(self.noalert5) alert:SetRepeat(99) CAP_Wing:AddMission(alert) + table.insert(self.ListOfAuftrag,alert) end self.wings[Airbasename] = { CAP_Wing, AIRBASE:FindByName(Airbasename):GetZone(), Airbasename } @@ -1156,7 +1177,7 @@ function EASYGCICAP:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,Group return assigned, wingsize end ---- Add a zone to the rejected zones set. +--- Here, we'll decide if we need to launch an intercepting flight, and from where -- @param #EASYGCICAP self -- @param Ops.Intel#INTEL.Cluster Cluster -- @return #EASYGCICAP self @@ -1170,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster) local wings = self.wings local ctlpts = self.ManagedCP - local MaxAliveMissions = self.MaxAliveMissions * self.capgrouping + local MaxAliveMissions = self.MaxAliveMissions --* self.capgrouping local nogozoneset = self.NoGoZoneSet local ReadyFlightGroups = self.ReadyFlightGroups @@ -1200,9 +1221,11 @@ function EASYGCICAP:_AssignIntercept(Cluster) local zone = _data[2] -- Core.Zone#ZONE local zonecoord = zone:GetCoordinate() local name = _data[3] -- #string + local coa = AIRBASE:FindByName(name):GetCoalition() local distance = position:DistanceFromPointVec2(zonecoord) local airframes = airwing:CountAssets(true) - if distance < bestdistance and airframes >= wingsize then + local samecoalitionab = coa == self.coalition and true or false + if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then bestdistance = distance targetairwing = airwing targetawname = name @@ -1218,10 +1241,11 @@ function EASYGCICAP:_AssignIntercept(Cluster) local name = data.AirbaseName local zonecoord = data.Coordinate local airwing = wings[name][1] - + local coa = AIRBASE:FindByName(name):GetCoalition() + local samecoalitionab = coa == self.coalition and true or false local distance = position:DistanceFromPointVec2(zonecoord) local airframes = airwing:CountAssets(true) - if distance < bestdistance and airframes >= wingsize then + if distance < bestdistance and airframes >= wingsize and samecoalitionab == true then bestdistance = distance targetairwing = airwing -- Ops.Airwing#AIRWING targetawname = name @@ -1232,9 +1256,10 @@ function EASYGCICAP:_AssignIntercept(Cluster) -- Do we have a matching airwing? if targetairwing then local AssetCount = targetairwing:CountAssetsOnMission(MissionTypes,Cohort) + local missioncount = self:_CountAliveAuftrags() -- Enough airframes on mission already? self:T(self.lid.." Assets on Mission "..AssetCount) - if AssetCount <= MaxAliveMissions then + if missioncount < MaxAliveMissions then local repeats = repeatsonfailure local InterceptAuftrag = AUFTRAG:NewINTERCEPT(contact.group) :SetMissionRange(150) @@ -1260,6 +1285,8 @@ function EASYGCICAP:_AssignIntercept(Cluster) nogozoneset ) end + + table.insert(self.ListOfAuftrag,InterceptAuftrag) local assigned, rest = self:_TryAssignIntercept(ReadyFlightGroups,InterceptAuftrag,contact.group,wingsize) if not assigned then InterceptAuftrag:SetRequiredAssets(rest) @@ -1280,11 +1307,11 @@ function EASYGCICAP:_StartIntel() self:T(self.lid.."_StartIntel") -- Border GCI Detection local BlueAir_DetectionSetGroup = SET_GROUP:New() - BlueAir_DetectionSetGroup:FilterPrefixes( { self.EWRName } ) + BlueAir_DetectionSetGroup:FilterPrefixes( self.EWRName ) BlueAir_DetectionSetGroup:FilterStart() -- Intel type detection - local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.EWRName) + local BlueIntel = INTEL:New(BlueAir_DetectionSetGroup,self.coalitionname, self.alias) BlueIntel:SetClusterAnalysis(true,false,false) BlueIntel:SetForgetTime(300) BlueIntel:SetAcceptZones(self.GoZoneSet) @@ -1300,7 +1327,7 @@ function EASYGCICAP:_StartIntel() self:_AssignIntercept(Cluster) end - function BlueIntel:OnAfterNewCluster(From,Event,To,Cluster) + function BlueIntel:onbeforeNewCluster(From,Event,To,Cluster) AssignCluster(Cluster) end @@ -1351,6 +1378,20 @@ end -- @return #EASYGCICAP self function EASYGCICAP:onafterStatus(From,Event,To) self:T({From,Event,To}) + -- cleanup + local cleaned = false + local cleanlist = {} + for _,_auftrag in pairs(self.ListOfAuftrag) do + local auftrag = _auftrag -- Ops.Auftrag#AUFTRAG + if auftrag and (not (auftrag:IsCancelled() or auftrag:IsDone() or auftrag:IsOver())) then + table.insert(cleanlist,auftrag) + cleaned = true + end + end + if cleaned == true then + self.ListOfAuftrag = nil + self.ListOfAuftrag = cleanlist + end -- Gather Some Stats local function counttable(tbl) local count = 0 @@ -1403,12 +1444,14 @@ function EASYGCICAP:onafterStatus(From,Event,To) local text = "GCICAP "..self.alias text = text.."\nWings: "..wings.."\nSquads: "..squads.."\nCapPoints: "..caps.."\nAssets on Mission: "..assets.."\nAssets in Stock: "..instock text = text.."\nThreats: "..threatcount - text = text.."\nMissions: "..capmission+interceptmission + text = text.."\nAirWing managed Missions: "..capmission+awacsmission+tankermission+reconmission text = text.."\n - CAP: "..capmission - text = text.."\n - Intercept: "..interceptmission text = text.."\n - AWACS: "..awacsmission text = text.."\n - TANKER: "..tankermission text = text.."\n - Recon: "..reconmission + text = text.."\nSelf managed Missions:" + text = text.."\n - Mission Limit: "..self.MaxAliveMissions + text = text.."\n - Alert5+Intercept "..self:_CountAliveAuftrags() MESSAGE:New(text,15,"GCICAP"):ToAll():ToLogIf(self.debug) end self:__Status(30) diff --git a/Moose Development/Moose/Ops/Fleet.lua b/Moose Development/Moose/Ops/Fleet.lua index 050d06c9c..22d465164 100644 --- a/Moose Development/Moose/Ops/Fleet.lua +++ b/Moose Development/Moose/Ops/Fleet.lua @@ -334,6 +334,9 @@ function FLEET:onafterStatus(From, Event, To) -- Info --- ----------- + -- Display tactival overview. + self:_TacticalOverview() + -- General info: if self.verbose>=1 then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 28c99e7eb..1d77397e0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -59,9 +59,6 @@ -- @field #boolean prohibitAB Disallow (true) or allow (false) AI to use the afterburner. -- @field #boolean jettisonEmptyTanks Allow (true) or disallow (false) AI to jettison empty fuel tanks. -- @field #boolean jettisonWeapons Allow (true) or disallow (false) AI to jettison weapons if in danger. --- @field #table flightplans Flight plans for this group. --- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Currently active flight plan. --- @field Core.Pathline#PATHLINE taxipath Assigned taxi pathline. -- @field #number holdtime Time [s] flight is holding before going on final. Set to nil for indefinitely. -- -- @extends Ops.OpsGroup#OPSGROUP @@ -154,8 +151,7 @@ FLIGHTGROUP = { playerWarnings = {}, prohibitAB = false, jettisonEmptyTanks = true, - jettisonWeapons = true, -- that's actually a negative option like prohibitAB - flightplans = {}, + jettisonWeapons = true, -- that's actually a negative option like prohibitAB } @@ -221,7 +217,7 @@ FLIGHTGROUP.Players={} --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="1.0.2" +FLIGHTGROUP.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -273,7 +269,7 @@ function FLIGHTGROUP:New(group) self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() - self:SetFuelCriticalRTB() + self:SetFuelCriticalRTB() -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) @@ -745,7 +741,7 @@ function FLIGHTGROUP:SetProhibitAfterburner() if self:GetGroup():IsAlive() then self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB, true) end - return self + return self end --- Set if aircraft is allowed to use afterburner. @@ -756,7 +752,7 @@ function FLIGHTGROUP:SetAllowAfterburner() if self:GetGroup():IsAlive() then self:GetGroup():SetOption(AI.Option.Air.id.PROHIBIT_AB, false) end - return self + return self end --- Set if aircraft is allowed to drop empty fuel tanks - set to true to allow, and false to forbid it. @@ -969,18 +965,6 @@ function FLIGHTGROUP:SetDespawnAfterHolding() return self end ---- Add flightplan. --- @param #FLIGHTGROUP self --- @param Navigation.FlightPlan#FLIGHTPLAN FlightPlan The flight plan. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:AddFlightPlan(FlightPlan) - - self:T(self.lid..string.format("Adding flight plan %s", FlightPlan:GetName() )) - table.insert(self.flightplans, FlightPlan) - - return self -end - --- Check if flight is parking. -- @param #FLIGHTGROUP self @@ -991,7 +975,7 @@ function FLIGHTGROUP:IsParking(Element) if Element then is=Element.status==OPSGROUP.ElementStatus.PARKING end - return is + return is end --- Check if is taxiing to the runway. @@ -1015,7 +999,7 @@ function FLIGHTGROUP:IsAirborne(Element) if Element then is=Element.status==OPSGROUP.ElementStatus.AIRBORNE end - return is + return is end --- Check if flight is airborne or cruising. @@ -1035,7 +1019,7 @@ function FLIGHTGROUP:IsLanding(Element) if Element then is=Element.status==OPSGROUP.ElementStatus.LANDING end - return is + return is end --- Check if flight has landed and is now taxiing to its parking spot. @@ -1047,7 +1031,7 @@ function FLIGHTGROUP:IsLanded(Element) if Element then is=Element.status==OPSGROUP.ElementStatus.LANDED end - return is + return is end --- Check if flight has arrived at its destination parking spot. @@ -1059,7 +1043,7 @@ function FLIGHTGROUP:IsArrived(Element) if Element then is=Element.status==OPSGROUP.ElementStatus.ARRIVED end - return is + return is end --- Check if flight is inbound and traveling to holding pattern. @@ -1196,20 +1180,20 @@ function FLIGHTGROUP:ClearToLand(Delay) else if self:IsHolding() then - + -- Set flag. self:T(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) self.flaghold:Set(1) - + -- Not holding any more. self.Tholding=nil - + -- Clear holding stack. if self.stack then self.stack.flightgroup=nil self.stack=nil end - + end end @@ -1258,30 +1242,30 @@ function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() - + -- Is group alive? local alive=self:IsAlive() - + if alive then -- Update position. self:_UpdatePosition() - + -- Check if group has detected any units. self:_CheckDetectedUnits() - + -- Check ammo status. self:_CheckAmmoStatus() - + -- Check damage. self:_CheckDamage() - + -- Check if stuck while taxiing. self:_CheckStuck() - + -- Get current mission (if any). local mission=self:GetMissionCurrent() - + -- TODO: Check if group is waiting? if self:IsWaiting() then if self.Twaiting and self.dTwait then @@ -1292,19 +1276,38 @@ function FLIGHTGROUP:Status() end end end - + + --- check if we need to end holding + --self:T(self.lid.."Checking if we are holding at a holding point...") + if mission and mission.missionHoldingCoord and self.isHoldingAtHoldingPoint == true then + self:T(self.lid.."...yes") + if mission:IsReadyToPush() then + --self:T(self.lid.."Ready to push -> YES") + -- move flag to 1 + self.flaghold:Set(1) + -- Not waiting any more. + self.Twaiting=nil + self.dTwait=nil + self.isHoldingAtHoldingPoint = false + --else + --self:T(self.lid.."Ready to push -> NO!") + end + --else + --self:T(self.lid.."...no") + end + -- If mission, check if DCS task needs to be updated. if mission and mission.updateDCSTask then - + -- Orbit missions might need updates. if (mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER or mission:GetType()==AUFTRAG.Type.CAP) and mission.orbitVec2 then - + -- Get 2D vector of orbit target. local vec2=mission:GetTargetVec2() - + -- Heading. local hdg=mission:GetTargetHeading() - + -- Heading change? local hdgchange=false if mission.orbitLeg then @@ -1312,71 +1315,71 @@ function FLIGHTGROUP:Status() hdgchange=true end end - + -- Distance to previous position. local dist=UTILS.VecDist2D(vec2, mission.orbitVec2) - + -- Distance change? local distchange=dist>mission.orbitDeltaR - + -- Debug info. self:T3(self.lid..string.format("Checking orbit mission dist=%d meters", dist)) - + -- Check if distance is larger than threshold. if distchange or hdgchange then - + -- Debug info. self:T3(self.lid..string.format("Updating orbit!")) - + -- Update DCS task. This also sets the new mission.orbitVec2. local DCSTask=mission:GetDCSMissionTask() --DCS#Task - + -- Get task. local Task=mission:GetGroupWaypointTask(self) - + -- Reset current orbit task. self.controller:resetTask() - + -- Push task after one second. We need to give resetTask some time or it will not work! self:_SandwitchDCSTask(DCSTask, Task, false, 1) - + end elseif mission.type==AUFTRAG.Type.CAPTUREZONE then - + -- Get task. local Task=mission:GetGroupWaypointTask(self) - + -- Update task: Engage or get new zone. if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then self:_UpdateTask(Task, mission) end - - end + + end end - - + + -- TODO: _CheckParking() function - + -- Check if flight began to taxi (if it was parking). if self:IsParking() then for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element - + -- Check for parking spot. if element.parking then - + -- Get distance to assigned parking spot. local dist=self:_GetDistToParking(element.parking, element.unit:GetCoord()) - + -- Debug info. self:T(self.lid..string.format("Distance to parking spot %d = %.1f meters", element.parking.TerminalID, dist)) - + -- If distance >10 meters, we consider the unit as taxiing. At least for fighters, the initial distance seems to be around 1.8 meters. if dist>12 and element.engineOn then self:ElementTaxiing(element) end - + else --self:T(self.lid..string.format("Element %s is in PARKING queue but has no parking spot assigned!", element.name)) end @@ -1385,9 +1388,9 @@ function FLIGHTGROUP:Status() else -- Check damage. - self:_CheckDamage() + self:_CheckDamage() end - + --- -- Group --- @@ -1402,7 +1405,7 @@ function FLIGHTGROUP:Status() -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() - + -- ROE and Alarm State. local roe=self:GetROE() or -1 local rot=self:GetROT() or -1 @@ -1414,41 +1417,41 @@ function FLIGHTGROUP:Status() local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 local wpN=#self.waypoints or 0 local wpF=tostring(self.passedfinalwp) - + -- Speed. local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) - + -- Altitude. local alt=self.position and self.position.y or 0 - + -- Heading in degrees. - local hdg=self.heading or 0 - + local hdg=self.heading or 0 + -- TODO: GetFormation function. local formation=self.option.Formation or "unknown" - + -- Life points. local life=self.life or 0 - + -- Total ammo. local ammo=self:GetAmmoTot().Total - + -- Detected units. local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" - + -- Get cargo weight. local cargo=0 for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element cargo=cargo+element.weightCargo end - + -- Home and destination base. local home=self.homebase and self.homebase:GetName() or "unknown" local dest=self.destbase and self.destbase:GetName() or "unknown" local curr=self.currbase and self.currbase:GetName() or "N/A" - + -- Info text. local text=string.format("%s [%d/%d]: ROE/ROT=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f | Base=%s [%s-->%s]", fsmstate, nelem, Nelem, roe, rot, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, hdg, ammo, ndetected, cargo, curr, home, dest) @@ -1492,7 +1495,7 @@ function FLIGHTGROUP:Status() --- if self.verbose>=4 and alive then - + -- TODO: _Check distance travelled. -- Travelled distance since last check. @@ -1539,34 +1542,34 @@ function FLIGHTGROUP:Status() end - --- + --- -- Track flight --- if false then - + for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element - + local unit=element.unit - + if unit and unit:IsAlive() then - + local vec3=unit:GetVec3() - + if vec3 and element.pos then - + local id=UTILS.GetMarkID() - + trigger.action.lineToAll(-1, id, vec3, element.pos, {1,1,1,0.5}, 1) - + end - + element.pos=vec3 - + end - + end - + end --- @@ -1646,16 +1649,12 @@ function FLIGHTGROUP:Status() if not mission then self.Twaiting=nil self.dTwait=nil - + -- Check if group is done. -- TODO: Not sure why I introduced this here. self:_CheckGroupDone() end - if not (self.cargoTransport or mission) then - self:_CheckFlightPlans() - end - end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1682,13 +1681,13 @@ function FLIGHTGROUP:OnEventEngineStartup(EventData) -- TODO: what? else self:T3(self.lid..string.format("EVENT: Element %s started engines ==> taxiing (if AI)", element.name)) - + -- Element started engies. self:ElementEngineOn(element) - + -- Engines are on. element.engineOn=true - + --[[ -- TODO: could be that this element is part of a human flight group. -- Problem: when player starts hot, the AI does too and starts to taxi immidiately :( @@ -1758,8 +1757,6 @@ function FLIGHTGROUP:OnEventLanding(EventData) if element then self:T3(self.lid..string.format("EVENT: Element %s landed at %s ==> landed", element.name, airbasename)) self:ElementLanded(element, airbase) - else - self:T3(self.lid.."EVENT: Could not get element in landing event") end end @@ -1781,7 +1778,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) local element=self:GetElementByName(unitname) if element then - + -- Engines are off. element.engineOn=false @@ -1872,7 +1869,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) -- Debug info. self:T(self.lid..string.format("Element spawned %s", Element.name)) - + if Element.playerName then self:_InitPlayerData(Element.playerName) end @@ -1899,7 +1896,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) self:__ElementParking(0.11, Element) end end - + end --- On after "ElementParking" event. @@ -1931,7 +1928,7 @@ function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) self:__ElementEngineOn(0.5, Element) Element.engineOn=true end - + end --- On after "ElementEngineOn" event. @@ -1947,7 +1944,7 @@ function FLIGHTGROUP:onafterElementEngineOn(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ENGINEON) - + end --- On after "ElementTaxiing" event. @@ -1969,7 +1966,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAXIING) - + end --- On after "ElementTakeoff" event. @@ -1992,7 +1989,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) -- Trigger element airborne event. self:__ElementAirborne(0.01, Element) - + end --- On after "ElementAirborne" event. @@ -2008,7 +2005,7 @@ function FLIGHTGROUP:onafterElementAirborne(From, Event, To, Element) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.AIRBORNE) - + end --- On after "ElementLanded" event. @@ -2022,7 +2019,7 @@ 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")) - + -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) @@ -2036,15 +2033,15 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) end - end + end -- Despawn after landing. if self.despawnAfterLanding then - + if self.legion then - + if airbase and self.legion.airbase and airbase.AirbaseName==self.legion.airbase.AirbaseName then - + if self:IsLanded() then -- Everybody landed ==> Return to legion. Will despawn the last one. self:ReturnToLegion() @@ -2052,16 +2049,16 @@ function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) -- Despawn the element. self:DespawnElement(Element) end - + end - + else -- Despawn the element. self:DespawnElement(Element) - + end - end + end end --- On after "ElementArrived" event. @@ -2107,9 +2104,9 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) if self.flightcontrol and Element.parking then self.flightcontrol:SetParkingFree(Element.parking) end - + -- Call OPSGROUP function. This will remove the flightcontrol. Therefore, has to be after setting parking free. - self:GetParent(self).onafterElementDead(self, From, Event, To, Element) + self:GetParent(self).onafterElementDead(self, From, Event, To, Element) -- Not parking any more. Element.parking=nil @@ -2124,7 +2121,7 @@ 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) @@ -2137,7 +2134,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) 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("Has EPLRS = %s\n", tostring(self.isEPLRS)) + text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) text=text..string.format("Helicopter = %s\n", tostring(self.isHelo)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) @@ -2157,7 +2154,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) text=text..string.format("\n[%d] %s: callsign=%s, modex=%s, player=%s", i, element.name, tostring(element.callsign), tostring(element.modex), tostring(element.playerName)) end self:I(self.lid..text) - end + end -- Update position. self:_UpdatePosition() @@ -2167,7 +2164,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self.isDestroyed=false if self.isAI then - + -- TODO: Could be that element is spawned UNCONTROLLED. -- In that case, the commands are not yet used. -- This should be shifted to something like after ACTIVATED @@ -2180,12 +2177,12 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Set default EPLRS. self:SwitchEPLRS(self.option.EPLRS) - + -- Set default Invisible. - self:SwitchInvisible(self.option.Invisible) + self:SwitchInvisible(self.option.Invisible) -- Set default Immortal. - self:SwitchImmortal(self.option.Immortal) + self:SwitchImmortal(self.option.Immortal) -- Set Formation self:SwitchFormation(self.option.Formation) @@ -2218,7 +2215,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:__UpdateRoute(-0.5) else - + -- Set flightcontrol. if self.currbase then local flightcontrol=_DATABASE:GetFlightControl(self.currbase:GetName()) @@ -2226,12 +2223,12 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SetFlightControl(flightcontrol) else -- F10 other menu. - self:_UpdateMenu(0.5) + self:_UpdateMenu(0.5) end else self:_UpdateMenu(0.5) end - + end end @@ -2246,13 +2243,13 @@ function FLIGHTGROUP:onafterParking(From, Event, To) -- Get closest airbase local airbase=self:GetClosestAirbase() local airbasename=airbase:GetName() or "unknown" - + -- Debug info - self:T(self.lid..string.format("Flight is parking at airbase %s", airbasename)) - + self:T(self.lid..string.format("Flight is parking at airbase %s", airbasename)) + -- Set current airbase. self.currbase=airbase - + -- Set homebase to current airbase if not defined yet. -- This is necessary, e.g, when flights are spawned at an airbase because they do not have a takeoff waypoint. if not self.homebase then @@ -2276,7 +2273,7 @@ function FLIGHTGROUP:onafterParking(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.PARKING) end - + else self:T3(self.lid.."INFO: No flight control in onAfterParking!") end @@ -2301,12 +2298,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAKEOFF) else -- Human flights go to TAXI OUT queue. They will go to the ready for takeoff queue when they request it. - if self.controlstatus~=FLIGHTCONTROL.FlightStatus.TAXIOUT then - self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIOUT) - else - -- Update menu. This happens now if we confirmed taxiing and later began to taxi. (in the FLIGHTCONTROL:SetFlightStatus function the menu is only updated, when the controlstatus changed) - self:_UpdateMenu(0.2) - end + self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIOUT) end end @@ -2340,7 +2332,7 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) -- No current airbase any more. self.currbase=nil - + -- Cruising. self:__Cruise(-0.01) @@ -2359,25 +2351,25 @@ function FLIGHTGROUP:onafterCruise(From, Event, To) self.dTwait=nil if self.isAI then - + --- -- AI --- - + -- Check group Done. self:_CheckGroupDone(nil, 120) - + else - + --- -- CLIENT --- - + -- Had this commented out (forgot why, probably because it was not necessary) but re-enabling it because of carrier launch. self:_UpdateMenu(0.1) - + end - + end --- On after "Landing" event. @@ -2395,15 +2387,15 @@ function FLIGHTGROUP:onafterLanding(From, Event, To) -- Add flight to landing queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.LANDING) end - + -- Not holding any more. self.Tholding=nil - + -- Clear holding stack. if self.stack then self.stack.flightgroup=nil self.stack=nil - end + end end @@ -2454,25 +2446,25 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) -- Add flight to arrived queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - + if not self.isAI then -- Player landed. No despawn. return end - + --TODO: Check that current base is airwing base. local airwing=self:GetAirwing() --airwing:GetAirbaseName()==self.currbase:GetName() -- Check what to do. if airwing and not (self:IsPickingup() or self:IsTransporting()) then - + -- Debug info. self:T(self.lid..string.format("Airwing asset group %s arrived ==> Adding asset back to stock of airwing %s", self.groupname, airwing.alias)) - + -- Add the asset back to the airwing. --airwing:AddAsset(self.group, 1) self:ReturnToLegion(1) - + elseif self.isLandingAtAirbase then local Template=UTILS.DeepCopy(self.template) --DCS#Template @@ -2597,14 +2589,14 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) allowed=false elseif self:IsInUtero() then self:T(self.lid.."Update route denied. Group is INUTERO!") - allowed=false + allowed=false else -- Not airborne yet. Try again in 5 sec. self:T(self.lid.."Update route denied ==> checking back in 5 sec") trepeat=-5 allowed=false end - + -- Check if group is uncontrolled. If so, the mission task cannot be set yet! if allowed and self:IsUncontrolled() then self:T(self.lid.."Update route denied. Group is UNCONTROLLED!") @@ -2614,7 +2606,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) else trepeat=-5 end - allowed=false + allowed=false end -- Requested waypoint index <1. Something is seriously wrong here! @@ -2660,7 +2652,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n, N) self:T2(self.lid.."Allowing update route for Task: Hover") elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then -- For relocate - self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") + self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") elseif task.description and task.description=="Task_Land_At" then -- We allow this self:T2(self.lid.."Allowing update route for Task: Task_Land_At") @@ -2710,7 +2702,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N) n=n or self.currentwp+1 -- Max index. - N=N or #self.waypoints + N=N or #self.waypoints N=math.min(N, #self.waypoints) -- Waypoints. @@ -2737,7 +2729,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n, N) for i=n, N do table.insert(wp, self.waypoints[i]) end - + if wp[2] then self.speedWp=wp[2].speed end @@ -2818,7 +2810,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) if delay and delay>0 then -- Debug info. self:T(self.lid..string.format("Check FLIGHTGROUP [state=%s] done in %.3f seconds... (t=%.4f)", fsmstate, delay, timer.getTime())) - + -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._CheckGroupDone, self) else @@ -2834,9 +2826,9 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) -- Check if group is going for fuel. if self:IsGoing4Fuel() then self:T(self.lid.."Going for FUEL! Group NOT done...") - return + return end - + -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() @@ -2844,7 +2836,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) local nMissions=self:CountRemainingMissison() -- Number of cargo transports remaining. - local nTransports=self:CountRemainingTransports() + local nTransports=self:CountRemainingTransports() -- Number of paused missions. local nPaused=self:_CountPausedMissions() @@ -2862,11 +2854,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid..string.format("Landing at airbase %s! Group NOT done...", self.isLandingAtAirbase:GetName())) return end - + -- Group is waiting. if self:IsWaiting() then self:T(self.lid.."Waiting! Group NOT done...") - return + return end -- Debug info. @@ -2876,7 +2868,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) -- Or next waypoint index is the first waypoint. Could be that the group was on a mission and the mission waypoints were deleted. then the final waypoint is FALSE but no real waypoint left. -- Since we do not do ad infinitum, this leads to a rapid oscillation between UpdateRoute and CheckGroupDone! if self:HasPassedFinalWaypoint() or self:GetWaypointIndexNext()==1 then - + --- -- Final Waypoint PASSED --- @@ -2915,7 +2907,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) else -- Check if not parking (could be on ALERT5 and just spawned (current mission=nil) - if not self:IsParking() then + if not self:IsParking() then self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions)) self:__Wait(-1) end @@ -2927,14 +2919,14 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) --- -- Final Waypoint NOT PASSED - --- - + --- + -- Debug info. self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route in -0.01 sec", self:GetState())) - + -- Update route. self:__UpdateRoute(-0.01) - + end end @@ -2970,16 +2962,16 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) self:T(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:T(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:T(self.lid.."WARNING: Flight has already landed. RTB canceled!") - return false + return false end if not self.group:IsAirborne(true) then @@ -3022,7 +3014,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) Tsuspend=-10 allowed=false end - + if self.Twaiting and self.dTwait then self:T(self.lid..string.format("WARNING: Group is Waiting for a specific duration ==> RTB event is canceled", Nwp)) allowed=false @@ -3060,7 +3052,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Set the destination base. self.destbase=airbase - + -- Cancel all missions. self:CancelAllMissions() @@ -3093,19 +3085,19 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase) self:T(self.lid..string.format("ERROR: Wrong airbase coalition %d in LandAtAirbase() 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:T(self.lid.."WARNING: Currbase is already same as LandAtAirbase airbase. LandAtAirbase 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:T(self.lid.."WARNING: Flight has already landed. LandAtAirbase canceled!") - return false + return false end - - if self:IsParking() then + + if self:IsParking() then allowed=false Tsuspend=-30 self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 30 sec") @@ -3114,7 +3106,7 @@ function FLIGHTGROUP:onbeforeLandAtAirbase(From, Event, To, airbase) Tsuspend=-1 self:T(self.lid.."WARNING: Flight is parking. LandAtAirbase call delayed by 1 sec") end - + if Tsuspend and not allowed then self:__LandAtAirbase(Tsuspend, airbase) end @@ -3152,10 +3144,10 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Set current airbase. self.currbase=airbase - + -- Passed final waypoint! self:_PassedFinalWaypoint(true, "_LandAtAirbase") - + -- Not waiting any more. self.Twaiting=nil self.dTwait=nil @@ -3183,29 +3175,29 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Do we have a flight control? local fc=_DATABASE:GetFlightControl(airbase:GetName()) - + if fc and self.isAI then - + -- Get holding stack from flight control. local stack=fc:_GetHoldingStack(self) - - if stack then - + + if stack then + stack.flightgroup=self self.stack=stack - + -- Race track points. p0=stack.pos0 p1=stack.pos1 - + -- Debug marks. if false then p0:MarkToAll(string.format("%s: Holding stack P0, alt=%d meters", self:GetName(), p0.y)) p1:MarkToAll(string.format("%s: Holding stack P1, alt=%d meters", self:GetName(), p0.y)) end - + else - + end -- Set flightcontrol for this flight. @@ -3213,24 +3205,24 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) -- Add flight to inbound queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.INBOUND) - + -- Callsign. local callsign=self:GetCallsignName() - + -- Pilot calls inbound for landing. local text=string.format("%s, %s, inbound for landing", fc.alias, callsign) - + -- Radio message. fc:TransmissionPilot(text, self) - + -- Message text. local text=string.format("%s, %s, roger, hold at angels %d. Report entering the pattern.", callsign, fc.alias, stack.angels) - + -- Send message. fc:TransmissionTower(text, self, 10) - + end - + -- Some intermediate coordinate to climb to the default cruise alitude. local c1=c0:GetIntermediateCoordinate(p0, 0.25):SetAltitude(self.altitudeCruise, true) local c2=c0:GetIntermediateCoordinate(p0, 0.75):SetAltitude(self.altitudeCruise, true) @@ -3290,14 +3282,14 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) local wind = airbase:GetCoordinate():GetWind() rheading = -wind end - + local papp=airbase:GetCoordinate():Translate(x1, rheading):SetAltitude(h1) wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {TaskFinal}, "Final Approach") -- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach. local pland=airbase:GetCoordinate():Translate(x2, rheading):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - + elseif airbase:IsShip() or airbase:IsHelipad() then --- @@ -3316,7 +3308,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand) --self:ClearTasks() -- Just route the group. Respawn might happen when going from holding to final. - -- NOTE: I have delayed that here because of RTB calling _LandAtAirbase which resets current task immediately. + -- NOTE: I have delayed that here because of RTB calling _LandAtAirbase which resets current task immediately. -- So the stop flag change to 1 will not trigger TaskDone() and a current mission is not done either! -- Looks like a delay of 0.1 sec was not enough for the stopflag to take effect. Increasing this to 1.0 sec. -- This delay is looking better. Hopefully not any unwanted side effects in other situations. @@ -3345,12 +3337,12 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Duration, Altitude, Speed) Tsuspend=-30 allowed=false end - + -- Check for a current transport assignment. if self.cargoTransport and not self:IsLandedAt() then --self:T(self.lid..string.format("WARNING: Got current TRANSPORT assignment ==> WAIT event is suspended for 30 sec!")) --Tsuspend=-30 - --allowed=false + --allowed=false end -- Call wait again. @@ -3374,14 +3366,14 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) -- Group will orbit at its current position. local Coord=self:GetCoordinate() - + -- Set altitude: 1000 ft for helos and 10,000 ft for panes. if Altitude then Altitude=UTILS.FeetToMeters(Altitude) else Altitude=self.altitudeCruise - end - + end + -- Set speed. Speed=Speed or (self.isHelo and 20 or 250) @@ -3397,7 +3389,7 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) local TaskStop = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, Duration) local TaskCntr = self.group:TaskControlled(TaskOrbit, TaskStop) local TaskOver = self.group:TaskFunction("FLIGHTGROUP._FinishedWaiting", self) - + local DCSTasks if Duration or true then DCSTasks=self.group:TaskCombo({TaskCntr, TaskOver}) @@ -3405,10 +3397,10 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed) DCSTasks=self.group:TaskCombo({TaskOrbit, TaskOver}) end - + -- Set task. self:PushTask(DCSTasks) - + -- Set time stamp. self.Twaiting=timer.getAbsTime() @@ -3451,7 +3443,7 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) local wp9=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, "Refuel") self:Route({wp0, wp9}, 1) - + -- Set RTB on Bingo option. Currently DCS does not execute the refueling task if RTB_ON_BINGO is set to "NO RTB ON BINGO" self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, true) @@ -3467,9 +3459,9 @@ function FLIGHTGROUP:onafterRefueled(From, Event, To) -- Debug message. local text=string.format("Flight group finished refuelling") self:T(self.lid..text) - + -- Set RTB on Bingo option to "NO RTB ON BINGO" - self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) + self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) -- Check if flight is done. self:_CheckGroupDone(1) @@ -3486,7 +3478,7 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) -- Set holding flag to 0 (just in case). self.flaghold:Set(0) - + -- Despawn after holding. if self.despawnAfterHolding then if self.legion then @@ -3509,28 +3501,28 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) -- Set flight status to holding. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.HOLDING) - + if self.isAI then - + -- Callsign. local callsign=self:GetCallsignName() -- Pilot arrived at holding pattern. local text=string.format("%s, %s, arrived at holding pattern", self.flightcontrol.alias, callsign) - + if self.stack then text=text..string.format(", angels %d.", self.stack.angels) end - + -- Radio message. self.flightcontrol:TransmissionPilot(text, self) -- Message to flight local text=string.format("%s, roger, fly heading %d and wait for landing clearance", callsign, self.stack.heading) - + -- Radio message from tower. self.flightcontrol:TransmissionTower(text, self, 10) - + end elseif self.airboss then @@ -3663,7 +3655,7 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) -- Duration. --Duration=Duration or 600 - + self:T(self.lid..string.format("Landing at Coordinate for %s seconds", tostring(Duration))) Coordinate=Coordinate or self:GetCoordinate() @@ -3673,7 +3665,7 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) local Task=self:NewTaskScheduled(DCStask, 1, "Task_Land_At", 0) self:TaskExecute(Task) - + end --- On after "FuelLow" event. @@ -3749,62 +3741,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) end end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Flightplan Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set flightplan and add waypoints. --- @param #FLIGHTGROUP self --- @param Navigation.FlightPlan#FLIGHTPLAN FlightPlan Flight plan. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:_SetFlightPlan(FlightPlan) - - self:I(self.lid..string.format("Setting flightplan %s", FlightPlan.alias)) - - self.flightplan=FlightPlan - - self:SetDestinationbase(self.flightplan.destinationAirbase) - - for i,_fix in pairs(FlightPlan.fixes) do - local fix=_fix --Navigation.Point#NAVFIX - - --fix.coordinate - local speed=fix:GetSpeed() or FlightPlan:GetCruiseSpeed() - - local altitude=fix:GetAltitude() or FlightPlan:GetCruiseAltitude() - - local wp=self:AddWaypoint(fix.vector, speed, nil, altitude or 10000, false) - wp.flightplan=FlightPlan - - wp.name=fix.name - - end - -end - ---- Check flightplans. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:_CheckFlightPlans() - - if self.flightplan then - return - end - - - for i,_flightplan in pairs(self.flightplans) do - local flightplan=_flightplan --Navigation.FlightPlan#FLIGHTPLAN - - if flightplan then - - self:_SetFlightPlan(flightplan) - - break - end - end - -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3848,7 +3784,7 @@ function FLIGHTGROUP._OnFinal(group, flightgroup) flightgroup:T2(flightgroup.lid..string.format("Group on final approach")) local fc=flightgroup.flightcontrol - + if fc and fc:IsControlling(flightgroup) then fc:_FlightOnFinal(flightgroup) end @@ -3870,7 +3806,7 @@ end -- @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 @@ -3886,117 +3822,125 @@ 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`. +-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:_InitGroup(Template) +function FLIGHTGROUP:_InitGroup(Template, Delay) - -- First check if group was already initialized. - if self.groupinitialized then - self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") - return - end - - -- Group object. - local group=self.group --Wrapper.Group#GROUP - - -- Helo group. - self.isHelo=group:IsHelicopter() - - -- Max speed in km/h. - self.speedMax=group:GetSpeedMax() - - -- Is group mobile? - if self.speedMax and self.speedMax>3.6 then - self.isMobile=true + if Delay and Delay>0 then + self:ScheduleOnce(Delay, FLIGHTGROUP._InitGroup, self, Template, 0) else - self.isMobile=false - self.speedMax = 0 - end - -- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings. - local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380) - - -- Cruise speed: 70% of max speed but within limit. - self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit) - - -- Group ammo. - self.ammo=self:GetAmmoTot() - - -- Get template of group. - local template=Template or self:_GetTemplate() - - -- Is (template) group uncontrolled. - self.isUncontrolled=template~=nil and template.uncontrolled or false - - -- Is (template) group late activated. - self.isLateActivated=template~=nil and template.lateActivation or false - - if template then - - -- Radio parameters from template. Default is set on spawn if not modified by user. - 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=template.units[1].callsign - --self:I({callsign=callsign}) - if type(callsign)=="number" then -- Sometimes callsign is just "101". - local cs=tostring(callsign) - callsign={} - callsign[1]=cs:sub(1,1) - callsign[2]=cs:sub(2,2) - callsign[3]=cs:sub(3,3) + -- First check if group was already initialized. + if self.groupinitialized then + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") + return end - self.callsign.NumberSquad=tonumber(callsign[1]) - self.callsign.NumberGroup=tonumber(callsign[2]) - self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) + + -- Group object. + local group=self.group --Wrapper.Group#GROUP + + -- Helo group. + self.isHelo=group:IsHelicopter() + + -- Max speed in km/h. + self.speedMax=group:GetSpeedMax() + -- Is group mobile? + if self.speedMax and self.speedMax>3.6 then + self.isMobile=true + else + self.isMobile=false + self.speedMax = 0 + end + + -- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings. + local speedCruiseLimit=self.isHelo and UTILS.KnotsToKmph(110) or UTILS.KnotsToKmph(380) + + -- Cruise speed: 70% of max speed but within limit. + self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit) + + -- Group ammo. + self.ammo=self:GetAmmoTot() + + -- Get template of group. + local template=Template or self:_GetTemplate() + + -- Is (template) group uncontrolled. + self.isUncontrolled=template~=nil and template.uncontrolled or false + + -- Is (template) group late activated. + self.isLateActivated=template~=nil and template.lateActivation or false + + if template then + + -- Radio parameters from template. Default is set on spawn if not modified by user. + 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=template.units[1].callsign + --self:I({callsign=callsign}) + if type(callsign)=="number" then -- Sometimes callsign is just "101". + local cs=tostring(callsign) + callsign={} + callsign[1]=cs:sub(1,1) + callsign[2]=cs:sub(2,2) + callsign[3]=cs:sub(3,3) + end + self.callsign.NumberSquad=tonumber(callsign[1]) + self.callsign.NumberGroup=tonumber(callsign[2]) + self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) + + end + + -- Set default formation. + if self.isHelo then + self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 + else + self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group + end + + -- Default TACAN off. + if not self.tacanDefault then + self:SetDefaultTACAN(nil, nil, nil, nil, true) + end + if not self.tacan then + self.tacan=UTILS.DeepCopy(self.tacanDefault) + end + + -- Is this purely AI? + self.isAI=not self:_IsHuman(group) + + -- Create Menu. + if not self.isAI then + self.menu=self.menu or {} + self.menu.atc=self.menu.atc or {} --#table + self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") --Core.Menu#MENU_GROUP + self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group, "Help", self.menu.atc.root) --Core.Menu#MENU_GROUP + end + + -- Units of the group. + local units=self.group:GetUnits() + + -- DCS group. + local dcsgroup=Group.getByName(self.groupname) + local size0=dcsgroup:getInitialSize() + + -- Quick check. + if #units~=size0 then + self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + end + + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) + end + + -- Init done. + self.groupinitialized=true end - - -- Set default formation. - if self.isHelo then - self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 - else - self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group - end - - -- Default TACAN off. - self:SetDefaultTACAN(nil, nil, nil, nil, true) - self.tacan=UTILS.DeepCopy(self.tacanDefault) - - -- Is this purely AI? - self.isAI=not self:_IsHuman(group) - - -- Create Menu. - if not self.isAI then - self.menu=self.menu or {} - self.menu.atc=self.menu.atc or {} --#table - self.menu.nav=self.menu.nav or {} --#table - self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") --Core.Menu#MENU_GROUP - self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group, "Help", self.menu.atc.root) --Core.Menu#MENU_GROUP - self.menu.nav.root=self.menu.nav.root or MENU_GROUP:New(self.group, "Navigation") --Core.Menu#MENU_GROUP - end - - -- Units of the group. - local units=self.group:GetUnits() - - -- DCS group. - local dcsgroup=Group.getByName(self.groupname) - local size0=dcsgroup:getInitialSize() - - -- Quick check. - if #units~=size0 then - self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) - end - - -- Add elemets. - for _,unit in pairs(units) do - self:_AddElementByName(unit:GetName()) - end - - -- Init done. - self.groupinitialized=true - + return self end @@ -4207,7 +4151,7 @@ end -- @return #boolean Air start? function FLIGHTGROUP:IsTakeoffAir() - local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) + local wp=self.waypoints0 and self.waypoints0[1] or nil --self:GetWaypoint(1) if wp then @@ -4264,51 +4208,6 @@ function FLIGHTGROUP:IsLandingAirbase(wp) return nil end ---- Initialize Mission Editor waypoints. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:InitWaypoints() - - -- Template waypoints. - self.waypoints0=self.group:GetTemplateRoutePoints() - - -- Waypoints - self.waypoints={} - - for index,wp in pairs(self.waypoints0) do - - local waypoint=self:_CreateWaypoint(wp) - self:_AddWaypoint(waypoint) - - end - - -- 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 - - -- 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")) - - -- Update route. - if #self.waypoints>0 then - - -- Check if only 1 wp? - if #self.waypoints==1 then - self:_PassedFinalWaypoint(true, "FLIGHTGROUP:InitWaypoints #self.waypoints==1") - end - - end - - return self -end --- Add an AIR waypoint to the flight plan. -- @param #FLIGHTGROUP self @@ -4321,17 +4220,17 @@ end function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) -- Create coordinate. - local coordinate=self:_CoordinateFromObject(Coordinate) + local coordinate=self:_CoordinateFromObject(Coordinate) -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) - + -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - + -- Debug info. self:T3(self.lid..string.format("Waypoint Speed=%.1f knots", Speed)) - + -- Alt type default is barometric (ASL). For helos we use radar (AGL). local alttype=COORDINATE.WaypointAltType.BARO if self.isHelo then @@ -4432,7 +4331,7 @@ end function FLIGHTGROUP:GetPlayerName() local playerElement=self:GetPlayerElement() - + if playerElement then return playerElement.playerName end @@ -4453,10 +4352,10 @@ function FLIGHTGROUP:_SetElementParkingAt(Element, Spot) -- Debug info. self:T(self.lid..string.format("Element %s is parking on spot %d", Element.name, Spot.TerminalID)) - + -- Get flightcontrol. local fc=_DATABASE:GetFlightControl(Spot.AirbaseName) - + if fc and not self.flightcontrol then self:SetFlightControl(fc) end @@ -4615,10 +4514,10 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) local distmin=math.huge for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot - + -- Distance to spot. dist=coord:Get2DDistance(parking.Coordinate) - + if dist=2 then - local text=string.format("Updating MENU: State=%s, ATC=%s [%s]", self:GetState(), + local text=string.format("Updating MENU: State=%s, ATC=%s [%s]", self:GetState(), self.flightcontrol and self.flightcontrol.airbasename or "None", self.flightcontrol and self.flightcontrol:GetFlightStatus(self) or "Unknown") - + -- Message to group. MESSAGE:New(text, 5):ToGroup(self.group) self:I(self.lid..text) end - + -- Get current position of player. local position=self:GetCoordinate(nil, player.name) - + -- Get all FLIGHTCONTROLS local fc={} for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS) do local flightcontrol=_flightcontrol --OPS.FlightControl#FLIGHTCONTROL - + -- Get coord of airbase. local coord=flightcontrol:GetCoordinate() - + -- Distance to flight. local dist=coord:Get2DDistance(position) - + -- Add to table. table.insert(fc, {airbasename=airbasename, dist=dist}) end - + -- Sort table wrt distance to airbases. local function _sort(a,b) return a.dist0 then + local N=self:CountMissionsInQueue(mtype) + text=text..string.format(" - %s: %d [Running=%d]\n", mtype, n, N) + end + end + + + local Ntransports=#self.transportqueue + if Ntransports>0 then + text=text..string.format("Transports: %d\n", Ntransports) + for _,_transport in pairs(self.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + text=text..string.format(" - %s", transport:GetState()) + end + end + + -- Message to coalition. + MESSAGE:New(text, 60, nil, true):ToCoalition(self:GetCoalition()) + + + end + +end --- Check if an asset is currently on a mission (STARTED or EXECUTING). -- @param #LEGION self @@ -2563,6 +2640,8 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti local RangeMax = RangeMax or 0 local InRange=(RangeMax and math.max(RangeMax, Rmax) or Rmax) >= TargetDistance + --env.info(string.format("Range TargetDist=%.1f Rmax=%.1f RangeMax=%.1f InRange=%s", TargetDistance, Rmax, RangeMax, tostring(InRange))) + return InRange end @@ -2628,7 +2707,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti else Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of category", Cohort.name)) return false - end + end if can then can=CheckAttribute(Cohort) @@ -2684,7 +2763,7 @@ function LEGION._CohortCan(Cohort, MissionType, Categories, Attributes, Properti else Cohort:T(Cohort.lid..string.format("Cohort %s cannot because of max weight", Cohort.name)) return false - end + end return nil end @@ -2728,6 +2807,8 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, -- Check if cohort can do the mission. local can=LEGION._CohortCan(cohort, MissionTypeRecruit, Categories, Attributes, Properties, WeaponTypes, TargetVec2, RangeMax, RefuelSystem, CargoWeight, MaxWeight) + + --env.info(string.format("RecruitCohortAssets %s Cohort=%s can=%s", MissionTypeRecruit, cohort:GetName(), tostring(can))) -- Check OnDuty, capable, in range and refueling type (if TANKER). if can then @@ -2744,6 +2825,12 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt, end + -- Break if no assets could be found + if #Assets==0 then + --env.info(string.format("LEGION.RecruitCohortAssets: No assets could be recruited for mission type %s [Nmin=%s, Nmax=%s]", MissionTypeRecruit, tostring(NreqMin), tostring(NreqMax))) + return false, {}, {} + end + -- Now we have a long list with assets. LEGION._OptimizeAssetSelection(Assets, MissionTypeOpt, TargetVec2, false, TotalWeight) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 0ed715fc9..85f03853d 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -44,6 +44,7 @@ -- @field #number pathCorridor Path corrdidor width in meters. -- @field #boolean ispathfinding If true, group is currently path finding. -- @field #NAVYGROUP.Target engage Engage target. +-- @field #boolean intowindold Use old calculation to determine heading into wind. -- @extends Ops.OpsGroup#OPSGROUP --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson @@ -90,7 +91,7 @@ NAVYGROUP = { --- NavyGroup version. -- @field #string version -NAVYGROUP.version="1.0.2" +NAVYGROUP.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -393,7 +394,8 @@ function NAVYGROUP: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.UnitLost, self.OnEventRemoveUnit) -- Start the status monitoring. self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) @@ -455,6 +457,18 @@ function NAVYGROUP:SetPathfindingOff() return self end +--- Set if old into wind calculation is used when carrier turns into the wind for a recovery. +-- @param #NAVYGROUP self +-- @param #boolean SwitchOn If `true` or `nil`, use old into wind calculation. +-- @return #NAVYGROUP self +function NAVYGROUP:SetIntoWindLegacy( SwitchOn ) + if SwitchOn==nil then + SwitchOn=true + end + self.intowindold=SwitchOn + return self +end + --- Add a *scheduled* task. -- @param #NAVYGROUP self @@ -600,6 +614,58 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset) return recovery end +--- Get "Turn Into Wind" data. You can specify a certain ID. +-- @param #NAVYGROUP self +-- @param #number TID (Optional) Turn Into wind ID. If not given, the currently open "Turn into Wind" data is return (if there is any). +-- @return #NAVYGROUP.IntoWind Turn into window data table. +function NAVYGROUP:GetTurnIntoWind(TID) + + if TID then + + -- Look for a specific ID. + for _,_turn in pairs(self.Qintowind) do + local turn=_turn --#NAVYGROUP.IntoWind + if turn.Id==TID then + return turn + end + end + + else + + -- Return currently open window. + return self.intowind + + end + + return nil +end + +--- Extend duration of turn into wind. +-- @param #NAVYGROUP self +-- @param #number Duration Duration in seconds. Default 300 sec. +-- @param #NAVYGROUP.IntoWind TurnIntoWind (Optional) Turn into window data table. If not given, the currently open one is used (if there is any). +-- @return #NAVYGROUP self +function NAVYGROUP:ExtendTurnIntoWind(Duration, TurnIntoWind) + + Duration=Duration or 300 + + -- ID of turn or nil + local TID=TurnIntoWind and TurnIntoWind.Id or nil + + -- Get turn data. + local turn=self:GetTurnIntoWind(TID) + + if turn then + turn.Tstop=turn.Tstop+Duration + self:T(self.lid..string.format("Extending turn into wind by %d seconds. New stop time is %s", Duration, UTILS.SecondsToClock(turn.Tstop))) + else + self:E(self.lid.."Could not get turn into wind to extend!") + end + + return self +end + + --- Remove steam into wind window from queue. If the window is currently active, it is stopped first. -- @param #NAVYGROUP self -- @param #NAVYGROUP.IntoWind IntoWindData Turn into window data table. @@ -709,7 +775,7 @@ end --- Update status. -- @param #NAVYGROUP self -function NAVYGROUP:Status(From, Event, To) +function NAVYGROUP:Status() -- FSM state. local fsmstate=self:GetState() @@ -912,6 +978,35 @@ function NAVYGROUP:Status(From, Event, To) end + --- + -- Elements + --- + + if self.verbose>=2 then + local text="Elements:" + for i,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + + local name=element.name + local status=element.status + local unit=element.unit + 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, 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!" + end + self:I(self.lid..text) + end + --- -- Engage Detected Targets --- @@ -975,7 +1070,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Debug info. if self.verbose>=1 then - local text=string.format("Initialized Navy Group %s:\n", self.groupname) + local text=string.format("Initialized Navy Group %s [GID=%d]:\n", self.groupname, self.group:GetID()) 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)) @@ -1203,7 +1298,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Depth) if self.verbose>=10 then for i=1,#waypoints do local wp=waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - local text=string.format("%s Waypoint [%d] UID=%d speed=%d", self.groupname, i-1, wp.uid or -1, wp.speed) + local text=string.format("%s Waypoint [%d] UID=%d speed=%d m/s", self.groupname, i-1, wp.uid or -1, wp.speed) self:I(self.lid..text) COORDINATE:NewFromWaypoint(wp):MarkToAll(text) end @@ -1775,81 +1870,96 @@ 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`. +-- @param #number Delay Delay in seconds before group is initialized. Default `nil`, *i.e.* instantaneous. -- @return #NAVYGROUP self -function NAVYGROUP:_InitGroup(Template) +function NAVYGROUP:_InitGroup(Template, Delay) - -- First check if group was already initialized. - if self.groupinitialized then - self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") - return - end - - -- Get template of group. - local template=Template or self:_GetTemplate() - - -- Ships are always AI. - self.isAI=true - - -- Is (template) group late activated. - self.isLateActivated=template.lateActivation - - -- Naval groups cannot be uncontrolled. - self.isUncontrolled=false - - -- Max speed in km/h. - self.speedMax=self.group:GetSpeedMax() - - -- Is group mobile? - if self.speedMax and self.speedMax>3.6 then - self.isMobile=true + if Delay and Delay>0 then + -- Delayed call + self:ScheduleOnce(Delay, NAVYGROUP._InitGroup, self, Template, 0) else - self.isMobile=false - self.speedMax = 0 - end - -- Cruise speed: 70% of max speed. - self.speedCruise=self.speedMax*0.7 + -- First check if group was already initialized. + if self.groupinitialized then + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") + return + end - -- Group ammo. - self.ammo=self:GetAmmoTot() + -- Get template of group. + local template=Template or self:_GetTemplate() - -- 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(template.units[1].frequency)/1000000 - self.radio.Modu=tonumber(template.units[1].modulation) + -- Ships are always AI. + self.isAI=true + + -- Is (template) group late activated. + self.isLateActivated=template.lateActivation + + -- Naval groups cannot be uncontrolled. + self.isUncontrolled=false + + -- Max speed in km/h. + self.speedMax=self.group:GetSpeedMax() + + -- Is group mobile? + if self.speedMax and self.speedMax>3.6 then + self.isMobile=true + else + self.isMobile=false + self.speedMax = 0 + end + + -- Cruise speed: 70% of max speed. + self.speedCruise=self.speedMax*0.7 + + -- Group ammo. + self.ammo=self:GetAmmoTot() + + -- 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(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" + self.option.Formation=self.optionDefault.Formation - -- Set default formation. No really applicable for ships. - self.optionDefault.Formation="Off Road" - self.option.Formation=self.optionDefault.Formation - - -- Default TACAN off. - self:SetDefaultTACAN(nil, nil, nil, nil, true) - self.tacan=UTILS.DeepCopy(self.tacanDefault) + -- Default TACAN off (we check if something is set already to keep those values in case of respawn) + if not self.tacanDefault then + self:SetDefaultTACAN(nil, nil, nil, nil, true) + end + if not self.tacan then + self.tacan=UTILS.DeepCopy(self.tacanDefault) + end + + -- Default ICLS off. + if not self.iclsDefault then + self:SetDefaultICLS(nil, nil, nil, true) + end + if not self.icls then + self.icls=UTILS.DeepCopy(self.iclsDefault) + end + + -- Get all units of the group. + local units=self.group:GetUnits() - -- Default ICLS off. - self:SetDefaultICLS(nil, nil, nil, true) - self.icls=UTILS.DeepCopy(self.iclsDefault) - - -- Get all units of the group. - local units=self.group:GetUnits() - - -- DCS group. - local dcsgroup=Group.getByName(self.groupname) - local size0=dcsgroup:getInitialSize() - - -- Quick check. - if #units~=size0 then - self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + -- DCS group. + local dcsgroup=Group.getByName(self.groupname) + local size0=dcsgroup:getInitialSize() + + -- Quick check. + if #units~=size0 then + self:E(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units!", #units, size0)) + end + + -- Add elemets. + for _,unit in pairs(units) do + self:_AddElementByName(unit:GetName()) + end + + -- Init done. + self.groupinitialized=true end - -- Add elemets. - for _,unit in pairs(units) do - self:_AddElementByName(unit:GetName()) - end - - -- Init done. - self.groupinitialized=true - return self end @@ -2068,8 +2178,43 @@ end --- Get heading of group into the wind. -- @param #NAVYGROUP self -- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway. +-- @param #number vdeck Desired wind speed on deck in Knots. -- @return #number Carrier heading in degrees. -function NAVYGROUP:GetHeadingIntoWind_old(Offset) +-- @return #number Carrier speed in knots. +function NAVYGROUP:GetHeadingIntoWind_old(Offset, vdeck) + + local function adjustDegreesForWindSpeed(windSpeed) + local degreesAdjustment = 0 + -- the windspeeds are in m/s + -- +0 degrees at 15m/s = 37kts + -- +0 degrees at 14m/s = 35kts + -- +0 degrees at 13m/s = 33kts + -- +4 degrees at 12m/s = 31kts + -- +4 degrees at 11m/s = 29kts + -- +4 degrees at 10m/s = 27kts + -- +4 degrees at 9m/s = 27kts + -- +4 degrees at 8m/s = 27kts + -- +8 degrees at 7m/s = 27kts + -- +8 degrees at 6m/s = 27kts + -- +8 degrees at 5m/s = 26kts + -- +20 degrees at 4m/s = 26kts + -- +20 degrees at 3m/s = 26kts + -- +30 degrees at 2m/s = 26kts 1s + + if windSpeed > 0 and windSpeed < 3 then + degreesAdjustment = 30 + elseif windSpeed >= 3 and windSpeed < 5 then + degreesAdjustment = 20 + elseif windSpeed >= 5 and windSpeed < 8 then + degreesAdjustment = 8 + elseif windSpeed >= 8 and windSpeed < 13 then + degreesAdjustment = 4 + elseif windSpeed >= 13 then + degreesAdjustment = 0 + end + + return degreesAdjustment + end Offset=Offset or 0 @@ -2077,7 +2222,7 @@ function NAVYGROUP:GetHeadingIntoWind_old(Offset) local windfrom, vwind=self:GetWind() -- Actually, we want the runway in the wind. - local intowind=windfrom-Offset + local intowind = windfrom - Offset + adjustDegreesForWindSpeed(vwind) -- If no wind, take current heading. if vwind<0.1 then @@ -2088,8 +2233,11 @@ function NAVYGROUP:GetHeadingIntoWind_old(Offset) if intowind<0 then intowind=intowind+360 end + + -- Speed of carrier in m/s but at least 4 knots. + local vtot = math.max(vdeck-UTILS.MpsToKnots(vwind), 4) - return intowind + return intowind, vtot end @@ -2099,7 +2247,8 @@ end -- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway. -- @param #number vdeck Desired wind speed on deck in Knots. -- @return #number Carrier heading in degrees. -function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck) +-- @return #number Carrier speed in knots. +function NAVYGROUP:GetHeadingIntoWind_new(Offset, vdeck) -- Default offset angle. Offset=Offset or 0 @@ -2180,6 +2329,25 @@ function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck) return intowind, v end +--- Get heading of group into the wind. This minimizes the cross wind for an angled runway. +-- Implementation based on [Mags & Bami](https://magwo.github.io/carrier-cruise/) work. +-- @param #NAVYGROUP self +-- @param #number Offset Offset angle in degrees, e.g. to account for an angled runway. +-- @param #number vdeck Desired wind speed on deck in Knots. +-- @return #number Carrier heading in degrees. +-- @return #number Carrier speed in knots. +function NAVYGROUP:GetHeadingIntoWind(Offset, vdeck) + + if self.intowindold then + --env.info("FF use OLD into wind") + return self:GetHeadingIntoWind_old(Offset, vdeck) + else + --env.info("FF use NEW into wind") + return self:GetHeadingIntoWind_new(Offset, vdeck) + end + +end + --- Find free path to next waypoint. -- @param #NAVYGROUP self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 4db2e974d..463f9bfa7 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -513,7 +513,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="1.0.1" +OPSGROUP.version="1.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1047,7 +1047,7 @@ function OPSGROUP:SetReturnToLegion(Switch) else self.legionReturn=true end - self:T(self.lid..string.format("Setting ReturnToLetion=%s", tostring(self.legionReturn))) + self:T(self.lid..string.format("Setting ReturnToLegion=%s", tostring(self.legionReturn))) return self end @@ -1339,8 +1339,9 @@ end -- @param Core.Point#COORDINATE TargetCoord Coordinate of the target. -- @param #number WeaponBitType Weapon type. -- @param Core.Point#COORDINATE RefCoord Reference coordinate. +-- @param #table SurfaceTypes Valid surfaces types of the coordinate. Default any (nil). -- @return Core.Point#COORDINATE Coordinate in weapon range -function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord) +function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord, SurfaceTypes) local coordInRange=nil --Core.Point#COORDINATE @@ -1349,35 +1350,58 @@ function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord) -- Get weapon range. local weapondata=self:GetWeaponData(WeaponBitType) + -- Heading intervals to search for a possible new coordinate in range. + local dh={0, -5, 5, -10, 10, -15, 15, -20, 20, -25, 25, -30, 30, -35, 35, -40, 40, -45, 45, -50, 50, -55, 55, -60, 60, -65, 65, -70, 70, -75, 75, -80, 80} + + -- Function that checks if the given surface type is valid + local function _checkSurface(point) + if SurfaceTypes then + local stype=point:GetSurfaceType() + for _,sf in pairs(SurfaceTypes) do + if sf==stype then + return true + end + end + return false + else + return true + end + end + if weapondata then -- Heading to target. - local heading=RefCoord:HeadingTo(TargetCoord) + local heading=TargetCoord:HeadingTo(RefCoord) -- Distance to target. local dist=RefCoord:Get2DDistance(TargetCoord) + + local range=nil + if dist>weapondata.RangeMax then + range=weapondata.RangeMax + self:T(self.lid..string.format("Out of max range = %.1f km by %.1f km for weapon %s", weapondata.RangeMax/1000, (weapondata.RangeMax-dist)/1000, tostring(WeaponBitType))) + elseif distweapondata.RangeMax then - - local d=(dist-weapondata.RangeMax)*1.05 - - -- New waypoint coord. - coordInRange=RefCoord:Translate(d, heading) - - -- Debug info. - self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType))) - elseif dist0 then + self:ScheduleOnce(Delay, OPSGROUP._Spawn, self, 0, Template) + else -- Debug output. - self:T({Template=Template}) + self:T2({Template=Template}) -- Spawn new group. self.group=_DATABASE:Spawn(Template) + --local countryID=self.group:GetCountry() + --local categoryID=self.group:GetCategory() + --local dcsgroup=coalition.addGroup(countryID, categoryID, Template) -- Set DCS group and controller. self.dcsgroup=self:GetDCSGroup() @@ -7800,7 +7868,6 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) self.isDead=false self.isDestroyed=false - self.groupinitialized=false self.wpcounter=1 self.currentwp=1 @@ -7808,15 +7875,12 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Init waypoints. self:_InitWaypoints() - -- Init Group. - self:_InitGroup(Template) + -- Init Group. This call is delayed because NAVY groups did not like to be initialized just yet (group did not contain any units). + self:_InitGroup(Template, 0.001) -- Reset events. - --self:ResetEvents() - + --self:ResetEvents() end - - return self end --- On after "InUtero" event. @@ -7836,24 +7900,6 @@ end -- @param #string To To state. function OPSGROUP:onafterDamaged(From, Event, To) self:T(self.lid..string.format("Group damaged at t=%.3f", timer.getTime())) - - --[[ - local lifemin=nil - for _,_element in pairs(self.elements) do - local element=_element --#OPSGROUP.Element - if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then - local life, life0=self:GetLifePoints(element) - if lifemin==nil or life0 then - if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then + if not self:IsAttacked() and self.Tblu>=self.threatlevelCapture then self:Attacked(coalition.side.BLUE) end @@ -1337,7 +1368,7 @@ function OPSZONE:EvaluateZone() if Nred>0 then - if not self:IsAttacked() and self.Tnut>=self.threatlevelCapture then + if not self:IsAttacked() and self.Tred>=self.threatlevelCapture then -- Red is attacking blue zone. self:Attacked(coalition.side.RED) end diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index f6a90c9a9..e178b58f0 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -80,6 +80,7 @@ -- @field #boolean smokeownposition -- @field #table SmokeOwn -- @field #boolean smokeaveragetargetpos +-- @field #boolean reporttostringbullsonly -- @extends Core.Fsm#FSM --- @@ -105,7 +106,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.1.23", + version = "0.1.26", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -133,7 +134,8 @@ PLAYERRECCE = { TargetCache = nil, smokeownposition = false, SmokeOwn = {}, - smokeaveragetargetpos = false, + smokeaveragetargetpos = true, + reporttostringbullsonly = true, } --- @@ -152,7 +154,8 @@ PLAYERRECCE.LaserRelativePos = { ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 }, - ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 } + ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 }, + ["OH58D"] = {x = 0, y = 2.8, z = 0}, } --- @@ -164,7 +167,8 @@ PLAYERRECCE.MaxViewDistance = { ["SA342Minigun"] = 8000, ["SA342L"] = 8000, ["Ka-50"] = 8000, - ["Ka-50_3"] = 8000, + ["Ka-50_3"] = 8000, + ["OH58D"] = 8000, } --- @@ -176,7 +180,8 @@ PLAYERRECCE.Cameraheight = { ["SA342Minigun"] = 2.85, ["SA342L"] = 2.85, ["Ka-50"] = 0.5, - ["Ka-50_3"] = 0.5, + ["Ka-50_3"] = 0.5, + ["OH58D"] = 4.25, } --- @@ -188,7 +193,8 @@ PLAYERRECCE.CanLase = { ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, ["Ka-50"] = true, - ["Ka-50_3"] = true, + ["Ka-50_3"] = true, + ["OH58D"] = false, -- has onboard and useable laser } --- @@ -236,6 +242,8 @@ function PLAYERRECCE:New(Name, Coalition, PlayerSet) self.minthreatlevel = 0 + self.reporttostringbullsonly = true + self.TForget = 600 self.TargetCache = FIFO:New() @@ -542,7 +550,7 @@ function PLAYERRECCE:SetAttackSet(AttackSet) return self end ----[Internal] Check Gazelle camera in on +---[Internal] Check Helicopter camera in on -- @param #PLAYERRECCE self -- @param Wrapper.Client#CLIENT client -- @param #string playername @@ -558,6 +566,12 @@ function PLAYERRECCE:_CameraOn(client,playername) if vivihorizontal < -0.7 or vivihorizontal > 0.7 then camera = false end + elseif string.find(typename,"OH58") then + local dcsunit = Unit.getByName(client:GetName()) + local vivihorizontal = dcsunit:getDrawArgumentValue(528) or 0 -- Kiow + if vivihorizontal < -0.527 or vivihorizontal > 0.527 then + camera = false + end elseif string.find(typename,"Ka-50") then camera = true end @@ -565,6 +579,52 @@ function PLAYERRECCE:_CameraOn(client,playername) return camera end +--- [Internal] Get the view parameters from a Kiowa MMS camera +-- @param #PLAYERRECCE self +-- @param Wrapper.Unit#UNIT Kiowa +-- @return #number cameraheading in degrees. +-- @return #number cameranodding in degrees. +-- @return #number maxview in meters. +-- @return #boolean cameraison If true, camera is on, else off. +function PLAYERRECCE:_GetKiowaMMSSight(Kiowa) + self:T(self.lid.."_GetKiowaMMSSight") + local unit = Kiowa -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local dcsunit = Unit.getByName(Kiowa:GetName()) + --[[ + shagrat — 01/01/2025 23:13 + Found the necessary ARGS for the Kiowa MMS angle and rotation: + Arg 527 vertical movement + 0 = neutral + -1.0 = max depression (30° max depression angle) + +1.0 = max elevation angle (30° max elevation angle) + + Arg 528 horizontal movement + 0 = forward (0 degr) + -0.25 = 90° left + -0.5 = rear (180°) left (max 190° = -0.527 + +0.25 = 90° right + +0.5 = 180° right (max 190° = 0.527) + --]] + local mmshorizontal = dcsunit:getDrawArgumentValue(528) or 0 + local mmsvertical = dcsunit:getDrawArgumentValue(527) or 0 + self:T(string.format("Kiowa MMS Arguments Read: H %.3f V %.3f",mmshorizontal,mmsvertical)) + local mmson = true + if mmshorizontal < -0.527 or mmshorizontal > 0.527 then mmson = false end + local horizontalview = mmshorizontal / 0.527 * 190 + local heading = unit:GetHeading() + local mmsheading = (heading+horizontalview)%360 + --local mmsyaw = mmsvertical * 30 + local mmsyaw = math.atan(mmsvertical)*40 + local maxview = self:_GetActualMaxLOSight(unit,mmsheading, mmsyaw,not mmson) + if maxview > 8000 then maxview = 8000 end + self:T(string.format("Kiowa MMS Heading %d, Yaw %d, MaxView %dm MMS On %s",mmsheading,mmsyaw,maxview,tostring(mmson))) + return mmsheading,mmsyaw,maxview,mmson + end + return 0,0,0,false +end + + --- [Internal] Get the view parameters from a Gazelle camera -- @param #PLAYERRECCE self -- @param Wrapper.Unit#UNIT Gazelle @@ -593,40 +653,15 @@ function PLAYERRECCE:_GetGazelleVivianneSight(Gazelle) vivioff = true return 0,0,0,false end - vivivertical = vivivertical / 1.10731 -- normalize + local horizontalview = vivihorizontal * -180 - local verticalview = vivivertical * 30 -- ca +/- 30° - --self:I(string.format("vivihorizontal=%.5f | vivivertical=%.5f",vivihorizontal,vivivertical)) - --self:I(string.format("horizontal=%.5f | vertical=%.5f",horizontalview,verticalview)) + --local verticalview = vivivertical * 30 -- ca +/- 30° + local verticalview = math.atan(vivivertical) + local heading = unit:GetHeading() local viviheading = (heading+horizontalview)%360 local maxview = self:_GetActualMaxLOSight(unit,viviheading, verticalview,vivioff) - --self:I(string.format("maxview=%.5f",maxview)) - -- visual skew - local factor = 3.15 - self.GazelleViewFactors = { - [1]=1.18, - [2]=1.32, - [3]=1.46, - [4]=1.62, - [5]=1.77, - [6]=1.85, - [7]=2.05, - [8]=2.05, - [9]=2.3, - [10]=2.3, - [11]=2.27, - [12]=2.27, - [13]=2.43, - } - local lfac = UTILS.Round(maxview,-2) - if lfac <= 1300 then - --factor = self.GazelleViewFactors[lfac/100] - factor = 3.15 - maxview = math.ceil((maxview*factor)/100)*100 - end if maxview > 8000 then maxview = 8000 end - --self:I(string.format("corrected maxview=%.5f",maxview)) return viviheading, verticalview,maxview, not vivioff end return 0,0,0,false @@ -647,20 +682,20 @@ function PLAYERRECCE:_GetActualMaxLOSight(unit,vheading, vnod, vivoff) if unit and unit:IsAlive() then local typename = unit:GetTypeName() maxview = self.MaxViewDistance[typename] or 8000 - local CamHeight = self.Cameraheight[typename] or 0 - if vnod < 0 then + local CamHeight = self.Cameraheight[typename] or 1 + if vnod < -2 then -- Looking down -- determine max distance we're looking at local beta = 90 - local gamma = math.floor(90-vnod) - local alpha = math.floor(180-beta-gamma) + local gamma = 90-math.abs(vnod) + local alpha = 90-gamma local a = unit:GetHeight()-unit:GetCoordinate():GetLandHeight()+CamHeight local b = a / math.sin(math.rad(alpha)) local c = b * math.sin(math.rad(gamma)) maxview = c*1.2 -- +20% end end - return math.abs(maxview) + return math.ceil(math.abs(maxview)) end --- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns. @@ -669,8 +704,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERRECCE self -function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -678,6 +715,8 @@ function PLAYERRECCE:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTransla end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -799,7 +838,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) local minview = 0 local typename = unit:GetTypeName() local playername = unit:GetPlayerName() - local maxview = self.MaxViewDistance[typename] or 5000 + local maxview = self.MaxViewDistance[typename] or 8000 local heading,nod,maxview,angle = 0,30,8000,10 local camon = false local name = unit:GetName() @@ -807,16 +846,25 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) heading,nod,maxview,camon = self:_GetGazelleVivianneSight(unit) angle=10 -- Model nod and actual TV view don't compute - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 elseif string.find(typename,"Ka-50") and camera then heading = unit:GetHeading() nod,maxview,camon = 10,1000,true angle = 10 - maxview = self.MaxViewDistance[typename] or 5000 + maxview = self.MaxViewDistance[typename] or 8000 + elseif string.find(typename,"OH58") and camera then + --heading = unit:GetHeading() + nod,maxview,camon = 0,8000,true + heading,nod,maxview,camon = self:_GetKiowaMMSSight(unit) + angle = 8 + if maxview == 0 then + maxview = self.MaxViewDistance[typename] or 8000 + end else -- visual heading = unit:GetHeading() - nod,maxview,camon = 10,1000,true + nod,maxview,camon = 10,3000,true + maxview = self.MaxViewDistance[typename] or 3000 angle = 45 end if laser then @@ -929,7 +977,8 @@ function PLAYERRECCE:_LaseTarget(client,targetset) if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then -- lost LOS or dead laser:LaseOff() - if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then + self:T(self.lid.."Target Life Points: "..target:GetLife() or "none") + if target:IsDead() or target:IsDestroyed() or target:GetDamage() > 79 or target:GetLife() <= 1 then self:__Shack(-1,client,oldtarget) --self.LaserTarget[playername] = nil else @@ -1274,6 +1323,9 @@ self:T(self.lid.."_ReportLaserTargets") report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") if not self.ReferencePoint then report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) end @@ -1317,8 +1369,14 @@ function PLAYERRECCE:_ReportVisualTargets(client,group,playername) report:Add("Threat Level: "..ThreatGraph.." ("..ThreatLevelText..")") if not self.ReferencePoint then report:Add("Location: "..client:GetCoordinate():ToStringBULLS(self.Coalition,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end else report:Add("Location: "..client:GetCoordinate():ToStringFromRPShort(self.ReferencePoint,self.RPName,client,Settings)) + if self.reporttostringbullsonly ~= true then + report:Add("Location: "..client:GetCoordinate():ToStringA2G(nil,Settings)) + end end report:Add(string.rep("-",15)) local text = report:Text() @@ -1347,6 +1405,7 @@ function PLAYERRECCE:_BuildMenus(Client) local client = _client -- Wrapper.Client#CLIENT if client and client:IsAlive() then local playername = client:GetPlayerName() + self:T("Menu for "..playername) if not self.UnitLaserCodes[playername] then self:_SetClientLaserCode(nil,nil,playername,1688) end @@ -1355,6 +1414,7 @@ function PLAYERRECCE:_BuildMenus(Client) end local group = client:GetGroup() if not self.ClientMenus[playername] then + self:T("Start Menubuild for "..playername) local canlase = self.CanLase[client:GetTypeName()] self.ClientMenus[playername] = MENU_GROUP:New(group,self.MenuName or self.Name or "RECCE") local txtonstation = self.OnStation[playername] and "ON" or "OFF" @@ -1492,8 +1552,9 @@ end -- Note that this must be installed on your windows system. Can also be Google voice types, if you are using Google TTS. -- @param #number Volume (Optional) Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS +-- @param #string Backend (optional) Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @return #PLAYERRECCE self -function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey) +function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -1515,6 +1576,9 @@ function PLAYERRECCE:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,V self.SRS:SetCulture(self.Culture) self.SRS:SetPort(self.Port) self.SRS:SetVolume(self.Volume) + if Backend then + self.SRS:SetBackend(Backend) + end if self.PathToGoogleKey then self.SRS:SetProviderOptionsGoogle(self.PathToGoogleKey,self.PathToGoogleKey) self.SRS:SetProvider(MSRS.Provider.GOOGLE) @@ -1552,6 +1616,16 @@ function PLAYERRECCE:SetMenuName(Name) return self end +--- [User] Set reporting to be BULLS only or BULLS plus playersettings based coordinate. +-- @param #PLAYERRECCE self +-- @param #boolean OnOff +-- @return #PLAYERRECCE self +function PLAYERRECCE:SetReportBullsOnly(OnOff) + self:T(self.lid.."SetReportBullsOnly: "..tostring(OnOff)) + self.reporttostringbullsonly = OnOff + return self +end + --- [User] Enable smoking of own position -- @param #PLAYERRECCE self -- @return #PLAYERRECCE self @@ -1561,6 +1635,15 @@ function PLAYERRECCE:EnableSmokeOwnPosition() return self end +--- [User] Enable auto lasing for the Kiowa OH-58D. +-- @param #PLAYERRECCE self +-- @return #PLAYERRECCE self +function PLAYERRECCE:EnableKiowaAutolase() + self:T(self.lid.."EnableKiowaAutolase") + self.CanLase.OH58D = true + return self +end + --- [User] Disable smoking of own position -- @param #PLAYERRECCE self -- @return #PLAYERRECCE @@ -1718,7 +1801,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1727,7 +1810,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) end local text1 = "Party time!" local text2 = string.format("All stations, FACA %s on station\nat %s!",callsign, coordtext) - local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) + local text2tts = string.format(" All stations, FACA %s on station at %s!",callsign, coordtext) text2tts = self:_GetTextForSpeech(text2tts) if self.debug then self:T(text2.."\n"..text2tts) @@ -1758,7 +1841,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.ReferencePoint then @@ -1898,7 +1981,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterIllumination(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -1941,7 +2024,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsSmoked(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -1984,7 +2067,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetsFlared(From, Event, To, Client, Playername, TargetSet) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition) if self.AttackSet then @@ -2028,7 +2111,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLasing(From, Event, To, Client, Target, Lasercode, Lasingtime) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2075,7 +2158,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterShack(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) @@ -2122,7 +2205,7 @@ end -- @return #PLAYERRECCE self function PLAYERRECCE:onafterTargetLOSLost(From, Event, To, Client, Target) self:T({From, Event, To}) - local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = Client:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local Settings = ( Client and _DATABASE:GetPlayerSettings( Client:GetPlayerName() ) ) or _SETTINGS local coord = Client:GetCoordinate() local coordtext = coord:ToStringBULLS(self.Coalition,Settings) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 2f02d9ce3..673c5095c 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update May 2024 +-- @date Last Update Jan 2025 do @@ -95,10 +95,10 @@ PLAYERTASK = { FinalState = "none", PreviousCount = 0, } - + --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.24" +PLAYERTASK.version="0.1.25" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -112,14 +112,14 @@ PLAYERTASK.version="0.1.24" -- @param #boolean Repeat Repeat this task if true (default = false) -- @param #number Times Repeat on failure this many times if Repeat is true (default = 1) -- @param #string TTSType TTS friendly task type name --- @return #PLAYERTASK self +-- @return #PLAYERTASK self function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #PLAYERTASK - + self.Type = Type - + self.Repeat = false self.repeats = 0 self.RepeatNo = 1 @@ -132,18 +132,18 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) self.timestamp = timer.getAbsTime() self.TTSType = TTSType or "close air support" self.lastsmoketime = 0 - - if Repeat then + + if type(Repeat) == "boolean" and Repeat == true and type(Times) == "number" and Times > 1 then self.Repeat = true self.RepeatNo = Times or 1 end - + _PlayerTaskNr = _PlayerTaskNr + 1 - + self.PlayerTaskNr = _PlayerTaskNr - + self.lid=string.format("PlayerTask #%d %s | ", self.PlayerTaskNr, tostring(self.Type)) - + if Target and Target.ClassName and Target.ClassName == "TARGET" then self.Target = Target elseif Target and Target.ClassName then @@ -152,16 +152,16 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) self:E(self.lid.."*** NO VALID TARGET!") return self end - + self.PreviousCount = self.Target:CountTargets() - + self:T(self.lid.."Created.") - + -- FMS start state is PLANNED. self:SetStartState("Planned") -- PLANNED --> REQUESTED --> EXECUTING --> DONE - self:AddTransition("*", "Planned", "Planned") -- Task is in planning stage. + self:AddTransition("*", "Planned", "Planned") -- Task is in planning stage. self:AddTransition("*", "Requested", "Requested") -- Task clients have been requested to join. self:AddTransition("*", "ClientAdded", "*") -- Client has been added to the task self:AddTransition("*", "ClientRemoved", "*") -- Client has been removed from the task @@ -174,28 +174,28 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) self:AddTransition("*", "Failed", "Failed") -- Done or repeat --> PLANNED self:AddTransition("*", "Status", "*") self:AddTransition("*", "Stop", "Stopped") - + self:__Status(-5) return self - + --- -- Pseudo Functions --- - + --- On After "Planned" event. Task has been planned. -- @function [parent=#PLAYERTASK] OnAfterPlanned -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Requested" event. Task has been Requested. -- @function [parent=#PLAYERTASK] OnAfterRequested -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "ClientAdded" event. Client has been added to the task. -- @function [parent=#PLAYERTASK] OnAfterClientAdded -- @param #PLAYERTASK self @@ -203,65 +203,191 @@ function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType) -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Client#CLIENT Client - + --- On After "ClientRemoved" event. Client has been removed from the task. -- @function [parent=#PLAYERTASK] OnAfterClientRemoved -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Executing" event. Task is executed by the 1st client. -- @function [parent=#PLAYERTASK] OnAfterExecuting -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Done" event. Task is done. -- @function [parent=#PLAYERTASK] OnAfterDone -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Cancel" event. Task has been cancelled. -- @function [parent=#PLAYERTASK] OnAfterCancel -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Planned" event. Task has been planned. -- @function [parent=#PLAYERTASK] OnAfterPilotPlanned -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Success" event. Task has been a success. -- @function [parent=#PLAYERTASK] OnAfterSuccess -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "ClientAborted" event. A client has aborted the task. -- @function [parent=#PLAYERTASK] OnAfterClientAborted -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + --- On After "Failed" event. Task has been a failure. -- @function [parent=#PLAYERTASK] OnAfterFailed -- @param #PLAYERTASK self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - + end +--- Constructor that automatically determines the task type based on the target. +-- @param #PLAYERTASK self +-- @param Ops.Target#TARGET Target Target for this task +-- @param #boolean Repeat Repeat this task if true (default = false) +-- @param #number Times Repeat on failure this many times if Repeat is true (default = 1) +-- @param #string TTSType TTS friendly task type name +-- @return #PLAYERTASK self +function PLAYERTASK:NewFromTarget(Target, Repeat, Times, TTSType) + return PLAYERTASK:New(self:_GetTaskTypeForTarget(Target), Target, Repeat, Times, TTSType) +end + +--- [Internal] Determines AUFTRAG type based on the target characteristics. +-- @param #PLAYERTASK self +-- @param Ops.Target#TARGET Target Target for this task +-- @return #string AUFTRAG.Type +function PLAYERTASK:_GetTaskTypeForTarget(Target) + + local group = nil --Wrapper.Group#GROUP + local auftrag = nil + + if Target:IsInstanceOf("GROUP") then + group = Target --Target is already a group. + elseif Target:IsInstanceOf("SET_GROUP") then + group = Target:GetFirst() + elseif Target:IsInstanceOf("UNIT") then + group = Target:GetGroup() + elseif Target:IsInstanceOf("SET_UNIT") then + group = Target:GetFirst():GetGroup() + elseif Target:IsInstanceOf("AIRBASE") then + + auftrag = AUFTRAG.Type.BOMBRUNWAY + + elseif Target:IsInstanceOf("STATIC") + or Target:IsInstanceOf("SET_STATIC") + or Target:IsInstanceOf("SCENERY") + or Target:IsInstanceOf("SET_SCENERY") then + + auftrag = AUFTRAG.Type.BOMBING + + elseif Target:IsInstanceOf("OPSZONE") + or Target:IsInstanceOf("SET_OPSZONE") then + auftrag = AUFTRAG.Type.CAPTUREZONE + end + + if group then + + local category = group:GetCategory() + local attribute = group:GetAttribute() + + if (category == Group.Category.AIRPLANE or category == Group.Category.HELICOPTER) + and group:InAir() then + + auftrag = AUFTRAG.Type.INTERCEPT + + elseif category == Group.Category.GROUND or category == Group.Category.TRAIN then + + if attribute == GROUP.Attribute.GROUND_SAM + or attribute == GROUP.Attribute.GROUND_EWR then + + auftrag = AUFTRAG.Type.SEAD + + elseif attribute == GROUP.Attribute.GROUND_AAA + or attribute == GROUP.Attribute.GROUND_APC + or attribute == GROUP.Attribute.GROUND_IFV + or attribute == GROUP.Attribute.GROUND_TRUCK + or attribute == GROUP.Attribute.GROUND_TRAIN then + + auftrag = AUFTRAG.Type.BAI + + elseif attribute == GROUP.Attribute.GROUND_INFANTRY + or attribute == GROUP.Attribute.GROUND_ARTILLERY + or attribute == GROUP.Attribute.GROUND_TANK then + + auftrag = AUFTRAG.Type.CAS + + else + + auftrag = AUFTRAG.Type.BAI + + end + + elseif category == Group.Category.SHIP then + + auftrag = AUFTRAG.Type.ANTISHIP + + else + self:T(self.lid .. "ERROR: Unknown Group category!") + end + end + + return auftrag + +end + + +--- [Internal] Check OpsZone capture success condition. +-- @param #PLAYERTASK self +-- @param Ops.OpsZone#OPSZONE OpsZone The OpsZone target object. +-- @param #string CaptureSquadGroupNamePrefix The prefix of the group name that needs to capture the zone. +-- @param #number Coalition The coalition that needs to capture the zone. +-- @param #boolean CheckClientInZone Check if any of the clients are in zone. +-- @return #PLAYERTASK self +function PLAYERTASK:_CheckCaptureOpsZoneSuccess(OpsZone, CaptureSquadGroupNamePrefix, Coalition, CheckClientInZone) + local isClientInZone = true + if CheckClientInZone then + isClientInZone = false + for _, client in ipairs(self:GetClientObjects()) do + local clientCoord = client:GetCoordinate() + if OpsZone.zone:IsCoordinateInZone(clientCoord) then + isClientInZone = true + break + end + end + end + + local isCaptureGroupInZone = false + OpsZone:GetScannedGroupSet():ForEachGroup(function(group) + if string.find(group:GetName(), CaptureSquadGroupNamePrefix) then + isCaptureGroupInZone = true + end + end) + + return OpsZone:GetOwner() == Coalition and isClientInZone and isCaptureGroupInZone +end + + --- [Internal] Add a PLAYERTASKCONTROLLER for this task -- @param #PLAYERTASK self -- @param Ops.PlayerTask#PLAYERTASKCONTROLLER Controller @@ -378,6 +504,169 @@ function PLAYERTASK:SetMenuName(Text) return self end +--- [USER] Adds task success condition for dead STATIC, SET_STATIC, SCENERY or SET_SCENERY target object. +-- @return #PLAYERTASK self +-- @usage +-- -- We can use either STATIC, SET_STATIC, SCENERY or SET_SCENERY as target objects. +-- local mytask = PLAYERTASK:NewFromTarget(static, true, 50, "Destroy the target") +-- mytask:SetMenuName("Destroy Power Plant") +-- mytask:AddFreetext("Locate and destroy the power plant near Olenya.") +-- mytask:AddStaticObjectSuccessCondition() +-- +-- playerTaskManager:AddPlayerTaskToQueue(mytask) +function PLAYERTASK:AddStaticObjectSuccessCondition() + local task = self + -- TODO Check if the killer is one of the task clients + task:AddConditionSuccess( + function(target) + if target == nil then return false end + + local isDead = false + if target:IsInstanceOf("STATIC") + or target:IsInstanceOf("SCENERY") + or target:IsInstanceOf("SET_SCENERY") then + isDead = (not target) or target:GetLife() < 1 or target:GetLife() < 0.2* target:GetLife0() + elseif target:IsInstanceOf("SET_STATIC") then + local deadCount = 0 + target:ForEachStatic(function(static) + if static:GetLife() < 1 or static:GetLife() < 0.2* static:GetLife0() then + deadCount = deadCount + 1 + end + end) + + if deadCount == target:Count() then + isDead = true + end + end + + return isDead + end, task:GetTarget() + ) + + -- TODO Check if the killer is one of the task clients + --task:AddConditionFailure( + -- function() + -- + -- end) + return self +end + +--- [USER] Adds task success condition for AUFTRAG.Type.CAPTUREZONE for OpsZone or OpsZone set target object. +--- At least one of the task clients and one capture group need to be inside the zone in order for the capture to be successful. +-- @param #PLAYERTASK self +-- @param #SET_BASE CaptureSquadGroupNamePrefix The prefix of the group name that needs to capture the zone. +-- @param #number Coalition The coalition that needs to capture the zone. +-- @return #PLAYERTASK self +-- @usage +-- -- We can use either STATIC, SET_STATIC, SCENERY or SET_SCENERY as target objects. +-- local opsZone = OPSZONE:New(zone, coalition.side.RED) +-- +-- ... +-- +-- -- We can use either OPSZONE or SET_OPSZONE. +-- local mytask = PLAYERTASK:NewFromTarget(opsZone, true, 50, "Capture the zone") +-- mytask:SetMenuName("Capture the ops zone") +-- mytask:AddFreetext("Transport capture squad to the ops zone.") +-- +-- -- We set CaptureSquadGroupNamePrefix the group name prefix as set in the ME or the spawn of the group that need to be present at the OpsZone like a capture squad, +-- -- and set the capturing Coalition in order to trigger a successful task. +-- mytask:AddOpsZoneCaptureSuccessCondition("capture-squad", coalition.side.BLUE) +-- +-- playerTaskManager:AddPlayerTaskToQueue(mytask) +function PLAYERTASK:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix, Coalition) + local task = self + task:AddConditionSuccess( + function(target) + if target:IsInstanceOf("OPSZONE") then + return task:_CheckCaptureOpsZoneSuccess(target, CaptureSquadGroupNamePrefix, Coalition, true) + elseif target:IsInstanceOf("SET_OPSZONE") then + local successes = 0 + local isClientInZone = false + target:ForEachZone(function(opszone) + if task:_CheckCaptureOpsZoneSuccess(opszone, CaptureSquadGroupNamePrefix, Coalition) then + successes = successes + 1 + end + + for _, client in ipairs(task:GetClientObjects()) do + local clientCoord = client:GetCoordinate() + if opszone.zone:IsCoordinateInZone(clientCoord) then + isClientInZone = true + break + end + end + end) + return successes == target:Count() and isClientInZone + end + + return false + end, task:GetTarget() + ) + return self +end + +--- [USER] Adds task success condition for AUFTRAG.Type.RECON when a client is at a certain LOS distance from the target. +-- @param #PLAYERTASK self +-- @param #number MinDistance (Optional) Minimum distance in meters from client to target in LOS for success condition. (Default 5 NM) +-- @return #PLAYERTASK self +-- @usage +-- -- target can be any object that has a `GetCoordinate()` function like STATIC, GROUP, ZONE... +-- local mytask = PLAYERTASK:New(AUFTRAG.Type.RECON, ZONE:New("WF Zone"), true, 50, "Deep Earth") +-- mytask:SetMenuName("Recon weapon factory") +-- mytask:AddFreetext("Locate and investigate underground weapons factory near Kovdor.") +-- +-- -- We set the MinDistance (optional) in meters for the client to be in LOS from the target in order to trigger a successful task. +-- mytask:AddReconSuccessCondition(10000) -- 10 km (default is 5 NM if not set) +-- +-- playerTaskManager:AddPlayerTaskToQueue(mytask) +function PLAYERTASK:AddReconSuccessCondition(MinDistance) + local task = self + task:AddConditionSuccess( + function(target) + local targetLocation = target:GetCoordinate() + local minD = MinDistance or UTILS.NMToMeters(5) + for _, client in ipairs(task:GetClientObjects()) do + local clientCoord = client:GetCoordinate() + local distance = clientCoord:Get2DDistance(targetLocation) + local isLos = land.isVisible(clientCoord:GetVec3(), targetLocation:GetVec3()) + + if distance < minD and isLos then + return true + end + end + return false + end, task:GetTarget()) + + return self +end + +--- [USER] Adds a time limit for the task to be completed. +-- @param #PLAYERTASK self +-- @param #number TimeLimit Time limit in seconds for the task to be completed. (Default 0 = no time limit) +-- @return #PLAYERTASK self +-- @usage +-- local mytask = PLAYERTASK:New(AUFTRAG.Type.RECON, ZONE:New("WF Zone"), true, 50, "Deep Earth") +-- mytask:SetMenuName("Recon weapon factory") +-- mytask:AddFreetext("Locate and investigate underground weapons factory near Kovdor.") +-- mytask:AddReconSuccessCondition(10000) -- 10 km +-- +-- -- We set the TimeLimit to 10 minutes (600 seconds) from the moment the task is started, once the time has passed and the task is not yet successful it will trigger a failure. +-- mytask:AddTimeLimitFailureCondition(600) +-- +-- playerTaskManager:AddPlayerTaskToQueue(mytask) +function PLAYERTASK:AddTimeLimitFailureCondition(TimeLimit) + local task = self + TimeLimit = TimeLimit or 0 + task.StartTime = -1 + task:AddConditionFailure( + function() + if task.StartTime == -1 then + task.StartTime = timer.getTime() + end + return TimeLimit > 0 and timer.getTime() - task.StartTime > TimeLimit + end) + return self +end + --- [USER] Add a task to be assigned to same clients when task was a success. -- @param #PLAYERTASK self -- @param Ops.PlayerTask#PLAYERTASK Task @@ -411,6 +700,15 @@ function PLAYERTASK:IsDone() return IsDone end +--- [User] Check if task is NOT done +-- @param #PLAYERTASK self +-- @return #boolean done +function PLAYERTASK:IsNotDone() + self:T(self.lid.."IsNotDone?") + local IsNotDone = not self:IsDone() + return IsNotDone +end + --- [User] Check if PLAYERTASK has clients assigned to it. -- @param #PLAYERTASK self -- @return #boolean hasclients @@ -867,7 +1165,7 @@ function PLAYERTASK:onafterCancel(From, Event, To) self.TaskController:__TaskCancelled(-1,self) end self.timestamp = timer.getAbsTime() - self.FinalState = "Cancel" + self.FinalState = "Cancelled" self:__Done(-1) return self end @@ -886,7 +1184,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To) if self.TargetMarker then self.TargetMarker:Remove() end - if self.TaskController.Scoring then + if self.TaskController and self.TaskController.Scoring then local clients,count = self:GetClientObjects() if count > 0 then for _,_client in pairs(clients) do @@ -944,7 +1242,7 @@ end do ------------------------------------------------------------------------------------------------------------------- -- PLAYERTASKCONTROLLER - -- TODO: PLAYERTASKCONTROLLER +-- TODO: PLAYERTASKCONTROLLER -- DONE Playername customized -- DONE Coalition-level screen info to SET based -- DONE Flash directions @@ -1019,6 +1317,8 @@ do -- @field Core.ClientMenu#CLIENTMENU ActiveTopMenu -- @field Core.ClientMenu#CLIENTMENU ActiveInfoMenu -- @field Core.ClientMenu#CLIENTMENU MenuNoTask +-- @field #boolean InformationMenu Show Radio Info Menu +-- @field #number TaskInfoDuration How long to show the briefing info on the screen -- @extends Core.Fsm#FSM --- @@ -1067,11 +1367,34 @@ do -- * Anti-Ship - Any ship targets, if the controller is of type "A2S" -- * CTLD - Combat transport and logistics deployment -- * CSAR - Combat search and rescue +-- * RECON - Identify targets +-- * CAPTUREZONE - Capture an Ops.OpsZone#OPSZONE +-- * Any #string name can be passed as Auftrag type, but then you need to make sure to define a success condition, and possibly also add the task type to the standard scoring list: `PLAYERTASKCONTROLLER.Scores["yournamehere"]=100` -- -- ## 3 Task repetition -- -- On failure, tasks will be replanned by default for a maximum of 5 times. -- +-- ## 3.1 Pre-configured success conditions +-- +-- Pre-configured success conditions for #PLAYERTASK tasks are available as follows: +-- +-- `mytask:AddStaticObjectSuccessCondition()` -- success if static object is at least 80% dead +-- +-- `mytask:AddOpsZoneCaptureSuccessCondition(CaptureSquadGroupNamePrefix,Coalition)` -- success if a squad of the given (partial) name and coalition captures the OpsZone +-- +-- `mytask:AddReconSuccessCondition(MinDistance)` -- success if object is in line-of-sight with the given min distance in NM +-- +-- `mytask:AddTimeLimitSuccessCondition(TimeLimit)` -- failure if the task is not completed within the time limit in seconds given +-- +-- ## 3.2 Task chaining +-- +-- You can create chains of tasks, which will depend on success or failure of the previous task with the following commands: +-- +-- `mytask:AddNextTaskAfterSuccess(FollowUpTask)` and +-- +-- `mytask:AddNextTaskAfterFailure(FollowUpTask)` +-- -- ## 4 SETTINGS, SRS and language options (localization) -- -- The system can optionally communicate to players via SRS. Also localization is available, both "en" and "de" has been build in already. @@ -1351,6 +1674,8 @@ PLAYERTASKCONTROLLER = { UseTypeNames = false, Scoring = nil, MenuNoTask = nil, + InformationMenu = false, + TaskInfoDuration = 30, } --- @@ -1384,7 +1709,11 @@ PLAYERTASKCONTROLLER.Scores = { [AUFTRAG.Type.SEAD] = 100, [AUFTRAG.Type.BOMBING] = 100, [AUFTRAG.Type.BOMBRUNWAY] = 100, - [AUFTRAG.Type.CONQUER] = 100, + [AUFTRAG.Type.CONQUER] = 100, + [AUFTRAG.Type.RECON] = 100, + [AUFTRAG.Type.ESCORT] = 100, + [AUFTRAG.Type.CAP] = 100, + [AUFTRAG.Type.CAPTUREZONE] = 100, } --- @@ -1483,6 +1812,7 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Cruiser", DESTROYER = "Destroyer", CARRIER = "Aircraft Carrier", + RADIOS = "Radios", }, DE = { TASKABORT = "Auftrag abgebrochen!", @@ -1566,12 +1896,13 @@ PLAYERTASKCONTROLLER.Messages = { CRUISER = "Kreuzer", DESTROYER = "Zerstörer", CARRIER = "Flugzeugträger", + RADIOS = "Frequenzen", }, } --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.66" +PLAYERTASKCONTROLLER.version="0.1.69" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -1604,6 +1935,7 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.TaskQueue = FIFO:New() -- Utilities.FiFo#FIFO self.TasksPerPlayer = FIFO:New() -- Utilities.FiFo#FIFO self.PrecisionTasks = FIFO:New() -- Utilities.FiFo#FIFO + self.LasingDroneSet = SET_OPSGROUP:New() -- Core.Set#SET_OPSGROUP --self.PlayerMenu = {} -- #table self.FlashPlayer = {} -- #table self.AllowFlash = false @@ -1633,6 +1965,10 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter) self.UseTypeNames = false + self.InformationMenu = false + + self.TaskInfoDuration = 30 + self.IsClientSet = false if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then @@ -1850,6 +2186,16 @@ function PLAYERTASKCONTROLLER:SetAllowFlashDirection(OnOff) return self end +--- [User] Set to show a menu entry to retrieve the radio frequencies used. +-- @param #PLAYERTASKCONTROLLER self +-- @param #boolean OnOff Set to `true` to switch on and `false` to switch off. Default is OFF. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetShowRadioInfoMenu(OnOff) + self:T(self.lid.."SetAllowRadioInfoMenu") + self.InformationMenu = OnOff + return self +end + --- [User] Do not show menu entries to smoke or flare targets -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -1916,8 +2262,10 @@ end -- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers. -- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized -- callsigns from playername or group name. +-- @param #func CallsignCustomFunc (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first two arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the custom function call after groupname and playername. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations) +function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations,CallsignCustomFunc,...) if not ShortCallsign or ShortCallsign == false then self.ShortCallsign = false else @@ -1925,6 +2273,8 @@ function PLAYERTASKCONTROLLER:SetCallSignOptions(ShortCallsign,Keepnumber,Callsi end self.Keepnumber = Keepnumber or false self.CallsignTranslations = CallsignTranslations + self.CallsignCustomFunc = CallsignCustomFunc + self.CallsignCustomArgs = arg or {} return self end @@ -1945,7 +2295,7 @@ function PLAYERTASKCONTROLLER:_GetTextForSpeech(text) return text end ---- [User] Set repetition options for tasks +--- [User] Set repetition options for tasks. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true` to switch on and `false` to switch off (defaults to true) -- @param #number Repeats Number of repeats (defaults to 5) @@ -1963,6 +2313,16 @@ function PLAYERTASKCONTROLLER:SetTaskRepetition(OnOff, Repeats) return self end +--- [User] Set how long the briefing is shown on screen. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number Seconds Duration in seconds. Defaults to 30 seconds. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:SetBriefingDuration(Seconds) + self:T(self.lid.."SetBriefingDuration") + self.TaskInfoDuration = Seconds or 30 + return self +end + --- [Internal] Send message to SET_CLIENT of players -- @param #PLAYERTASKCONTROLLER self -- @param #string Text the text to be send @@ -1973,7 +2333,9 @@ function PLAYERTASKCONTROLLER:_SendMessageToClients(Text,Seconds) local seconds = Seconds or 10 self.ClientSet:ForEachClient( function (Client) - local m = MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) + if Client ~= nil and Client:IsActive() then + local m = MESSAGE:New(Text,seconds,"Tasking"):ToClient(Client) + end end ) return self @@ -1987,6 +2349,7 @@ end -- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. -- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. -- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @param #number MaxTravelDist (Optional) Max distance to travel to traget. Only applies if using a FLIGHTGROUP object! Defaults to 100 NM. -- @return #PLAYERTASKCONTROLLER self -- @usage -- -- Set up precision bombing, FlightGroup as lasing unit @@ -2001,35 +2364,56 @@ end -- ArmyGroup:Activate() -- taskmanager:EnablePrecisionBombing(ArmyGroup,1688) -- -function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) +function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) self:T(self.lid.."EnablePrecisionBombing") + + if not self.LasingDroneSet then + self.LasingDroneSet = SET_OPSGROUP:New() + end + + local LasingDrone -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + if FlightGroup then if FlightGroup.ClassName and (FlightGroup.ClassName == "FLIGHTGROUP" or FlightGroup.ClassName == "ARMYGROUP")then -- ok we have a FG - self.LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup - self.LasingDrone.playertask = {} - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.id = 0 + LasingDrone = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP FlightGroup + self.precisionbombing = true - self.LasingDrone:SetLaser(LaserCode) - self.LaserCode = LaserCode or 1688 - self.LasingDroneTemplate = self.LasingDrone:_GetTemplate(true) - self.LasingDroneAlt = Alt or 10000 - self.LasingDroneSpeed = Speed or 120 + + LasingDrone.playertask = {} + LasingDrone.playertask.id = 0 + LasingDrone.playertask.busy = false + LasingDrone.playertask.lasercode = LaserCode or 1688 + LasingDrone:SetLaser(LasingDrone.playertask.lasercode) + LasingDrone.playertask.template = LasingDrone:_GetTemplate(true) + LasingDrone.playertask.alt = Alt or 10000 + LasingDrone.playertask.speed = Speed or 120 + LasingDrone.playertask.maxtravel = UTILS.NMToMeters(MaxTravelDist or 50) + -- let it orbit the BullsEye if FG - if self.LasingDrone:IsFlightgroup() then - self.LasingDroneIsFlightgroup = true + if LasingDrone:IsFlightgroup() then + --settings.IsFlightgroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end - local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(Orbit) - elseif self.LasingDrone:IsArmygroup() then - self.LasingDroneIsArmygroup = true + local Orbit = AUFTRAG:NewORBIT_CIRCLE(BullsCoordinate,Alt,Speed) + Orbit:SetMissionAltitude(Alt) + LasingDrone:AddMission(Orbit) + elseif LasingDrone:IsArmygroup() then + --settings.IsArmygroup = true local BullsCoordinate = COORDINATE:NewFromVec3( coalition.getMainRefPoint( self.Coalition )) if HoldingPoint then BullsCoordinate = HoldingPoint end local Orbit = AUFTRAG:NewONGUARD(BullsCoordinate) - self.LasingDrone:AddMission(Orbit) + LasingDrone:AddMission(Orbit) end + + self.LasingDroneSet:AddObject(FlightGroup) + + elseif FlightGroup.ClassName and (FlightGroup.ClassName == "SET_OPSGROUP") then --SET_OPSGROUP + FlightGroup:ForEachGroup( + function(group) + self:EnablePrecisionBombing(group,LaserCode,HoldingPoint,Alt,Speed,MaxTravelDist) + end + ) else self:E(self.lid.."No FLIGHTGROUP object passed or FLIGHTGROUP is not alive!") end @@ -2040,6 +2424,20 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode,Holdi return self end +--- [User] Convenience function - add done or ground allowing precision laser-guided bombing on statics and "high-value" ground units (MBT etc) +-- @param #PLAYERTASKCONTROLLER self +-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup The FlightGroup (e.g. drone) to be used for lasing (one unit in one group only). +-- Can optionally be handed as Ops.ArmyGroup#ARMYGROUP - **Note** might not find an LOS spot or get lost on the way. Cannot island-hop. +-- @param #number LaserCode The lasercode to be used. Defaults to 1688. +-- @param Core.Point#COORDINATE HoldingPoint (Optional) Point where the drone should initially circle. If not set, defaults to BullsEye of the coalition. +-- @param #number Alt (Optional) Altitude in feet. Only applies if using a FLIGHTGROUP object! Defaults to 10000. +-- @param #number Speed (Optional) Speed in knots. Only applies if using a FLIGHTGROUP object! Defaults to 120. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddPrecisionBombingOpsGroup(FlightGroup,LaserCode,HoldingPoint, Alt, Speed) + self:EnablePrecisionBombing(FlightGroup,LaserCode,HoldingPoint,Alt,Speed) + return self +end + --- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing. -- @param #PLAYERTASKCONTROLLER self @@ -2124,7 +2522,7 @@ function PLAYERTASKCONTROLLER:_GetPlayerName(Client) if not self.customcallsigns[playername] then local playergroup = Client:GetGroup() if playergroup ~= nil then - ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + ttsplayername = playergroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local newplayername = self:_GetTextForSpeech(ttsplayername) self.customcallsigns[playername] = newplayername ttsplayername = newplayername @@ -2206,7 +2604,7 @@ end function PLAYERTASKCONTROLLER:_EventHandler(EventData) self:T(self.lid.."_EventHandler: "..EventData.id) --self:T(self.lid.."_EventHandler: "..EventData.IniPlayerName) - if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then + if EventData.id == EVENTS.UnitLost or EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then if EventData.IniPlayerName then self:T(self.lid.."Event for player: "..EventData.IniPlayerName) --if self.PlayerMenu[EventData.IniPlayerName] then @@ -2264,7 +2662,7 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData) if self.customcallsigns[playername] then self.customcallsigns[playername] = nil end - playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber) + playername = EventData.IniGroup:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) end playername = self:_GetTextForSpeech(playername) --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) @@ -2561,99 +2959,155 @@ end -- @return #PLAYERTASKCONTROLLER self function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() self:T(self.lid.."_CheckPrecisionTasks") + self:T({count=self.PrecisionTasks:Count(),enabled=self.precisionbombing}) if self.PrecisionTasks:Count() > 0 and self.precisionbombing then - if not self.LasingDrone or self.LasingDrone:IsDead() then - -- we need a new drone - self:E(self.lid.."Lasing drone is dead ... creating a new one!") - if self.LasingDrone then - self.LasingDrone:_Respawn(1,nil,true) - else - -- DONE: Handle ArmyGroup - if self.LasingDroneIsFlightgroup then - local FG = FLIGHTGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + + -- alive checks + self.LasingDroneSet:ForEachGroup( + function(LasingDrone) + if not LasingDrone or LasingDrone:IsDead() then + -- we need a new drone + self:E(self.lid.."Lasing drone is dead ... creating a new one!") + if LasingDrone then + LasingDrone:_Respawn(1,nil,true) else - local FG = ARMYGROUP:New(self.LasingDroneTemplate) - FG:Activate() - self:EnablePrecisionBombing(FG,self.LaserCode or 1688) - end - end - return self - end - -- do we have a lasing unit assigned? - if self.LasingDrone and self.LasingDrone:IsAlive() then - if self.LasingDrone.playertask and (not self.LasingDrone.playertask.busy) then - -- not busy, get a task - self:T(self.lid.."Sending lasing unit to target") - local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK - self.LasingDrone.playertask.id = task.PlayerTaskNr - self.LasingDrone.playertask.busy = true - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - -- move the drone to target - if self.LasingDroneIsFlightgroup then - self.LasingDrone:CancelAllMissions() - local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),self.LasingDroneAlt,self.LasingDroneSpeed) - self.LasingDrone:AddMission(auftrag) - elseif self.LasingDroneIsArmygroup then - local tgtcoord = task.Target:GetCoordinate() - local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) - local finalpos=nil -- Core.Point#COORDINATE - for i=1,50 do - finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) - if finalpos then - if finalpos:IsLOS(tgtcoord,0) then - break - end + --[[ + -- DONE: Handle ArmyGroup + if LasingDrone:IsFlightgroup() then + local FG = FLIGHTGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + else + local FG = ARMYGROUP:New(LasingDroneTemplate) + FG:Activate() + self:EnablePrecisionBombing(FG,self.LaserCode or 1688) + end -- if LasingDroneIsFlightgroup + --]] + end -- if LasingDrone + end -- if not LasingDrone + end -- function + ) + + local function SelectDrone(coord) + local selected = nil + local mindist = math.huge + local dist = math.huge + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp.playertask and (not grp.playertask.busy) then + local gc = grp:GetCoordinate() + if coord and gc then + dist = coord:Get2DDistance(gc) + end + if dist < mindist then + selected = grp + mindist = dist end end - if finalpos then - self.LasingDrone:CancelAllMissions() - -- yeah we got one - local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") - self.LasingDrone:AddMission(auftrag) - else - -- could not find LOS position! - self:E("***Could not find LOS position to post ArmyGroup for lasing!") - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.reachmessage = false - end end - self.PrecisionTasks:Push(task,task.PlayerTaskNr) - elseif self.LasingDrone.playertask and self.LasingDrone.playertask.busy then + ) + return selected + end + + local task = self.PrecisionTasks:Pull() -- Ops.PlayerTask#PLAYERTASK + local taskpt = task.Target:GetCoordinate() + + local SelectedDrone = SelectDrone(taskpt) -- Ops.OpsGroup#OPSGROUP + + -- do we have a lasing unit assignable? + if SelectedDrone and SelectedDrone:IsAlive() then + if SelectedDrone.playertask and (not SelectedDrone.playertask.busy) then + -- not busy, get a task + self:T(self.lid.."Sending lasing unit to target") + local isassigned = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) + -- distance check + local startpoint = SelectedDrone:GetCoordinate() + local endpoint = task.Target:GetCoordinate() + local dist = math.huge + if startpoint and endpoint then + dist = startpoint:Get2DDistance(endpoint) + end + if dist <= SelectedDrone.playertask.maxtravel and (not isassigned) then + SelectedDrone.playertask.id = task.PlayerTaskNr + SelectedDrone.playertask.busy = true + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + -- move the drone to target + if SelectedDrone:IsFlightgroup() then + SelectedDrone:CancelAllMissions() + local auftrag = AUFTRAG:NewORBIT_CIRCLE(task.Target:GetCoordinate(),SelectedDrone.playertask.alt,SelectedDrone.playertask.speed) + SelectedDrone:AddMission(auftrag) + elseif SelectedDrone:IsArmygroup() then + local tgtcoord = task.Target:GetCoordinate() + local tgtzone = ZONE_RADIUS:New("ArmyGroup-"..math.random(1,10000),tgtcoord:GetVec2(),3000) + local finalpos=nil -- Core.Point#COORDINATE + for i=1,50 do + finalpos = tgtzone:GetRandomCoordinate(2500,0,{land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.SHALLOW_WATER}) + if finalpos then + if finalpos:IsLOS(tgtcoord,0) then + break + end + end + end + if finalpos then + SelectedDrone:CancelAllMissions() + -- yeah we got one + local auftrag = AUFTRAG:NewARMOREDGUARD(finalpos,"Off road") + SelectedDrone:AddMission(auftrag) + else + -- could not find LOS position! + self:E("***Could not find LOS position to post ArmyGroup for lasing!") + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.reachmessage = false + end + end + else + self:T(self.lid.."Lasing unit too far from target") + end + + end + end + + self.PrecisionTasks:Push(task,task.PlayerTaskNr) + + + local function DronesWithTask(SelectedDrone) + -- handle drones with a task + if SelectedDrone.playertask and SelectedDrone.playertask.busy then -- drone is busy, set up laser when over target - local task = self.PrecisionTasks:ReadByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:ReadByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:T("Looking at Task: "..task.PlayerTaskNr.." Type: "..task.Type.." State: "..task:GetState()) if (not task) or task:GetState() == "Done" or task:GetState() == "Stopped" then -- we're done here - local task = self.PrecisionTasks:PullByID(self.LasingDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK + local task = self.PrecisionTasks:PullByID(SelectedDrone.playertask.id) -- Ops.PlayerTask#PLAYERTASK self:_CheckTaskQueue() task = nil - if self.LasingDrone:IsLasing() then - self.LasingDrone:__LaserOff(-1) + if SelectedDrone:IsLasing() then + SelectedDrone:__LaserOff(-1) end - self.LasingDrone.playertask.busy = false - self.LasingDrone.playertask.inreach = false - self.LasingDrone.playertask.id = 0 - self.LasingDrone.playertask.reachmessage = false + SelectedDrone.playertask.busy = false + SelectedDrone.playertask.inreach = false + SelectedDrone.playertask.id = 0 + SelectedDrone.playertask.reachmessage = false self:T(self.lid.."Laser Off") else -- not done yet - local dcoord = self.LasingDrone:GetCoordinate() + self:T(self.lid.."Not done yet") + local dcoord = SelectedDrone:GetCoordinate() local tcoord = task.Target:GetCoordinate() tcoord.y = tcoord.y + 2 local dist = dcoord:Get2DDistance(tcoord) + self:T(self.lid.."Dist "..dist) -- close enough? - if dist < 3000 and not self.LasingDrone:IsLasing() then + if dist < 3000 and not SelectedDrone:IsLasing() then self:T(self.lid.."Laser On") - self.LasingDrone:__LaserOn(-1,tcoord) - self.LasingDrone.playertask.inreach = true - if not self.LasingDrone.playertask.reachmessage then + SelectedDrone:__LaserOn(-1,tcoord) + SelectedDrone.playertask.inreach = true + if not SelectedDrone.playertask.reachmessage then --local textmark = self.gettext:GetEntry("FLARETASK",self.locale) - self.LasingDrone.playertask.reachmessage = true + SelectedDrone.playertask.reachmessage = true local clients = task:GetClients() local text = "" for _,playername in pairs(clients) do @@ -2661,7 +3115,7 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() local ttsplayername = playername if self.customcallsigns[playername] then ttsplayername = self.customcallsigns[playername] - end + end -- --text = string.format("%s, %s, pointer over target for task %03d, lasing!", playername, self.MenuName or self.Name, task.PlayerTaskNr) text = string.format(pointertext, ttsplayername, self.MenuName or self.Name, task.PlayerTaskNr) if not self.NoScreenOutput then @@ -2673,18 +3127,21 @@ function PLAYERTASKCONTROLLER:_CheckPrecisionTasks() ) if client then local m = MESSAGE:New(text,15,"Tasking"):ToClient(client) - end - end - end + end -- + end -- + end -- if self.UseSRS then self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2) - end - end - end - end - end - end - end + end -- + end -- + end -- + end -- end else + end -- end handle drones with a task + end -- end function + + self.LasingDroneSet:ForEachGroup(DronesWithTask) + + end -- return self end @@ -3146,6 +3603,32 @@ function PLAYERTASKCONTROLLER:_SwitchFlashing(Group, Client) return self end +function PLAYERTASKCONTROLLER:_ShowRadioInfo(Group, Client) + self:T(self.lid.."_ShowRadioInfo") + local playername, ttsplayername = self:_GetPlayerName(Client) + + if self.UseSRS then + local frequency = self.Frequency + local freqtext = "" + if type(frequency) == "table" then + freqtext = self.gettext:GetEntry("FREQUENCIES",self.locale) + freqtext = freqtext..table.concat(frequency,", ") + else + local freqt = self.gettext:GetEntry("FREQUENCY",self.locale) + freqtext = string.format(freqt,frequency) + end + + local switchtext = self.gettext:GetEntry("BROADCAST",self.locale) + + playername = ttsplayername or self:_GetTextForSpeech(playername) + --local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext) + local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext) + self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2,{Group},text,30,self.BCFrequency,self.BCModulation) + end + + return self +end + --- [Internal] Flashing directional info for a client -- @param #PLAYERTASKCONTROLLER self -- @return #PLAYERTASKCONTROLLER self @@ -3171,6 +3654,22 @@ function PLAYERTASKCONTROLLER:_FlashInfo() return self end +--- [Internal] Find matching drone for precision bombing task, if any is assigned. +-- @param #PLAYERTASKCONTROLLER self +-- @param #number ID Task ID to look for +-- @return Ops.OpsGroup#OPSGROUP Drone +function PLAYERTASKCONTROLLER:_FindLasingDroneForTaskID(ID) + local drone = nil + self.LasingDroneSet:ForEachGroup( + function(grp) + if grp and grp:IsAlive() and grp.playertask and grp.playertask.id and grp.playertask.id == ID then + drone = grp + end + end + ) + return drone +end + --- [Internal] Show active task info -- @param #PLAYERTASKCONTROLLER self -- @param Ops.PlayerTask#PLAYERTASK Task @@ -3198,6 +3697,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local Elevation = Coordinate:GetLandHeight() or 0 -- meters local CoordText = "" local CoordTextLLDM = nil + local LasingDrone = self:_FindLasingDroneForTaskID(task.PlayerTaskNr) if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then CoordText = Coordinate:ToStringA2G(Client,nil,self.ShowMagnetic) else @@ -3233,14 +3733,14 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = text .. string.format(elev,tostring(math.floor(Elevation)),elevationmeasure) -- Prec bombing if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone and self.LasingDrone.playertask then + if LasingDrone and LasingDrone.playertask then local yes = self.gettext:GetEntry("YES",self.locale) local no = self.gettext:GetEntry("NO",self.locale) - local inreach = self.LasingDrone.playertask.inreach == true and yes or no - local islasing = self.LasingDrone:IsLasing() == true and yes or no + local inreach = LasingDrone.playertask.inreach == true and yes or no + local islasing = LasingDrone:IsLasing() == true and yes or no local prectext = self.gettext:GetEntry("POINTERTARGETREPORT",self.locale) prectext = string.format(prectext,inreach,islasing) - text = text .. prectext.." ("..self.LaserCode..")" + text = text .. prectext.." ("..LasingDrone.playertask.lasercode..")" end end -- Buddylasing @@ -3258,7 +3758,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local pcoord = player:GetCoordinate() if pcoord:Get2DDistance(Coordinate) <= reachdist then inreach = true - local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations) + local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations,self.CallsignCustomFunc,self.CallsignCustomArgs) local playername = player:GetPlayerName() local islasing = no if self.PlayerRecce.CanLase[player:GetTypeName()] and self.PlayerRecce.AutoLase[playername] then @@ -3345,7 +3845,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local ttstext = string.format(ThreatLocaleTextTTS,ttsplayername,self.MenuName or self.Name,ttstaskname,ThreatLevelText, targets, CoordText) -- POINTERTARGETLASINGTTS = ". Pointer over target and lasing." if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then - if self.LasingDrone.playertask.inreach and self.LasingDrone:IsLasing() then + if LasingDrone and LasingDrone.playertask.inreach and LasingDrone:IsLasing() then local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale) ttstext = ttstext .. lasingtext end @@ -3365,7 +3865,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) text = self.gettext:GetEntry("NOACTIVETASK",self.locale) end if not self.NoScreenOutput then - local m=MESSAGE:New(text,15,"Tasking"):ToClient(Client) + local m=MESSAGE:New(text,self.TaskInfoDuration or 30,"Tasking"):ToClient(Client) end return self end @@ -3719,6 +4219,11 @@ function PLAYERTASKCONTROLLER:_CreateJoinMenuTemplate() self.MenuNoTask = nil end + if self.InformationMenu then + local radioinfo = self.gettext:GetEntry("RADIOS",self.locale) + JoinTaskMenuTemplate:NewEntry(radioinfo,self.JoinTopMenu,self._ShowRadioInfo,self) + end + self.JoinTaskMenuTemplate = JoinTaskMenuTemplate return self @@ -4058,8 +4563,9 @@ end -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here. -- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here. -- @param Core.Point#COORDINATE Coordinate Coordinate from which the controller radio is sending +-- @param #string Backend (Optional) MSRS Backend to be used, can be MSRS.Backend.SRSEXE or MSRS.Backend.GRPC; if you use a config file for MSRS, hand in nil here. -- @return #PLAYERTASKCONTROLLER self -function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate) +function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Coordinate,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" -- self.Gender = Gender or MSRS.gender or "male" -- @@ -4075,7 +4581,7 @@ function PLAYERTASKCONTROLLER:SetSRS(Frequency,Modulation,PathToSRS,Gender,Cultu self.Modulation = Modulation or {radio.modulation.FM,radio.modulation.AM} -- self.BCModulation = self.Modulation -- set up SRS - self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation) + self.SRS=MSRS:New(self.PathToSRS,self.Frequency,self.Modulation,Backend) self.SRS:SetCoalition(self.Coalition) self.SRS:SetLabel(self.MenuName or self.Name) self.SRS:SetGender(self.Gender) @@ -4138,6 +4644,7 @@ function PLAYERTASKCONTROLLER:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Crash, self._EventHandler) self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.UnitLost, self._EventHandler) self:SetEventPriority(5) return self end diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index a4c00cfea..02e0828a7 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -660,9 +660,9 @@ function RECOVERYTANKER:SetRecoveryAirboss(switch) return self end ---- Set that the group takes the roll of an AWACS instead of a refueling tanker. +--- Set that the group takes the role of an AWACS instead of a refueling tanker. -- @param #RECOVERYTANKER self --- @param #boolean switch If true or nil, set roll AWACS. +-- @param #boolean switch If true or nil, set role AWACS. -- @param #boolean eplrs If true or nil, enable EPLRS. If false, EPLRS will be off. -- @return #RECOVERYTANKER self function RECOVERYTANKER:SetAWACS(switch, eplrs) @@ -997,6 +997,8 @@ function RECOVERYTANKER:onafterStart(From, Event, To) -- Init status updates in 10 seconds. self:__Status(10) + + return self end diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 65f1e57c5..70a1e95f4 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -963,6 +963,8 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Init status check self:__Status(1) + + return self end --- On after Status event. Checks player status. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 5f72742d6..fcc108087 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -153,13 +153,13 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.6.0" +TARGET.version="0.7.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Had cases where target life was 0 but target was not dead. Need to figure out why! +-- DONE: Had cases where target life was 0 but target was not dead. Need to figure out why! <== This is due to delayed dead event. -- DONE: Add pseudo functions. -- DONE: Initial object can be nil. @@ -243,6 +243,36 @@ function TARGET:New(TargetObject) -- @function [parent=#TARGET] __Status -- @param #TARGET self -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "ObjectDamaged". + -- @function [parent=#TARGET] ObjectDamaged + -- @param #TARGET self + -- @param #TARGET.Object Target Target object. + + --- Triggers the FSM event "ObjectDestroyed". + -- @function [parent=#TARGET] ObjectDestroyed + -- @param #TARGET self + -- @param #TARGET.Object Target Target object. + + --- Triggers the FSM event "ObjectDead". + -- @function [parent=#TARGET] ObjectDead + -- @param #TARGET self + -- @param #TARGET.Object Target Target object. + + + --- Triggers the FSM event "Damaged". + -- @function [parent=#TARGET] Damaged + -- @param #TARGET self + + --- Triggers the FSM event "Destroyed". + -- @function [parent=#TARGET] Destroyed + -- @param #TARGET self + + --- Triggers the FSM event "Dead". + -- @function [parent=#TARGET] Dead + -- @param #TARGET self + --- On After "ObjectDamaged" event. A (sub-) target object has been damaged, e.g. a UNIT of a GROUP, or an object of a SET -- @function [parent=#TARGET] OnAfterObjectDamaged @@ -267,22 +297,23 @@ function TARGET:New(TargetObject) -- @param #string Event Event. -- @param #string To To state. -- @param #TARGET.Object Target Target object. + - --- On After "Damaged" event. The (whole) target object has been damaged. + --- On After "Damaged" event. Any of the target objects has been damaged. -- @function [parent=#TARGET] OnAfterDamaged -- @param #TARGET self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - --- On After "ObjectDestroyed" event. The (whole) target object has been destroyed. + --- On After "Destroyed" event. All target objects have been destroyed. -- @function [parent=#TARGET] OnAfterDestroyed -- @param #TARGET self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - --- On After "ObjectDead" event. The (whole) target object is dead. + --- On After "Dead" event. All target objects are dead. -- @function [parent=#TARGET] OnAfterDead -- @param #TARGET self -- @param #string From From state. @@ -290,7 +321,7 @@ function TARGET:New(TargetObject) -- @param #string To To state. -- Start. - self:__Start(-1) + self:__Start(-0.1) return self end @@ -356,6 +387,8 @@ function TARGET:AddObject(Object) if Object:IsInstanceOf("OPSGROUP") then self:_AddObject(Object:GetGroup()) -- We add the MOOSE GROUP object not the OPSGROUP object. + --elseif Object:IsInstanceOf("OPSZONE") then + --self:_AddObject(Object:GetZone()) else self:_AddObject(Object) end @@ -527,6 +560,11 @@ function TARGET:IsAlive() for _,_target in pairs(self.targets) do local target=_target --Ops.Target#TARGET.Object if target.Status~=TARGET.ObjectStatus.DEAD then + if self.isDestroyed then + self:E(self.lid..string.format("ERROR: target is DESTROYED but target object status is not DEAD but %s for object %s", target.Status, target.Name)) + elseif self:IsDead() then + self:E(self.lid..string.format("ERROR: target is DEAD but target object status is not DEAD but %s for object %s", target.Status, target.Name)) + end return true end end @@ -550,6 +588,25 @@ function TARGET:IsDead() return is end +--- Check if target object is dead. +-- @param #TARGET self +-- @param #TARGET.Object TargetObject The target object. +-- @return #boolean If true, target is dead. +function TARGET:IsTargetDead(TargetObject) + local isDead=TargetObject.Status==TARGET.ObjectStatus.DEAD + return isDead +end + +--- Check if target object is alive. +-- @param #TARGET self +-- @param #TARGET.Object TargetObject The target object. +-- @return #boolean If true, target is dead. +function TARGET:IsTargetAlive(TargetObject) + local isAlive=TargetObject.Status==TARGET.ObjectStatus.ALIVE + return isAlive +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -581,7 +638,8 @@ end -- @param #string Event Event. -- @param #string To To state. function TARGET:onafterStatus(From, Event, To) - self:T({From, Event, To}) + --self:T({From, Event, To}) + -- FSM state. local fsmstate=self:GetState() @@ -592,6 +650,7 @@ function TARGET:onafterStatus(From, Event, To) -- old life local life=target.Life + -- curr life target.Life=self:GetTargetLife(target) @@ -603,13 +662,14 @@ function TARGET:onafterStatus(From, Event, To) self.life0 = self.life0+delta end + -- Check if life decreased ==> damaged if target.Life object dead now for target object %s!", tostring(target.Name))) self:ObjectDead(target) damaged = true @@ -624,11 +684,12 @@ function TARGET:onafterStatus(From, Event, To) -- Log output verbose=1. if self.verbose>=1 then - local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.N0, self:GetLife(), self:GetLife0(), self:GetDamage()) + local text=string.format("%s: Targets=%d/%d [%d, %d], Life=%.1f/%.1f, Damage=%.1f", + fsmstate, self:CountTargets(), self.N0, self.Ndestroyed, self.Ndead, self:GetLife(), self:GetLife0(), self:GetDamage()) if self:CountTargets() == 0 or self:GetDamage() >= 100 then - text=text.." Dead!" + text=text.." - Dead!" elseif damaged then - text=text.." Damaged!" + text=text.." - Damaged!" end self:I(self.lid..text) end @@ -639,19 +700,35 @@ function TARGET:onafterStatus(From, Event, To) for i,_target in pairs(self.targets) do local target=_target --#TARGET.Object local damage=(1-target.Life/target.Life0)*100 - text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage) + text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f, N0=%d, Ndestroyed=%d, Ndead=%d", + i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage, target.N0, target.Ndestroyed, target.Ndead) end self:I(self.lid..text) end - if self:CountTargets() == 0 or self:GetDamage() >= 100 then + -- Consitency check if target is still alive but all target objects are dead + if self:IsAlive() and (self:CountTargets()==0 or self:GetDamage()>=100) then self:Dead() end + -- Quick sanity check + for i,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + if target.Ndestroyed>target.N0 then + self:E(self.lid..string.format("ERROR: Number of destroyed target objects greater than number of initial target objects: %d>%d!", target.Ndestroyed, target.N0)) + end + if target.Ndestroyed>target.N0 then + self:E(self.lid..string.format("ERROR: Number of dead target objects greater than number of initial target objects: %d>%d!", target.Ndead, target.N0)) + end + end + -- Update status again in 30 sec. if self:IsAlive() then self:__Status(-self.TStatus) + else + self:I(self.lid..string.format("Target is not alive any more ==> no further status updates are carried out")) end + return self end @@ -687,6 +764,10 @@ function TARGET:onafterObjectDestroyed(From, Event, To, Target) -- Increase destroyed counter. self.Ndestroyed=self.Ndestroyed+1 + Target.Ndestroyed=Target.Ndestroyed+1 + + Target.Life=0 + -- Call object dead event. self:ObjectDead(Target) @@ -707,6 +788,12 @@ function TARGET:onafterObjectDead(From, Event, To, Target) -- Set target status. Target.Status=TARGET.ObjectStatus.DEAD + -- Increase dead object counter + Target.Ndead=Target.Ndead+1 + + -- Set target object life to 0. + Target.Life=0 + -- Increase dead counter. self.Ndead=self.Ndead+1 @@ -716,6 +803,7 @@ function TARGET:onafterObjectDead(From, Event, To, Target) local target=_target --#TARGET.Object if target.Status==TARGET.ObjectStatus.ALIVE then dead=false + break -- break the loop because we now we are not dead end end @@ -759,7 +847,6 @@ end -- @param #string Event Event. -- @param #string To To state. function TARGET:onafterDestroyed(From, Event, To) - self:T({From, Event, To}) self:T(self.lid..string.format("TARGET destroyed")) @@ -813,23 +900,27 @@ function TARGET:OnEventUnitDeadOrLost(EventData) -- Check if we could find a target object. if target then + local Ndead=target.Ndead + local Ndestroyed=target.Ndestroyed if EventData.id==EVENTS.RemoveUnit then - target.Ndead=target.Ndead+1 + Ndead=Ndead+1 else - target.Ndestroyed=target.Ndestroyed+1 - target.Ndead=target.Ndead+1 + Ndestroyed=Ndestroyed+1 + Ndead=Ndead+1 end - if target.Ndead==target.N0 then - if target.Ndestroyed>=target.N0 then + -- Check if ALL objects are dead + if Ndead==target.N0 then + + if Ndestroyed>=target.N0 then -- Debug message. self:T2(self.lid..string.format("EVENT ID=%d: target %s dead/lost ==> destroyed", EventData.id, tostring(target.Name))) target.Life = 0 - -- Trigger object destroyed event. + -- Trigger object destroyed event. This sets the Life to zero and increases Ndestroyed self:ObjectDestroyed(target) else @@ -839,7 +930,7 @@ function TARGET:OnEventUnitDeadOrLost(EventData) target.Life = 0 - -- Trigger object dead event. + -- Trigger object dead event. This sets the Life to zero and increases Ndead counter self:ObjectDead(target) end @@ -979,6 +1070,8 @@ function TARGET:_AddObject(Object) target.Life0=1 target.Life=1 + + target.N0=target.N0+1 elseif Object:IsInstanceOf("ZONE_BASE") then @@ -992,6 +1085,8 @@ function TARGET:_AddObject(Object) target.Life0=1 target.Life=1 + + target.N0=target.N0+1 elseif Object:IsInstanceOf("OPSZONE") then @@ -1203,11 +1298,27 @@ function TARGET:GetTargetThreatLevelMax(Target) return 0 elseif Target.Type==TARGET.ObjectType.ZONE then + + local zone = Target.Object -- Core.Zone#ZONE_RADIUS + local foundunits = {} + if zone:IsInstanceOf("ZONE_RADIUS") or zone:IsInstanceOf("ZONE_POLYGON") then + zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}) + foundunits = zone:GetScannedSetUnit() + else + foundunits = SET_UNIT:New():FilterZones({zone}):FilterOnce() + end + local ThreatMax = foundunits:GetThreatLevelMax() or 0 + return ThreatMax - return 0 + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + local unitset = Target.Object:GetScannedUnitSet() -- Core.Set#SET_UNIT + local ThreatMax = unitset:GetThreatLevelMax() + return ThreatMax else self:E("ERROR: unknown target object type in GetTargetThreatLevel!") + return 0 end return self @@ -1919,12 +2030,16 @@ function TARGET:CountObjectives(Target, Coalitions) elseif Target.Type==TARGET.ObjectType.COORDINATE then - -- No target we can check! + -- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate. + -- This is also more consitent with the life and is alive status. + N=N+1 elseif Target.Type==TARGET.ObjectType.ZONE then - -- No target we can check! - + -- No target, where we can check the alive status, so we assume it is alive. Changed this because otherwise target count is 0 if we pass a coordinate. + -- This is also more consitent with the life and is alive status. + N=N+1 + elseif Target.Type==TARGET.ObjectType.OPSZONE then local target=Target.Object --Ops.OpsZone#OPSZONE diff --git a/Moose Development/Moose/Sound/Radio.lua b/Moose Development/Moose/Sound/Radio.lua index 68acf2a5a..961ccdc6a 100644 --- a/Moose Development/Moose/Sound/Radio.lua +++ b/Moose Development/Moose/Sound/Radio.lua @@ -97,6 +97,7 @@ RADIO = { Power = 100, Loop = false, alias = nil, + moduhasbeenset = false, } --- Create a new RADIO Object. This doesn't broadcast a transmission, though, use @{#RADIO.Broadcast} to actually broadcast. @@ -167,12 +168,13 @@ function RADIO:SetFrequency(Frequency) self:F2(Frequency) if type(Frequency) == "number" then - + -- If frequency is in range --if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then -- Convert frequency from MHz to Hz - self.Frequency = Frequency * 1000000 + self.Frequency = Frequency + self.HertzFrequency = Frequency * 1000000 -- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then @@ -180,7 +182,7 @@ function RADIO:SetFrequency(Frequency) local commandSetFrequency={ id = "SetFrequency", params = { - frequency = self.Frequency, + frequency = self.HertzFrequency, modulation = self.Modulation, } } @@ -197,7 +199,7 @@ function RADIO:SetFrequency(Frequency) return self end ---- Set AM or FM modulation of the radio transmitter. +--- Set AM or FM modulation of the radio transmitter. Set this before you set a frequency! -- @param #RADIO self -- @param #number Modulation Modulation is either radio.modulation.AM or radio.modulation.FM. -- @return #RADIO self @@ -206,6 +208,10 @@ function RADIO:SetModulation(Modulation) if type(Modulation) == "number" then if Modulation == radio.modulation.AM or Modulation == radio.modulation.FM then --TODO Maybe make this future proof if ED decides to add an other modulation ? self.Modulation = Modulation + if self.moduhasbeenset == false and Modulation == radio.modulation.FM then -- override default + self:SetFrequency(self.Frequency) + end + self.moduhasbeenset = true return self end end diff --git a/Moose Development/Moose/Sound/RadioQueue.lua b/Moose Development/Moose/Sound/RadioQueue.lua index 43a5c6aac..b558f6be4 100644 --- a/Moose Development/Moose/Sound/RadioQueue.lua +++ b/Moose Development/Moose/Sound/RadioQueue.lua @@ -361,7 +361,7 @@ end -- @param #RADIOQUEUE self -- @param #RADIOQUEUE.Transmission transmission The transmission. function RADIOQUEUE:Broadcast(transmission) - self:T("Broarcast") + self:T("Broadcast") if ((transmission.soundfile and transmission.soundfile.useSRS) or transmission.soundtext) and self.msrs then self:_BroadcastSRS(transmission) @@ -380,7 +380,8 @@ function RADIOQUEUE:Broadcast(transmission) self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName())) - if not self.senderinit then + --if not self.senderinit then + -- TODO Seems to be a DCS bug - if I explode ANY unit in a group the BC assignment gets lost -- Command to set the Frequency for the transmission. local commandFrequency={ @@ -394,7 +395,7 @@ function RADIOQUEUE:Broadcast(transmission) sender:SetCommand(commandFrequency) self.senderinit=true - end + --end -- Set subtitle only if duration>0 sec. local subtitle=nil @@ -455,7 +456,7 @@ function RADIOQUEUE:Broadcast(transmission) MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll() end else - self:E("ERROR: Could not get vec3 to determin transmission origin! Did you specify a sender and is it still alive?") + self:E("ERROR: Could not get vec3 to determine transmission origin! Did you specify a sender and is it still alive?") end end diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index b27564a62..c2e9cf152 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -52,6 +52,7 @@ -- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`. -- @field #string provider Provider of TTS (win, gcloud, azure, amazon). -- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE or MSRS.Backend.GRPC). +-- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe -- @extends Core.Base#BASE --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -256,26 +257,156 @@ MSRS = { ConfigFilePath = "Config\\", ConfigLoaded = false, poptions = {}, + UsePowerShell = false, } --- MSRS class version. -- @field #string version -MSRS.version="0.3.0" +MSRS.version="0.3.3" --- Voices -- @type MSRS.Voices MSRS.Voices = { + Amazon = { + Generative = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + }, + en_US = { + Danielle = "Danielle", + Joanna = "Joanna", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + LongForm = { + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Ruth = "Ruth", + Patrick = "Patrick", + }, + es_ES = { + Alba = "Alba", + ["Raúl"] = "Raúl", + }, + }, + Neural = { + en_AU = { + Olivia = "Olivia", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + Arthur = "Arthur", + }, + en_US = { + Danielle = "Danielle", + Gregory = "Gregory", + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + Ruth = "Ruth", + Stephen = "Stephen", + }, + fr_FR = { + ["Léa"] = "Léa", + ["Rémi"] = "Rémi", + }, + de_DE = { + Vicki = "Vicki", + Daniel = "Daniel", + }, + it_IT = { + Bianca = "Bianca", + Adriano = "Adriano", + }, + es_ES = { + Lucia = "Lucia", + Sergio = "Sergio", + }, + }, + Standard = { + en_AU = { + Nicole = "Nicole", + Russel = "Russel", + }, + en_GB = { + Amy = "Amy", + Emma = "Emma", + Brian = "Brian", + }, + en_IN = { + Aditi = "Aditi", + Raveena = "Raveena", + }, + en_US = { + Ivy = "Ivy", + Joanna = "Joanna", + Kendra = "Kendra", + Kimberly = "Kimberly", + Salli = "Salli", + Joey = "Joey", + Kevin = "Kevin", + }, + fr_FR = { + Celine = "Celine", + ["Léa"] = "Léa", + Mathieu = "Mathieu", + }, + de_DE = { + Marlene = "Marlene", + Vicki = "Vicki", + Hans = "Hans", + }, + it_IT = { + Carla = "Carla", + Bianca = "Bianca", + Giorgio = "Giorgio", + }, + es_ES = { + Conchita = "Conchita", + Lucia = "Lucia", + Enrique = "Enrique", + }, + }, + }, Microsoft = { -- working ones if not using gRPC and MS ["Hedda"] = "Microsoft Hedda Desktop", -- de-DE ["Hazel"] = "Microsoft Hazel Desktop", -- en-GB ["David"] = "Microsoft David Desktop", -- en-US ["Zira"] = "Microsoft Zira Desktop", -- en-US ["Hortense"] = "Microsoft Hortense Desktop", --fr-FR - ["de-DE-Hedda"] = "Microsoft Hedda Desktop", -- de-DE - ["en-GB-Hazel"] = "Microsoft Hazel Desktop", -- en-GB - ["en-US-David"] = "Microsoft David Desktop", -- en-US - ["en-US-Zira"] = "Microsoft Zira Desktop", -- en-US - ["fr-FR-Hortense"] = "Microsoft Hortense Desktop", --fr-FR + ["de_DE_Hedda"] = "Microsoft Hedda Desktop", -- de-DE + ["en_GB_Hazel"] = "Microsoft Hazel Desktop", -- en-GB + ["en_US_David"] = "Microsoft David Desktop", -- en-US + ["en_US_Zira"] = "Microsoft Zira Desktop", -- en-US + ["fr_FR_Hortense"] = "Microsoft Hortense Desktop", --fr-FR }, MicrosoftGRPC = { -- en-US/GB voices only as of Jan 2024, working ones if using gRPC and MS, if voice packs are installed --["Hedda"] = "Hedda", -- de-DE @@ -304,8 +435,7 @@ MSRS.Voices = { ["en_CA_Linda"] = "Linda", --en-CA ["en_IN_Ravi"] = "Ravi", --en-IN ["en_IN_Heera"] = "Heera", --en-IN - ["en_IR_Sean"] = "Sean", --en-IR - --]] + ["en_IR_Sean"] = "Sean", --en-IR }, Google = { Standard = { @@ -317,11 +447,14 @@ MSRS.Voices = { ["en_IN_Standard_B"] = 'en-IN-Standard-B', -- [6] MALE ["en_IN_Standard_C"] = 'en-IN-Standard-C', -- [7] MALE ["en_IN_Standard_D"] = 'en-IN-Standard-D', -- [8] FEMALE - ["en_GB_Standard_A"] = 'en-GB-Standard-A', -- [9] FEMALE - ["en_GB_Standard_B"] = 'en-GB-Standard-B', -- [10] MALE - ["en_GB_Standard_C"] = 'en-GB-Standard-C', -- [11] FEMALE - ["en_GB_Standard_D"] = 'en-GB-Standard-D', -- [12] MALE - ["en_GB_Standard_F"] = 'en-GB-Standard-F', -- [13] FEMALE + -- 2025 changes + ["en_GB_Standard_A"] = 'en-GB-Standard-N', -- [9] FEMALE + ["en_GB_Standard_B"] = 'en-GB-Standard-O', -- [10] MALE + ["en_GB_Standard_C"] = 'en-GB-Standard-N', -- [11] FEMALE + ["en_GB_Standard_D"] = 'en-GB-Standard-O', -- [12] MALE + ["en_GB_Standard_F"] = 'en-GB-Standard-N', -- [13] FEMALE + ["en_GB_Standard_O"] = 'en-GB-Standard-O', -- [12] MALE + ["en_GB_Standard_N"] = 'en-GB-Standard-N', -- [13] FEMALE ["en_US_Standard_A"] = 'en-US-Standard-A', -- [14] MALE ["en_US_Standard_B"] = 'en-US-Standard-B', -- [15] MALE ["en_US_Standard_C"] = 'en-US-Standard-C', -- [16] FEMALE @@ -332,25 +465,36 @@ MSRS.Voices = { ["en_US_Standard_H"] = 'en-US-Standard-H', -- [21] FEMALE ["en_US_Standard_I"] = 'en-US-Standard-I', -- [22] MALE ["en_US_Standard_J"] = 'en-US-Standard-J', -- [23] MALE - ["fr_FR_Standard_A"] = "fr-FR-Standard-A", -- Female - ["fr_FR_Standard_B"] = "fr-FR-Standard-B", -- Male - ["fr_FR_Standard_C"] = "fr-FR-Standard-C", -- Female - ["fr_FR_Standard_D"] = "fr-FR-Standard-D", -- Male - ["fr_FR_Standard_E"] = "fr-FR-Standard-E", -- Female - ["de_DE_Standard_A"] = "de-DE-Standard-A", -- Female - ["de_DE_Standard_B"] = "de-DE-Standard-B", -- Male - ["de_DE_Standard_C"] = "de-DE-Standard-C", -- Female - ["de_DE_Standard_D"] = "de-DE-Standard-D", -- Male - ["de_DE_Standard_E"] = "de-DE-Standard-E", -- Male - ["de_DE_Standard_F"] = "de-DE-Standard-F", -- Female - ["es_ES_Standard_A"] = "es-ES-Standard-A", -- Female - ["es_ES_Standard_B"] = "es-ES-Standard-B", -- Male - ["es_ES_Standard_C"] = "es-ES-Standard-C", -- Female - ["es_ES_Standard_D"] = "es-ES-Standard-D", -- Female - ["it_IT_Standard_A"] = "it-IT-Standard-A", -- Female - ["it_IT_Standard_B"] = "it-IT-Standard-B", -- Female - ["it_IT_Standard_C"] = "it-IT-Standard-C", -- Male - ["it_IT_Standard_D"] = "it-IT-Standard-D", -- Male + -- 2025 catalog changes + ["fr_FR_Standard_A"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_B"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_C"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_D"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_E"] = "fr-FR-Standard-F", -- Female + ["fr_FR_Standard_G"] = "fr-FR-Standard-G", -- Male + ["fr_FR_Standard_F"] = "fr-FR-Standard-F", -- Female + -- 2025 catalog changes + ["de_DE_Standard_A"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_B"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_C"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_D"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_E"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_F"] = "de-DE-Standard-G", -- Female + ["de_DE_Standard_H"] = "de-DE-Standard-H", -- Male + ["de_DE_Standard_G"] = "de-DE-Standard-G", -- Female + ["es_ES_Standard_A"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_B"] = "es-ES-Standard-F", -- Male + ["es_ES_Standard_C"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_D"] = "es-ES-Standard-F", -- Male + ["es_ES_Standard_E"] = "es-ES-Standard-E", -- Female + ["es_ES_Standard_F"] = "es-ES-Standard-F", -- Male + -- 2025 catalog changes + ["it_IT_Standard_A"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_B"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_C"] = "it-IT-Standard-F", -- Male + ["it_IT_Standard_D"] = "it-IT-Standard-F", -- Male + ["it_IT_Standard_E"] = "it-IT-Standard-E", -- Female + ["it_IT_Standard_F"] = "it-IT-Standard-F", -- Male }, Wavenet = { ["en_AU_Wavenet_A"] = 'en-AU-Wavenet-A', -- [1] FEMALE @@ -361,12 +505,15 @@ MSRS.Voices = { ["en_IN_Wavenet_B"] = 'en-IN-Wavenet-B', -- [6] MALE ["en_IN_Wavenet_C"] = 'en-IN-Wavenet-C', -- [7] MALE ["en_IN_Wavenet_D"] = 'en-IN-Wavenet-D', -- [8] FEMALE - ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-A', -- [9] FEMALE - ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-B', -- [10] MALE - ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-C', -- [11] FEMALE - ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-D', -- [12] MALE - ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-F', -- [13] FEMALE - ["en_US_Wavenet_A"] = 'en-US-Wavenet-A', -- [14] MALE + -- 2025 changes + ["en_GB_Wavenet_A"] = 'en-GB-Wavenet-N', -- [9] FEMALE + ["en_GB_Wavenet_B"] = 'en-GB-Wavenet-O', -- [10] MALE + ["en_GB_Wavenet_C"] = 'en-GB-Wavenet-N', -- [11] FEMALE + ["en_GB_Wavenet_D"] = 'en-GB-Wavenet-O', -- [12] MALE + ["en_GB_Wavenet_F"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_GB_Wavenet_O"] = 'en-GB-Wavenet-O', -- [12] MALE + ["en_GB_Wavenet_N"] = 'en-GB-Wavenet-N', -- [13] FEMALE + ["en_US_Wavenet_A"] = 'en-US-Wavenet-N', -- [14] MALE ["en_US_Wavenet_B"] = 'en-US-Wavenet-B', -- [15] MALE ["en_US_Wavenet_C"] = 'en-US-Wavenet-C', -- [16] FEMALE ["en_US_Wavenet_D"] = 'en-US-Wavenet-D', -- [17] MALE @@ -376,24 +523,35 @@ MSRS.Voices = { ["en_US_Wavenet_H"] = 'en-US-Wavenet-H', -- [21] FEMALE ["en_US_Wavenet_I"] = 'en-US-Wavenet-I', -- [22] MALE ["en_US_Wavenet_J"] = 'en-US-Wavenet-J', -- [23] MALE - ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-A", -- Female - ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-B", -- Male - ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-C", -- Female - ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-D", -- Male - ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-E", -- Female - ["de_DE_Wavenet_A"] = "de-DE-Wavenet-A", -- Female - ["de_DE_Wavenet_B"] = "de-DE-Wavenet-B", -- Male - ["de_DE_Wavenet_C"] = "de-DE-Wavenet-C", -- Female - ["de_DE_Wavenet_D"] = "de-DE-Wavenet-D", -- Male - ["de_DE_Wavenet_E"] = "de-DE-Wavenet-E", -- Male - ["de_DE_Wavenet_F"] = "de-DE-Wavenet-F", -- Female - ["es_ES_Wavenet_B"] = "es-ES-Wavenet-B", -- Male - ["es_ES_Wavenet_C"] = "es-ES-Wavenet-C", -- Female - ["es_ES_Wavenet_D"] = "es-ES-Wavenet-D", -- Female - ["it_IT_Wavenet_A"] = "it-IT-Wavenet-A", -- Female - ["it_IT_Wavenet_B"] = "it-IT-Wavenet-B", -- Female - ["it_IT_Wavenet_C"] = "it-IT-Wavenet-C", -- Male - ["it_IT_Wavenet_D"] = "it-IT-Wavenet-D", -- Male + -- 2025 catalog changes + ["fr_FR_Wavenet_A"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_B"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_C"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_D"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_E"] = "fr-FR-Wavenet-F", -- Female + ["fr_FR_Wavenet_G"] = "fr-FR-Wavenet-G", -- Male + ["fr_FR_Wavenet_F"] = "fr-FR-Wavenet-F", -- Female + -- 2025 catalog changes + ["de_DE_Wavenet_A"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_B"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_C"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_D"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_E"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_F"] = "de-DE-Wavenet-G", -- Female + ["de_DE_Wavenet_H"] = "de-DE-Wavenet-H", -- Male + ["de_DE_Wavenet_G"] = "de-DE-Wavenet-G", -- Female + ["es_ES_Wavenet_B"] = "es-ES-Wavenet-E", -- Male + ["es_ES_Wavenet_C"] = "es-ES-Wavenet-F", -- Female + ["es_ES_Wavenet_D"] = "es-ES-Wavenet-E", -- Female + ["es_ES_Wavenet_E"] = "es-ES-Wavenet-E", -- Male + ["es_ES_Wavenet_F"] = "es-ES-Wavenet-F", -- Female + -- 2025 catalog changes + ["it_IT_Wavenet_A"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_B"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_C"] = "it-IT-Wavenet-F", -- Male + ["it_IT_Wavenet_D"] = "it-IT-Wavenet-F", -- Male + ["it_IT_Wavenet_E"] = "it-IT-Wavenet-E", -- Female + ["it_IT_Wavenet_F"] = "it-IT-Wavenet-F", -- Male } , }, } @@ -589,7 +747,7 @@ function MSRS:SetBackendSRSEXE() end --- Set the default backend. --- @param #MSRS self +-- @param #string Backend function MSRS.SetDefaultBackend(Backend) MSRS.backend=Backend or MSRS.Backend.SRSEXE end @@ -945,7 +1103,7 @@ end -- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default) -- - `MSRS.Provider.GOOGLE`: Google Cloud -- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend) --- - `MSRS.Provier.AMAZON`: Amazone Web Service (only with DCS-gRPC backend) +-- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend) -- -- Note that all providers except Microsoft Windows need as additonal information the credentials of your account. -- @@ -1155,7 +1313,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay) -- Append file. command=command..' --file="'..tostring(soundfile)..'"' - + command=string.gsub(command,"--ssml","-h") + -- Execute command. self:_ExecCommand(command) @@ -1238,7 +1397,7 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate} ) if Delay and Delay>0 then - self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) + self:ScheduleOnce(Delay, self.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate) else Frequencies = Frequencies or self:GetFrequencies() @@ -1376,20 +1535,25 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp modus=modus:gsub("1", "FM") -- Command. + local pwsh = string.format('Start-Process -WindowStyle Hidden -WorkingDirectory \"%s\" -FilePath \"%s\" -ArgumentList \'-f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume ) + local command=string.format('"%s\\%s" -f "%s" -m "%s" -c %s -p %s -n "%s" -v "%.1f"', path, exe, freqs, modus, coal, port, label,volume) -- Set voice or gender/culture. - if voice then + if voice and self.UsePowerShell ~= true then -- Use a specific voice (no need for gender and/or culture. command=command..string.format(" --voice=\"%s\"", tostring(voice)) + pwsh=pwsh..string.format(" --voice=\"%s\"", tostring(voice)) else -- Add gender. if gender and gender~="female" then command=command..string.format(" -g %s", tostring(gender)) + pwsh=pwsh..string.format(" -g %s", tostring(gender)) end -- Add culture. if culture and culture~="en-GB" then command=command..string.format(" -l %s", tostring(culture)) + pwsh=pwsh..string.format(" -l %s", tostring(culture)) end end @@ -1397,16 +1561,18 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp if coordinate then local lat,lon,alt=self:_GetLatLongAlt(coordinate) command=command..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) + pwsh=pwsh..string.format(" -L %.4f -O %.4f -A %d", lat, lon, alt) end -- Set provider options if self.provider==MSRS.Provider.GOOGLE then local pops=self:GetProviderOptions() command=command..string.format(' --ssml -G "%s"', pops.credentials) + pwsh=pwsh..string.format(' --ssml -G "%s"', pops.credentials) elseif self.provider==MSRS.Provider.WINDOWS then -- Nothing to do. else - self:E("ERROR: SRS only supports WINWOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as ") + self:E("ERROR: SRS only supports WINDOWS and GOOGLE as TTS providers! Use DCS-gRPC backend for other providers such as AWS and Azure.") end if not UTILS.FileExists(fullPath) then @@ -1416,8 +1582,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp -- Debug output. self:T("MSRS command from _GetCommand="..command) - - return command + + if self.UsePowerShell == true then + return pwsh + else + return command + end end --- Execute SRS command to play sound using the `DCS-SR-ExternalAudio.exe`. @@ -1425,7 +1595,7 @@ end -- @param #string command Command to executer -- @return #number Return value of os.execute() command. function MSRS:_ExecCommand(command) - self:F( {command=command} ) + self:T2( {command=command} ) -- Skip this function if _GetCommand was not able to find the executable if string.find(command, "CommandNotFound") then return 0 end @@ -1433,7 +1603,13 @@ function MSRS:_ExecCommand(command) local batContent = command.." && exit" -- Create a tmp file. local filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".bat" - + + if self.UsePowerShell == true then + filename=os.getenv('TMP').."\\MSRS-"..MSRS.uuid()..".ps1" + batContent = command .. "\'" + self:T({batContent=batContent}) + end + local script=io.open(filename, "w+") script:write(batContent) script:close() @@ -1442,7 +1618,7 @@ function MSRS:_ExecCommand(command) self:T("MSRS batch content: "..batContent) local res=nil - if true then + if self.UsePowerShell ~= true then -- Create a tmp file. local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs" @@ -1470,23 +1646,20 @@ function MSRS:_ExecCommand(command) timer.scheduleFunction(os.remove, filenvbs, timer.getTime()+1) self:T("MSRS vbs and batch file removed") - elseif false then - - -- Create a tmp file. - local filenvbs = os.getenv('TMP') .. "\\MSRS-"..MSRS.uuid()..".vbs" - - -- VBS script - local script = io.open(filenvbs, "w+") - script:write(string.format('Set oShell = CreateObject ("Wscript.Shell")\n')) - script:write(string.format('Dim strArgs\n')) - script:write(string.format('strArgs = "cmd /c %s"\n', filename)) - script:write(string.format('oShell.Run strArgs, 0, false')) - script:close() - - local runvbs=string.format('cscript.exe //Nologo //B "%s"', filenvbs) + elseif self.UsePowerShell == true then + local pwsh = string.format('start /min "" powershell.exe -ExecutionPolicy Unrestricted -WindowStyle Hidden -Command "%s"',filename) + --env.info("[MSRS] TextToSpeech Command :\n" .. pwsh.."\n") + + if string.len(pwsh) > 255 then + self:E("[MSRS] - pwsh string too long") + end + -- Play file in 0.01 seconds - res=os.execute(runvbs) + res=os.execute(pwsh) + + -- Remove file in 1 second. + timer.scheduleFunction(os.remove, filename, timer.getTime()+1) else -- Play command. @@ -1560,8 +1733,8 @@ end function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate) -- Debug info. - self:F("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") - self:F({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate}) + self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()") + self:T({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate}) local options = {} -- #MSRS.GRPCOptions @@ -1587,7 +1760,6 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab -- Provider (win, gcloud, ...) local provider = self.provider or MSRS.Provider.WINDOWS - self:F({provider=provider}) -- Provider options: voice, credentials options.provider = {} @@ -1595,7 +1767,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab -- Voice Voice=Voice or self:GetVoice(self.provider) or self.voice - + if Voice then -- We use a specific voice options.provider[provider].voice = Voice @@ -1618,11 +1790,11 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab ssml=string.format("%s", gender, language, Text) end end - + for _,freq in pairs(Frequencies) do - self:F("Calling GRPC.tts with the following parameter:") - self:F({ssml=ssml, freq=freq, options=options}) - self:F(options.provider[provider]) + self:T("Calling GRPC.tts with the following parameter:") + self:T({ssml=ssml, freq=freq, options=options}) + self:T(options.provider[provider]) GRPC.tts(ssml, freq*1e6, options) end @@ -1944,7 +2116,7 @@ end -- @param Core.Point#COORDINATE coordinate Coordinate to be used -- @return #MSRSQUEUE.Transmission Radio transmission table. function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate) - + self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label}) if self.TransmitOnlyWithPlayers then if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then return self @@ -1984,7 +2156,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr transmission.volume = volume or msrs.volume transmission.label = label or msrs.Label transmission.coordinate = coordinate or msrs.coordinate - + -- Add transmission to queue. self:AddTransmission(transmission) diff --git a/Moose Development/Moose/Sound/SoundOutput.lua b/Moose Development/Moose/Sound/SoundOutput.lua index 466d0fa22..638dc9aec 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 (STTS) +-- * Create a SOUNDTEXT object for text-to-speech output vis SRS Simple-Text-To-Speech (MSRS) -- -- === -- @@ -24,7 +24,7 @@ do -- Sound Base - --- @type SOUNDBASE + -- @type SOUNDBASE -- @field #string ClassName Name of the class. -- @extends Core.Base#BASE @@ -100,7 +100,7 @@ end do -- Sound File - --- @type SOUNDFILE + -- @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. This includes the final slash "/". @@ -292,7 +292,7 @@ end do -- Text-To-Speech - --- @type SOUNDTEXT + -- @type SOUNDTEXT -- @field #string ClassName Name of the class -- @field #string text Text to speak. -- @field #number duration Duration in seconds. diff --git a/Moose Development/Moose/Sound/UserSound.lua b/Moose Development/Moose/Sound/UserSound.lua index ceaeeb6e2..f78c42e78 100644 --- a/Moose Development/Moose/Sound/UserSound.lua +++ b/Moose Development/Moose/Sound/UserSound.lua @@ -21,7 +21,7 @@ do -- UserSound - --- @type USERSOUND + -- @type USERSOUND -- @extends Core.Base#BASE diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 873279e4a..59ab36a3c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -181,7 +181,8 @@ COMMANDCENTER = { } ---- @type COMMANDCENTER.AutoAssignMethods +--- +-- @type COMMANDCENTER.AutoAssignMethods COMMANDCENTER.AutoAssignMethods = { ["Random"] = 1, ["Distance"] = 2, @@ -210,7 +211,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, - --- @param #COMMANDCENTER self + -- @param #COMMANDCENTER self -- @param Core.Event#EVENTDATA EventData function( self, EventData ) if EventData.IniObjectCategory == 1 then @@ -241,7 +242,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) -- -- - Assign the PlayerUnit to the Task if required. -- -- - Send a message to the other players in the group that this player has joined. -- self:HandleEvent( EVENTS.PlayerEnterUnit, --- --- @param #COMMANDCENTER self +-- -- @param #COMMANDCENTER self -- -- @param Core.Event#EVENTDATA EventData -- function( self, EventData ) -- local PlayerUnit = EventData.IniUnit @@ -258,7 +259,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) -- The PlayerUnit will be UnAssigned from the Task. -- When there is no Unit left running the Task, the Task goes into Abort... self:HandleEvent( EVENTS.MissionEnd, - --- @param #TASK self + -- @param #TASK self -- @param Core.Event#EVENTDATA EventData function( self, EventData ) local PlayerUnit = EventData.IniUnit @@ -273,7 +274,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) -- The PlayerUnit will be UnAssigned from the Task. -- When there is no Unit left running the Task, the Task goes into Abort... self:HandleEvent( EVENTS.PlayerLeaveUnit, - --- @param #TASK self + -- @param #TASK self -- @param Core.Event#EVENTDATA EventData function( self, EventData ) local PlayerUnit = EventData.IniUnit @@ -290,7 +291,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) -- The PlayerUnit will be UnAssigned from the Task. -- When there is no Unit left running the Task, the Task goes into Abort... self:HandleEvent( EVENTS.Crash, - --- @param #TASK self + -- @param #TASK self -- @param Core.Event#EVENTDATA EventData function( self, EventData ) local PlayerUnit = EventData.IniUnit diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index da560e5d0..81dd1f882 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -48,7 +48,7 @@ do -- DETECTION MANAGER - --- @type DETECTION_MANAGER + -- @type DETECTION_MANAGER -- @field Core.Set#SET_GROUP SetGroup The groups to which the FAC will report to. -- @field Functional.Detection#DETECTION_BASE Detection The DETECTION_BASE object that is used to report the detected objects. -- @field Tasking.CommandCenter#COMMANDCENTER CC The command center that is used to communicate with the players. @@ -62,7 +62,7 @@ do -- DETECTION MANAGER Detection = nil, } - --- @field Tasking.CommandCenter#COMMANDCENTER + -- @field Tasking.CommandCenter#COMMANDCENTER DETECTION_MANAGER.CC = nil --- FAC constructor. diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index 1c65725d2..6fa3da747 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -19,7 +19,8 @@ -- @module Tasking.Mission -- @image Task_Mission.JPG ---- @type MISSION +--- +-- @type MISSION -- @field #MISSION.Clients _Clients -- @field Core.Menu#MENU_COALITION MissionMenu -- @field #string MissionBriefing @@ -790,7 +791,7 @@ function MISSION:HasGroup( TaskGroup ) return Has end ---- @param #MISSION self +-- @param #MISSION self -- @return #number function MISSION:GetTasksRemaining() -- Determine how many tasks are remaining. @@ -805,7 +806,7 @@ function MISSION:GetTasksRemaining() return TasksRemaining end ---- @param #MISSION self +-- @param #MISSION self -- @return #number function MISSION:GetTaskTypes() -- Determine how many tasks are remaining. @@ -865,7 +866,7 @@ end ---- - Aborted Tasks (xp) ---- - Failed Tasks (xp) ---- ----- @param #MISSION self +-- @param #MISSION self ---- @return #string --function MISSION:ReportSummary() -- @@ -1175,7 +1176,7 @@ end ---- @param #MISSION self +-- @param #MISSION self -- @param #string TaskStatus The status -- @param Wrapper.Group#GROUP ReportGroup function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus ) @@ -1186,7 +1187,7 @@ function MISSION:MenuReportTasksPerStatus( ReportGroup, TaskStatus ) end ---- @param #MISSION self +-- @param #MISSION self -- @param Wrapper.Group#GROUP ReportGroup function MISSION:MenuReportPlayersPerTask( ReportGroup ) @@ -1195,7 +1196,7 @@ function MISSION:MenuReportPlayersPerTask( ReportGroup ) self:GetCommandCenter():MessageTypeToGroup( Report, ReportGroup, MESSAGE.Type.Overview ) end ---- @param #MISSION self +-- @param #MISSION self -- @param Wrapper.Group#GROUP ReportGroup function MISSION:MenuReportPlayersProgress( ReportGroup ) diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 21be7dfd8..452f88242 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -220,7 +220,8 @@ -- @module Tasking.Task -- @image MOOSE.JPG ---- @type TASK +--- +-- @type TASK -- @field Core.Scheduler#SCHEDULER TaskScheduler -- @field Tasking.Mission#MISSION Mission -- @field Core.Set#SET_GROUP SetGroup The Set of Groups assigned to the Task @@ -726,7 +727,7 @@ function TASK:AddGroups( GroupSet ) GroupSet = GroupSet or SET_GROUP:New() self.SetGroup:ForEachGroup( - --- @param Wrapper.Group#GROUP GroupSet + -- @param Wrapper.Group#GROUP GroupSet function( GroupItem ) GroupSet:Add( GroupItem:GetName(), GroupItem) end @@ -819,7 +820,7 @@ end do -- Group Assignment - --- @param #TASK self + -- @param #TASK self -- @param Actions.Act_Assign#ACT_ASSIGN AcceptClass function TASK:SetAssignMethod( AcceptClass ) @@ -1199,7 +1200,7 @@ function TASK:RemoveAssignedMenuForGroup( TaskGroup ) end ---- @param #TASK self +-- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup function TASK:MenuAssignToGroup( TaskGroup ) @@ -1208,7 +1209,7 @@ function TASK:MenuAssignToGroup( TaskGroup ) self:AssignToGroup( TaskGroup ) end ---- @param #TASK self +-- @param #TASK self -- @param Wrapper.Group#GROUP TaskGroup function TASK:MenuMarkToGroup( TaskGroup ) self:F() diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua index 4007230dc..428e629c9 100644 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ b/Moose Development/Moose/Tasking/TaskInfo.lua @@ -11,7 +11,8 @@ -- @module Tasking.TaskInfo -- @image MOOSE.JPG ---- @type TASKINFO +--- +-- @type TASKINFO -- @extends Core.Base#BASE --- @@ -29,7 +30,8 @@ TASKINFO = { ClassName = "TASKINFO", } ---- @type TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report. +--- +-- @type TASKINFO.Detail #string A string that flags to document which level of detail needs to be shown in the report. -- -- - "M" for Markings on the Map (F10). -- - "S" for Summary Reports. @@ -279,7 +281,7 @@ function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep ) local CargoReport = REPORT:New() CargoReport:Add( "" ) SetCargo:ForEachCargo( - --- @param Cargo.Cargo#CARGO Cargo + -- @param Cargo.Cargo#CARGO Cargo function( Cargo ) CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) ) end diff --git a/Moose Development/Moose/Tasking/Task_A2A.lua b/Moose Development/Moose/Tasking/Task_A2A.lua index a43c79c9f..9309526bb 100644 --- a/Moose Development/Moose/Tasking/Task_A2A.lua +++ b/Moose Development/Moose/Tasking/Task_A2A.lua @@ -173,19 +173,19 @@ do -- TASK_A2A end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. function TASK_A2A:SetTargetSetUnit( TargetSetUnit ) self.TargetSetUnit = TargetSetUnit end - --- @param #TASK_A2A self + -- @param #TASK_A2A self function TASK_A2A:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param Wrapper.Unit#UNIT TaskUnit @@ -198,7 +198,7 @@ do -- TASK_A2A ActRouteRendezVous:SetRange( RendezVousRange ) end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. @@ -210,7 +210,7 @@ do -- TASK_A2A return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetRendezVousZone( RendezVousZone, TaskUnit ) @@ -221,7 +221,7 @@ do -- TASK_A2A ActRouteRendezVous:SetZone( RendezVousZone ) end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. function TASK_A2A:GetRendezVousZone( TaskUnit ) @@ -232,7 +232,7 @@ do -- TASK_A2A return ActRouteRendezVous:GetZone() end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetTargetCoordinate( TargetCoordinate, TaskUnit ) @@ -243,7 +243,7 @@ do -- TASK_A2A ActRouteTarget:SetCoordinate( TargetCoordinate ) end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. function TASK_A2A:GetTargetCoordinate( TaskUnit ) @@ -254,7 +254,7 @@ do -- TASK_A2A return ActRouteTarget:GetCoordinate() end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_A2A:SetTargetZone( TargetZone, Altitude, Heading, TaskUnit ) @@ -265,7 +265,7 @@ do -- TASK_A2A ActRouteTarget:SetZone( TargetZone, Altitude, Heading ) end - --- @param #TASK_A2A self + -- @param #TASK_A2A self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. function TASK_A2A:GetTargetZone( TaskUnit ) @@ -309,7 +309,7 @@ do -- TASK_A2A self:__Goal( -10 ) end - --- @param #TASK_A2A self + -- @param #TASK_A2A self function TASK_A2A:UpdateTaskInfo( DetectedItem ) if self:IsStatePlanned() or self:IsStateAssigned() then @@ -501,7 +501,7 @@ do -- TASK_A2A_SWEEP return self end - --- @param #TASK_A2A_SWEEP self + -- @param #TASK_A2A_SWEEP self function TASK_A2A_SWEEP:onafterGoal( TaskUnit, From, Event, To ) local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT diff --git a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua index 9b0f8f5b5..d592ad4a6 100644 --- a/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2A_Dispatcher.lua @@ -148,7 +148,7 @@ do -- TASK_A2A_DISPATCHER -- -- TaskDispatcher = TASK_A2A_DISPATCHER:New( ... ) -- - -- --- @param #TaskDispatcher self + -- -- @param #TaskDispatcher self -- -- @param #string From Contains the name of the state from where the Event was triggered. -- -- @param #string Event Contains the name of the event that was triggered. In this case Assign. -- -- @param #string To Contains the name of the state that will be transitioned to. diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index e2839d64e..be894d805 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -409,7 +409,7 @@ do -- TASK_CARGO - --- @type TASK_CARGO + -- @type TASK_CARGO -- @extends Tasking.Task#TASK --- Model tasks for players to transport Cargo. @@ -620,7 +620,7 @@ do -- TASK_CARGO Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - ---- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param #TASK_CARGO Task function Fsm:OnAfterAssigned( TaskUnit, Task ) @@ -643,7 +643,7 @@ do -- TASK_CARGO Task.SetCargo:ForEachCargo( - --- @param Cargo.Cargo#CARGO Cargo + -- @param Cargo.Cargo#CARGO Cargo function( Cargo ) if Cargo:IsAlive() then @@ -837,7 +837,7 @@ do -- TASK_CARGO --#Wrapper.Unit#UNIT - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task -- @param From @@ -857,7 +857,7 @@ do -- TASK_CARGO - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterArriveAtPickup( TaskUnit, Task ) @@ -873,7 +873,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterCancelRouteToPickup( TaskUnit, Task ) @@ -884,7 +884,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit function Fsm:onafterRouteToDeploy( TaskUnit, Task, From, Event, To, DeployZone ) self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) @@ -896,7 +896,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterArriveAtDeploy( TaskUnit, Task ) @@ -911,7 +911,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterCancelRouteToDeploy( TaskUnit, Task ) @@ -923,7 +923,7 @@ do -- TASK_CARGO - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action ) @@ -958,7 +958,7 @@ do -- TASK_CARGO end end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action ) @@ -991,7 +991,7 @@ do -- TASK_CARGO end end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo ) @@ -1003,7 +1003,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterBoard( TaskUnit, Task, From, Event, To, Cargo ) @@ -1031,7 +1031,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterBoarded( TaskUnit, Task, From, Event, To, Cargo ) @@ -1046,7 +1046,7 @@ do -- TASK_CARGO end - --- @param #FSM_PROCESS self + -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_Cargo#TASK_CARGO Task function Fsm:onafterLoad( TaskUnit, Task, From, Event, To, Cargo ) @@ -1195,26 +1195,26 @@ do -- TASK_CARGO return self.SmokeColor end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self function TASK_CARGO:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @return Core.Set#SET_CARGO The Cargo Set. function TASK_CARGO:GetCargoSet() return self.SetCargo end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @return #list The Deployment Zones. function TASK_CARGO:GetDeployZones() return self.DeployZones end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param AI.AI_Cargo#AI_CARGO Cargo The cargo. -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO @@ -1237,7 +1237,7 @@ do -- TASK_CARGO end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param Core.Zone#ZONE DeployZone -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO @@ -1258,7 +1258,7 @@ do -- TASK_CARGO end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param Core.Zone#ZONE DeployZone -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO @@ -1269,7 +1269,7 @@ do -- TASK_CARGO return self end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param Core.Zone#ZONE DeployZone -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO @@ -1280,7 +1280,7 @@ do -- TASK_CARGO return self end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param #list DeployZones -- @param Wrapper.Unit#UNIT TaskUnit -- @return #TASK_CARGO @@ -1295,7 +1295,7 @@ do -- TASK_CARGO - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. function TASK_CARGO:GetTargetZone( TaskUnit ) @@ -1364,7 +1364,7 @@ do -- TASK_CARGO return self.GoalTotal end - --- @param #TASK_CARGO self + -- @param #TASK_CARGO self function TASK_CARGO:UpdateTaskInfo() if self:IsStatePlanned() or self:IsStateAssigned() then diff --git a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua index be2086a2f..c9c476efa 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua @@ -69,7 +69,7 @@ do -- TASK_CAPTURE_DISPATCHER -- @extends Tasking.Task_Manager#TASK_MANAGER -- @field TASK_CAPTURE_DISPATCHER.ZONE ZONE - --- @type TASK_CAPTURE_DISPATCHER.CSAR + -- @type TASK_CAPTURE_DISPATCHER.CSAR -- @field Wrapper.Unit#UNIT PilotUnit -- @field Tasking.Task#TASK Task diff --git a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua index bd0637a9d..33b464a35 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua @@ -119,7 +119,7 @@ do -- TASK_ZONE_GOAL end - --- @param #TASK_ZONE_GOAL self + -- @param #TASK_ZONE_GOAL self -- @param Functional.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal Engine. function TASK_ZONE_GOAL:SetProtect( ZoneGoal ) @@ -128,13 +128,13 @@ do -- TASK_ZONE_GOAL - --- @param #TASK_ZONE_GOAL self + -- @param #TASK_ZONE_GOAL self function TASK_ZONE_GOAL:GetPlannedMenuText() return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.ZoneGoal:GetZoneName() .. " )" end - --- @param #TASK_ZONE_GOAL self + -- @param #TASK_ZONE_GOAL self -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Wrapper.Unit#UNIT TaskUnit function TASK_ZONE_GOAL:SetTargetZone( TargetZone, TaskUnit ) @@ -146,7 +146,7 @@ do -- TASK_ZONE_GOAL end - --- @param #TASK_ZONE_GOAL self + -- @param #TASK_ZONE_GOAL self -- @param Wrapper.Unit#UNIT TaskUnit -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. function TASK_ZONE_GOAL:GetTargetZone( TaskUnit ) @@ -286,7 +286,7 @@ do -- TASK_CAPTURE_ZONE end - --- @param #TASK_CAPTURE_ZONE self + -- @param #TASK_CAPTURE_ZONE self -- @param Wrapper.Unit#UNIT TaskUnit function TASK_CAPTURE_ZONE:OnAfterGoal( From, Event, To, PlayerUnit, PlayerName ) diff --git a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua index f0c14b227..e27a225c0 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_CSAR.lua @@ -72,8 +72,8 @@ do -- TASK_CARGO_CSAR - --- @type TASK_CARGO_CSAR - -- @extends Tasking.Task_CARGO#TASK_CARGO + -- @type TASK_CARGO_CSAR + -- @extends Tasking.Task_Cargo#TASK_CARGO --- Orchestrates the task for players to execute CSAR for downed pilots. -- diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua index d60318573..f78b2d5cd 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua @@ -77,7 +77,7 @@ do -- TASK_CARGO_DISPATCHER -- @field TASK_CARGO_DISPATCHER.CSAR CSAR -- @field Core.Set#SET_ZONE SetZonesCSAR - --- @type TASK_CARGO_DISPATCHER.CSAR + -- @type TASK_CARGO_DISPATCHER.CSAR -- @field Wrapper.Unit#UNIT PilotUnit -- @field Tasking.Task#TASK Task diff --git a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua index 6f8137c6a..6293f03fa 100644 --- a/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua +++ b/Moose Development/Moose/Tasking/Task_Cargo_Transport.lua @@ -285,7 +285,7 @@ do -- TASK_CARGO_TRANSPORT local CargoReport = REPORT:New( "Transport Cargo. The following cargo needs to be transported including initial positions:") SetCargo:ForEachCargo( - --- @param Core.Cargo#CARGO Cargo + -- @param Core.Cargo#CARGO Cargo function( Cargo ) local CargoType = Cargo:GetType() local CargoName = Cargo:GetName() @@ -348,7 +348,7 @@ do -- TASK_CARGO_TRANSPORT return CargoDeployed end - --- @param #TASK_CARGO_TRANSPORT self + -- @param #TASK_CARGO_TRANSPORT self function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To ) local CargoSet = self.CargoSet diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index d3b63882f..a269fb972 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -580,6 +580,18 @@ ENUMS.Link16Power = { --- Enums for the STORAGE class for stores - which need to be in "" -- @type ENUMS.Storage -- @type ENUMS.Storage.weapons +-- @type ENUMS.Storage.weapons.missiles +-- @type ENUMS.Storage.weapons.bombs +-- @type ENUMS.Storage.weapons.nurs +-- @type ENUMS.Storage.weapons.containers +-- @type ENUMS.Storage.weapons.droptanks +-- @type ENUMS.Storage.weapons.adapters +-- @type ENUMS.Storage.weapons.torpedoes +-- @type ENUMS.Storage.weapons.Gazelle +-- @type ENUMS.Storage.weapons.CH47 +-- @type ENUMS.Storage.weapons.OH58 +-- @type ENUMS.Storage.weapons.UH1H +-- @type ENUMS.Storage.weapons.AH64D ENUMS.Storage = { weapons = { missiles = {}, -- Missiles @@ -589,6 +601,11 @@ ENUMS.Storage = { droptanks = {}, -- Droptanks adapters = {}, -- Adapter torpedoes = {}, -- Torpedoes + Gazelle = {}, -- Gazelle specifics + CH47 = {}, -- Chinook specifics + OH58 = {}, -- Kiowa specifics + UH1H = {}, -- Huey specifics + AH64D = {}, -- Huey specifics } } @@ -1148,4 +1165,197 @@ ENUMS.Storage.weapons.bombs.BDU_50LD = "weapons.bombs.BDU_50LD" ENUMS.Storage.weapons.bombs.AGM_62 = "weapons.bombs.AGM_62" ENUMS.Storage.weapons.containers.US_M10_SMOKE_TANK_WHITE = "weapons.containers.{US_M10_SMOKE_TANK_WHITE}" ENUMS.Storage.weapons.missiles.MICA_T = "weapons.missiles.MICA_T" -ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +ENUMS.Storage.weapons.containers.HVAR_rocket = "weapons.containers.HVAR_rocket" +-- 2025 +ENUMS.Storage.weapons.containers.LANTIRN = "weapons.containers.LANTIRN" +ENUMS.Storage.weapons.missiles.AGM_78B = "weapons.missiles.AGM_78B" +ENUMS.Storage.weapons.containers.uh_60l_pilot = "weapons.containers.uh-60l_pilot" +ENUMS.Storage.weapons.missiles.AIM_92E = "weapons.missiles.AIM-92E" +ENUMS.Storage.weapons.missiles.KD_63B = "weapons.missiles.KD_63B" +ENUMS.Storage.weapons.bombs.Type_200A = "weapons.bombs.Type_200A" +ENUMS.Storage.weapons.missiles.HB_AIM_7E_2 = "weapons.missiles.HB-AIM-7E-2" +ENUMS.Storage.weapons.containers.Spear = "weapons.containers.Spear" +ENUMS.Storage.weapons.missiles.LS_6 = "weapons.missiles.LS_6" +ENUMS.Storage.weapons.containers.HB_ALE_40_0_120 = "weapons.containers.HB_ALE_40_0_120" +ENUMS.Storage.weapons.containers.Fantasm = "weapons.containers.Fantasm" +ENUMS.Storage.weapons.nurs.FFAR_Mk61 = "weapons.nurs.FFAR_Mk61" +ENUMS.Storage.weapons.bombs.HB_F4E_GBU15V1 = "weapons.bombs.HB_F4E_GBU15V1" +ENUMS.Storage.weapons.containers.HB_F14_EXT_AN_APQ_167 = "weapons.containers.HB_F14_EXT_AN_APQ-167" +ENUMS.Storage.weapons.nurs.LWL_RP = "weapons.nurs.LWL_RP" +ENUMS.Storage.weapons.bombs.AGM_62_I = "weapons.bombs.AGM_62_I" +ENUMS.Storage.weapons.containers.ETHER = "weapons.containers.ETHER" +ENUMS.Storage.weapons.containers.TANGAZH = "weapons.containers.TANGAZH" +ENUMS.Storage.weapons.bombs.LYSBOMB_11086 = "weapons.bombs.LYSBOMB 11086" +ENUMS.Storage.weapons.containers.Stub_Wing = "weapons.containers.Stub_Wing" +ENUMS.Storage.weapons.missiles.AIM_9E = "weapons.missiles.AIM-9E" +ENUMS.Storage.weapons.missiles.C_701T = "weapons.missiles.C_701T" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.missiles.CM_400AKG = "weapons.missiles.CM-400AKG" +ENUMS.Storage.weapons.missiles.C_802AK = "weapons.missiles.C_802AK" +ENUMS.Storage.weapons.missiles.KD_63 = "weapons.missiles.KD_63" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike_Fast = "weapons.containers.HB_ORD_Pave_Spike_Fast" +ENUMS.Storage.weapons.missiles.SPIKE_ER2 = "weapons.missiles.SPIKE_ER2" +ENUMS.Storage.weapons.containers.KINGAL = "weapons.containers.KINGAL" +ENUMS.Storage.weapons.containers.LANTIRN_F14_TARGET = "weapons.containers.LANTIRN-F14-TARGET" +ENUMS.Storage.weapons.containers.SPS_141 = "weapons.containers.SPS-141" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_0 = "weapons.containers.HB_ALE_40_30_0" +ENUMS.Storage.weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL = "weapons.droptanks.HB_HIGH_PERFORMANCE_CENTERLINE_600_GAL" +ENUMS.Storage.weapons.containers.ALQ_184 = "weapons.containers.ALQ-184" +ENUMS.Storage.weapons.missiles.AGM_45B = "weapons.missiles.AGM_45B" +ENUMS.Storage.weapons.bombs.BLU_3_GROUP = "weapons.bombs.BLU-3_GROUP" +ENUMS.Storage.weapons.missiles.SPIKE_ER = "weapons.missiles.SPIKE_ER" +ENUMS.Storage.weapons.nurs.ARAKM70BAPPX = "weapons.nurs.ARAKM70BAPPX" +ENUMS.Storage.weapons.bombs.LYSBOMB_11088 = "weapons.bombs.LYSBOMB 11088" +ENUMS.Storage.weapons.bombs.LYSBOMB_11087 = "weapons.bombs.LYSBOMB 11087" +ENUMS.Storage.weapons.missiles.KD_20 = "weapons.missiles.KD_20" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank = "weapons.droptanks.HB_F-4E_EXT_WingTank" +ENUMS.Storage.weapons.missiles.Rb_04 = "weapons.missiles.Rb_04" +ENUMS.Storage.weapons.containers.AAQ_33 = "weapons.containers.AAQ-33" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_R_EMPTY" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_EMPTY = "weapons.droptanks.HB_F-4E_EXT_WingTank_EMPTY" +ENUMS.Storage.weapons.containers.uh_60l_copilot = "weapons.containers.uh-60l_copilot" +ENUMS.Storage.weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2 = "weapons.droptanks.JAYHAWK_80gal_Fuel_Tankv2" +ENUMS.Storage.weapons.containers.supply_m134 = "weapons.containers.supply_m134" +ENUMS.Storage.weapons.containers.Seahawk_Pylon = "weapons.containers.Seahawk_Pylon" +ENUMS.Storage.weapons.nurs.LWL_MPP = "weapons.nurs.LWL_MPP" +ENUMS.Storage.weapons.nurs.S_5KP = "weapons.nurs.S_5KP" +ENUMS.Storage.weapons.missiles.AIM_92J = "weapons.missiles.AIM-92J" +ENUMS.Storage.weapons.missiles.HB_AIM_7E = "weapons.missiles.HB-AIM-7E" +ENUMS.Storage.weapons.containers.ALQ_131 = "weapons.containers.ALQ-131" +ENUMS.Storage.weapons.containers.HB_F14_EXT_TARPS = "weapons.containers.HB_F14_EXT_TARPS" +ENUMS.Storage.weapons.containers.MH60_SOAR = "weapons.containers.MH60_SOAR" +ENUMS.Storage.weapons.missiles.YJ_83 = "weapons.missiles.YJ-83" +ENUMS.Storage.weapons.bombs.GBU_8_B = "weapons.bombs.GBU_8_B" +ENUMS.Storage.weapons.containers.HB_F14_EXT_ECA = "weapons.containers.HB_F14_EXT_ECA" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.nurs.M261_MPSM_Rocket = "weapons.nurs.M261_MPSM_Rocket" +ENUMS.Storage.weapons.droptanks.SEAHAWK_120_Fuel_Tank = "weapons.droptanks.SEAHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.containers.SHPIL = "weapons.containers.SHPIL" +ENUMS.Storage.weapons.bombs.GBU_39 = "weapons.bombs.GBU_39" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.containers.HB_ALE_40_15_90 = "weapons.containers.HB_ALE_40_15_90" +ENUMS.Storage.weapons.missiles.AIM_7E = "weapons.missiles.AIM-7E" +ENUMS.Storage.weapons.missiles.AIM_9P3 = "weapons.missiles.AIM-9P3" +ENUMS.Storage.weapons.missiles.AGM_12B = "weapons.missiles.AGM_12B" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Dual_Tank" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_Center_Fuel_Tank = "weapons.droptanks.HB_F-4E_EXT_Center_Fuel_Tank" +ENUMS.Storage.weapons.containers.PAVETACK = "weapons.containers.PAVETACK" +ENUMS.Storage.weapons.missiles.LS_6_500 = "weapons.missiles.LS_6_500" +ENUMS.Storage.weapons.bombs.LYSBOMB_11089 = "weapons.bombs.LYSBOMB 11089" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.containers.ah_64d_radar = "weapons.containers.ah-64d_radar" +ENUMS.Storage.weapons.containers.F_18_LDT_POD = "weapons.containers.F-18-LDT-POD" +ENUMS.Storage.weapons.containers.HB_ALE_40_30_60 = "weapons.containers.HB_ALE_40_30_60" +ENUMS.Storage.weapons.bombs.LS_6_100 = "weapons.bombs.LS_6_100" +ENUMS.Storage.weapons.droptanks.HB_F_4E_EXT_WingTank_R = "weapons.droptanks.HB_F-4E_EXT_WingTank_R" +ENUMS.Storage.weapons.containers.SORBCIJA_R = "weapons.containers.SORBCIJA_R" +ENUMS.Storage.weapons.missiles.CATM_65K = "weapons.missiles.CATM_65K" +ENUMS.Storage.weapons.containers.HB_ORD_Pave_Spike = "weapons.containers.HB_ORD_Pave_Spike" +ENUMS.Storage.weapons.containers.RobbieTank1 = "weapons.containers.RobbieTank1" +ENUMS.Storage.weapons.containers.SKY_SHADOW = "weapons.containers.SKY_SHADOW" +ENUMS.Storage.weapons.containers.SORBCIJA_L = "weapons.containers.SORBCIJA_L" +ENUMS.Storage.weapons.containers.Pavehawk = "weapons.containers.Pavehawk" +ENUMS.Storage.weapons.bombs.BLG66_EG = "weapons.bombs.BLG66_EG" +ENUMS.Storage.weapons.missiles.AGM_12C_ED = "weapons.missiles.AGM_12C_ED" +ENUMS.Storage.weapons.missiles.AIM_92C = "weapons.missiles.AIM-92C" +ENUMS.Storage.weapons.containers.MPS_410 = "weapons.containers.MPS-410" +ENUMS.Storage.weapons.missiles.HJ_12 = "weapons.missiles.HJ-12" +ENUMS.Storage.weapons.containers.AAQ_28_LITENING = "weapons.containers.AAQ-28_LITENING" +ENUMS.Storage.weapons.containers.F_18_FLIR_POD = "weapons.containers.F-18-FLIR-POD" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.containers.UH60L_Jayhawk = "weapons.containers.UH60L_Jayhawk" +ENUMS.Storage.weapons.containers.BOZ_100 = "weapons.containers.BOZ-100" +ENUMS.Storage.weapons.missiles.AGM_78A = "weapons.missiles.AGM_78A" +ENUMS.Storage.weapons.missiles.LAU_61_APKWS_M282 = "weapons.missiles.LAU_61_APKWS_M282" +ENUMS.Storage.weapons.bombs.BAP_100 = "weapons.bombs.BAP-100" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM-802AKG" +ENUMS.Storage.weapons.bombs.BLU_3B_GROUP = "weapons.bombs.BLU_3B_GROUP" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU-4B_GROUP" +ENUMS.Storage.weapons.nurs.S_5M = "weapons.nurs.S_5M" +ENUMS.Storage.weapons.missiles.AGM_12A = "weapons.missiles.AGM_12A" +ENUMS.Storage.weapons.droptanks.JAYHAWK_120_Fuel_Tank = "weapons.droptanks.JAYHAWK_120_Fuel_Tank" +ENUMS.Storage.weapons.bombs.GBU_15_V_1_B = "weapons.bombs.GBU_15_V_1_B" +ENUMS.Storage.weapons.missiles.HYDRA_70_M151_APKWS = {4,4,8,292} +ENUMS.Storage.weapons.missiles.HYDRA_70_M282_APKWS = {4,4,8,293} +-- dupes with typos +ENUMS.Storage.weapons.bombs.BAP100 = "weapons.bombs.BAP_100" +ENUMS.Storage.weapons.bombs.BLU3B_GROUP = "weapons.bombs.BLU-3B_GROUP" +ENUMS.Storage.weapons.missiles.CM_802AKG = "weapons.missiles.CM_802AKG" +ENUMS.Storage.weapons.bombs.BLU_4B_GROUP = "weapons.bombs.BLU_4B_GROUP" +ENUMS.Storage.weapons.nurs.S5M = "weapons.nurs.S-5M" +-- Gazelle +ENUMS.Storage.weapons.Gazelle.HMP400_100RDS = {4,15,46,1771} +ENUMS.Storage.weapons.Gazelle.HMP400_200RDS = {4,15,46,1770} +ENUMS.Storage.weapons.Gazelle.HMP400_400RDS = {4,15,46,1769} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_AP = {4,15,46,1768} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_SAPHEI = {4,15,46,1767} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HE = {4,15,46,1766} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_HEAP = {4,15,46,1765} +ENUMS.Storage.weapons.Gazelle.GIAT_M261_APHE = {4,15,46,1764} +ENUMS.Storage.weapons.Gazelle.GAZELLE_IR_DEFLECTOR = {4,15,47,680} +ENUMS.Storage.weapons.Gazelle.GAZELLE_FAS_SANDFILTER = {4,15,47,679} +-- Chinook (changed) +ENUMS.Storage.weapons.CH47.CH47_PORT_M60D = {4,15,46,2489} +ENUMS.Storage.weapons.CH47.CH47_STBD_M60D = {4,15,46,2488} +ENUMS.Storage.weapons.CH47.CH47_AFT_M60D = {4,15,46,2490} +ENUMS.Storage.weapons.CH47.CH47_PORT_M134D = {4,15,46,2494} +ENUMS.Storage.weapons.CH47.CH47_STBD_M134D = {4,15,46,2495} +ENUMS.Storage.weapons.CH47.CH47_AFT_M3M = {4,15,46,2496} -- +ENUMS.Storage.weapons.CH47.CH47_PORT_M240H = {4,15,46,2492} +ENUMS.Storage.weapons.CH47.CH47_STBD_M240H = {4,15,46,2491} +ENUMS.Storage.weapons.CH47.CH47_AFT_M240H = {4,15,46,2493} +-- Huey +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right = {4,15,46,161} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left = {4,15,46,160} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Right_Door = {4,15,46,175} +ENUMS.Storage.weapons.UH1H.M60_MG_Right_Door = {4,15,46,177} +ENUMS.Storage.weapons.UH1H.M134_MiniGun_Left_Door = {4,15,46,174} +ENUMS.Storage.weapons.UH1H.M60_MG_Left_Door = {4,15,46,176} +-- Kiowa +ENUMS.Storage.weapons.OH58.FIM92 = {4,4,7,449} +ENUMS.Storage.weapons.OH58.MG_M3P100 = {4,15,46,2611} +ENUMS.Storage.weapons.OH58.MG_M3P200 = {4,15,46,2610} +ENUMS.Storage.weapons.OH58.MG_M3P300 = {4,15,46,2609} +ENUMS.Storage.weapons.OH58.MG_M3P400 = {4,15,46,2608} +ENUMS.Storage.weapons.OH58.MG_M3P500 = {4,15,46,2607} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Blue = {4,5,9,488} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Green = {4,5,9,489} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Red = {4,5,9,487} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Violet = {4,5,9,490} +ENUMS.Storage.weapons.OH58.Smk_Grenade_White = {4,5,9,492} +ENUMS.Storage.weapons.OH58.Smk_Grenade_Yellow = {4,5,9,491} +-- Apache +ENUMS.Storage.weapons.AH64D.AN_APG78 = {4,15,44,2114} +ENUMS.Storage.weapons.AH64D.Internal_Aux_FuelTank = {1,3,43,1700} + +--- +-- @type ENUMS.FARPType +-- @field #string FARP +-- @field #string INVISIBLE +-- @field #string HELIPADSINGLE +-- @field #string PADSINGLE +ENUMS.FARPType = { + FARP = "FARP", + INVISIBLE = "INVISIBLE", + HELIPADSINGLE = "HELIPADSINGLE", + PADSINGLE = "PADSINGLE", +} + + +--- +-- @type ENUMS.FARPObjectTypeNamesAndShape +-- @field #string FARP +-- @field #string INVISIBLE +-- @field #string HELIPADSINGLE +-- @field #string PADSINGLE +ENUMS.FARPObjectTypeNamesAndShape ={ + [ENUMS.FARPType.FARP] = { TypeName="FARP", ShapeName="FARPS"}, + [ENUMS.FARPType.INVISIBLE] = { TypeName="Invisible FARP", ShapeName="invisiblefarp"}, + [ENUMS.FARPType.HELIPADSINGLE] = { TypeName="SINGLE_HELIPAD", ShapeName="FARP"}, + [ENUMS.FARPType.PADSINGLE] = { TypeName="FARP_SINGLE_01", ShapeName="FARP_SINGLE_01"}, +} + diff --git a/Moose Development/Moose/Utilities/STTS.lua b/Moose Development/Moose/Utilities/STTS.lua deleted file mode 100644 index 1a27696f1..000000000 --- a/Moose Development/Moose/Utilities/STTS.lua +++ /dev/null @@ -1,259 +0,0 @@ ---- **Utilities** - DCS Simple Text-To-Speech (STTS). --- --- --- @module Utilities.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 sanitization. --- 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 initialize 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 specific 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" - ---- DON'T 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, average of 5 letters for english word so --- --- * 5 chars * 100wpm = 500 characters per min = 8.3 chars per second --- --- So length 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 #number length can also be passed as #string --- @param #number speed Defaults to 1.0 --- @param #boolean isGoogle We're using Google TTS -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 length/cps --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 diff --git a/Moose Development/Moose/Utilities/Templates.lua b/Moose Development/Moose/Utilities/Templates.lua deleted file mode 100644 index 52a648a9a..000000000 --- a/Moose Development/Moose/Utilities/Templates.lua +++ /dev/null @@ -1,612 +0,0 @@ ---- **Utilities** - 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 = {}, -} - ---- Ground unit type names. --- @type TEMPLATE.TypeGround --- @param #string InfantryAK -TEMPLATE.TypeGround={ - InfantryAK="Infantry AK", - ParatrooperAKS74="Paratrooper AKS-74", - ParatrooperRPG16="Paratrooper RPG-16", - SoldierWWIIUS="soldier_wwii_us", - InfantryM248="Infantry M249", - 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", -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- 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 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) - - -- 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.GROUND_UNIT - - -- 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. --- @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 - ---- 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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -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, -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Ship Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -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, -} - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Generic Aircraft Template -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - -TEMPLATE.GenericAircraft= -{ - ["groupId"] = nil, - ["name"] = "Rotary-1", - ["uncontrolled"] = false, - ["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] = - { - ["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"] - }, -- end of ["params"] - }, -- end of ["task"] - ["ETA"] = 0, - ["ETA_locked"] = true, - ["speed"] = 100, - ["speed_locked"] = true, - ["formation_template"] = "", - }, -- end of [1] - }, -- end of ["points"] - }, -- end of ["route"] - ["units"] = - { - [1] = - { - ["name"] = "Rotary-1-1", - ["unitId"] = nil, - ["type"] = "AH-1W", - ["onboard_num"] = "050", - ["livery_id"] = "USA X Black", - ["skill"] = "High", - ["ropeLength"] = 15, - ["speed"] = 0, - ["x"] = 0, - ["y"] = 0, - ["alt"] = 10, - ["alt_type"] = "BARO", - ["heading"] = 0, - ["psi"] = 0, - ["parking"] = nil, - ["parking_id"] = nil, - ["payload"] = - { - ["pylons"] = {}, -- end of ["pylons"] - ["fuel"] = "1250.0", - ["flare"] = 30, - ["chaff"] = 30, - ["gun"] = 100, - }, -- end of ["payload"] - ["callsign"] = - { - [1] = 2, - [2] = 1, - [3] = 1, - ["name"] = "Springfield11", - }, -- end of ["callsign"] - }, -- end of [1] - }, -- end of ["units"] -} -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3bd3bf716..d824bbd4f 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -56,6 +56,8 @@ BIGSMOKEPRESET = { -- @field #string Falklands South Atlantic map. -- @field #string Sinai Sinai map. -- @field #string Kola Kola map. +-- @field #string Afghanistan Afghanistan map +-- @field #string Iraq Iraq map DCSMAP = { Caucasus="Caucasus", NTTR="Nevada", @@ -66,7 +68,9 @@ DCSMAP = { MarianaIslands="MarianaIslands", Falklands="Falklands", Sinai="SinaiMap", - Kola="Kola" + Kola="Kola", + Afghanistan="Afghanistan", + Iraq="Iraq" } @@ -482,6 +486,15 @@ UTILS.BasicSerialize = function(s) end end +--- Counts the number of elements in a table. +-- @param #table T Table to count +-- @return #int Number of elements in the table +function UTILS.TableLength(T) + local count = 0 + for _ in pairs(T or {}) do count = count + 1 end + return count +end + --- Print a table to log in a nice format -- @param #table table The table to print -- @param #number indent Number of indents @@ -496,12 +509,12 @@ function UTILS.PrintTableToLog(table, indent, noprint) if not indent then indent = 0 end for k, v in pairs(table) do if string.find(k," ") then k='"'..k..'"'end - if type(v) == "table" then + if type(v) == "table" and UTILS.TableLength(v) > 0 then if not noprint then env.info(string.rep(" ", indent) .. tostring(k) .. " = {") end text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n" - text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n" + text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1), noprint).."\n" if not noprint then env.info(string.rep(" ", indent) .. "},") end @@ -1238,7 +1251,7 @@ function UTILS.SecondsToClock(seconds, short) end -- Seconds - local seconds = tonumber(seconds) + local seconds = tonumber(seconds) or 0 -- Seconds of this day. local _seconds=seconds%(60*60*24) @@ -1819,7 +1832,8 @@ end -- * Mariana Islands +2 (East) -- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole -- * Sinai +4.8 (East) --- * Kola +15 (East) - not there is a lot of deviation across the map (-1° to +24°), as we are close to the North pole +-- * Kola +15 (East) - note there is a lot of deviation across the map (-1° to +24°), as we are close to the North pole +-- * Afghanistan +3 (East) - actually +3.6 (NW) to +2.3 (SE) -- @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) @@ -1848,6 +1862,10 @@ function UTILS.GetMagneticDeclination(map) declination=4.8 elseif map==DCSMAP.Kola then declination=15 + elseif map==DCSMAP.Afghanistan then + declination=3 + elseif map==DCSMAP.Iraq then + declination=4.4 else declination=0 end @@ -2079,6 +2097,8 @@ function UTILS.GMTToLocalTimeDifference() return 2 -- Currently map is +2 but should be +3 (DCS bug?) elseif theatre==DCSMAP.Kola then return 3 -- Currently map is +2 but should be +3 (DCS bug?) + elseif theatre==DCSMAP.Afghanistan then + return 4.5 -- UTC +4:30 else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 @@ -2182,9 +2202,9 @@ function UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, Rising, Tlocal) local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) if rising and cosH > 1 then - return "N/R" -- The sun never rises on this location on the specified date + return "N/S" -- The sun never rises on this location on the specified date elseif cosH < -1 then - return "N/S" -- The sun never sets on this location on the specified date + return "N/R" -- The sun never sets on this location on the specified date end -- Finish calculating H and convert into hours @@ -2375,12 +2395,21 @@ function UTILS.IsLoadingDoorOpen( unit_name ) return true end - if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then - BASE:T(unit_name .. " cargo door is open") - return true + if type_name == "OH58D" then + BASE:T(unit_name .. " front door(s) are open") + return true -- no doors on this one ;) end - return false + if type_name == "CH-47Fbl1" and (unit:getDrawArgumentValue(86) > 0.5) then + BASE:T(unit_name .. " rear cargo door is open") + return true + end + + -- ground + local UnitDescriptor = unit:getDesc() + local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) + + return IsGroundResult end -- nil @@ -2405,17 +2434,19 @@ 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,305,307,309.5,311,312,312.5,316, - 320,324,328,329,330,332,336,337, - 342,343,348,351,352,353,358, - 363,365,368,372.5,374, - 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, + 214,243,264,273,274,288,291.5,295,297.5, + 300.5,304,305,307,309.5,310,311,312,312.5,316,317, + 320,323,324,325,326,328,329,330,332,335,336,337, + 340,342,343,346,348,351,352,353,358, + 360,363,364,365,368,372.5,373,374, + 380,381,384,385,387,389,391,395,396,399, + 403,404,410,412,414,418,420,423, + 430,432,435,440,445, + 450,455,462,470,485,490, + 507,515,520,525,528,540,550,560,563,570,577,580,595, 602,625,641,662,670,680,682,690, 705,720,722,730,735,740,745,750,770,795, 822,830,862,866, @@ -2576,7 +2607,7 @@ end --- Function to save an object to a file -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. Existing file will be overwritten. --- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line. +-- @param #string Data The data structure to save. This will be e.g. a string of text lines with an \\n at the end of each line. -- @return #boolean outcome True if saving is possible, else false. function UTILS.SaveToFile(Path,Filename,Data) -- Thanks to @FunkyFranky @@ -4180,3 +4211,326 @@ function UTILS.ReadCSV(filename) return csvdata end + +--- Seed the LCG random number generator. +-- @param #number seed Seed value. Default is a random number using math.random() +function UTILS.LCGRandomSeed(seed) + UTILS.lcg = { + seed = seed or math.random(1, 2^32 - 1), + a = 1664525, + c = 1013904223, + m = 2^32 + } +end + +--- Return a pseudo-random number using the LCG algorithm. +-- @return #number Random number between 0 and 1. +function UTILS.LCGRandom() + if UTILS.lcg == nil then + UTILS.LCGRandomSeed() + end + UTILS.lcg.seed = (UTILS.lcg.a * UTILS.lcg.seed + UTILS.lcg.c) % UTILS.lcg.m + return UTILS.lcg.seed / UTILS.lcg.m +end + +--- Spawns a new FARP of a defined type and coalition and functional statics (fuel depot, ammo storage, tent, windsock) around that FARP to make it operational. +-- Adds vehicles from template if given. Fills the FARP warehouse with liquids and known materiels. +-- References: [DCS Forum Topic](https://forum.dcs.world/topic/282989-farp-equipment-to-run-it) +-- @param #string Name Name of this FARP installation. Must be unique. +-- @param Core.Point#COORDINATE Coordinate Where to spawn the FARP. +-- @param #string FARPType Type of FARP, can be one of the known types ENUMS.FARPType.FARP, ENUMS.FARPType.INVISIBLE, ENUMS.FARPType.HELIPADSINGLE, ENUMS.FARPType.PADSINGLE. Defaults to ENUMS.FARPType.FARP. +-- @param #number Coalition Coalition of this FARP, i.e. coalition.side.BLUE or coalition.side.RED, defaults to coalition.side.BLUE. +-- @param #number Country Country of this FARP, defaults to country.id.USA (blue) or country.id.RUSSIA (red). +-- @param #number CallSign Callsign of the FARP ATC, defaults to CALLSIGN.FARP.Berlin. +-- @param #number Frequency Frequency of the FARP ATC Radio, defaults to 127.5 (MHz). +-- @param #number Modulation Modulation of the FARP ATC Radio, defaults to radio.modulation.AM. +-- @param #number ADF ADF Beacon (FM) Frequency in KHz, e.g. 428. If not nil, creates an VHF/FM ADF Beacon for this FARP. Requires a sound called "beacon.ogg" to be in the mission (trigger "sound to" ...) +-- @param #number SpawnRadius Radius of the FARP, i.e. where the FARP objects will be placed in meters, not more than 150m away. Defaults to 100. +-- @param #string VehicleTemplate, template name for additional vehicles. Can be nil for no additional vehicles. +-- @param #number Liquids Tons of fuel to be added initially to the FARP. Defaults to 10 (tons). Set to 0 for no fill. +-- @param #number Equipment Number of equipment items per known item to be added initially to the FARP. Defaults to 10 (items). Set to 0 for no fill. +-- @return #list Table of spawned objects and vehicle object (if given). +-- @return #string ADFBeaconName Name of the ADF beacon, to be able to remove/stop it later. +function UTILS.SpawnFARPAndFunctionalStatics(Name,Coordinate,FARPType,Coalition,Country,CallSign,Frequency,Modulation,ADF,SpawnRadius,VehicleTemplate,Liquids,Equipment) + + -- Set Defaults + local farplocation = Coordinate + local farptype = FARPType or ENUMS.FARPType.FARP + local Coalition = Coalition or coalition.side.BLUE + local callsign = CallSign or CALLSIGN.FARP.Berlin + local freq = Frequency or 127.5 + local mod = Modulation or radio.modulation.AM + local radius = SpawnRadius or 100 + if radius < 0 or radius > 150 then radius = 100 end + local liquids = Liquids or 10 + liquids = liquids * 1000 -- tons to kg + local equip = Equipment or 10 + local statictypes = ENUMS.FARPObjectTypeNamesAndShape[farptype] or {TypeName="FARP", ShapeName="FARPS"} + local STypeName = statictypes.TypeName + local SShapeName = statictypes.ShapeName + local Country = Country or (Coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA) + local ReturnObjects = {} + + -- Spawn FARP + local newfarp = SPAWNSTATIC:NewFromType(STypeName,"Heliports",Country) -- "Invisible FARP" "FARP" + newfarp:InitShape(SShapeName) -- "invisiblefarp" "FARPS" + newfarp:InitFARP(callsign,freq,mod) + local spawnedfarp = newfarp:SpawnFromCoordinate(farplocation,0,Name) + table.insert(ReturnObjects,spawnedfarp) + -- Spawn Objects + local FARPStaticObjectsNato = { + ["FUEL"] = { TypeName = "FARP Fuel Depot", ShapeName = "GSM Rus", Category = "Fortifications"}, + ["AMMO"] = { TypeName = "FARP Ammo Dump Coating", ShapeName = "SetkaKP", Category = "Fortifications"}, + ["TENT"] = { TypeName = "FARP Tent", ShapeName = "PalatkaB", Category = "Fortifications"}, + ["WINDSOCK"] = { TypeName = "Windsock", ShapeName = "H-Windsock_RW", Category = "Fortifications"}, + } + + local farpobcount = 0 + for _name,_object in pairs(FARPStaticObjectsNato) do + local objloc = farplocation:Translate(radius,farpobcount*30) + local heading = objloc:HeadingTo(farplocation) + local newobject = SPAWNSTATIC:NewFromType(_object.TypeName,_object.Category,Country) + newobject:InitShape(_object.ShapeName) + newobject:InitHeading(heading) + newobject:SpawnFromCoordinate(objloc,farpobcount*30,_name.." - "..Name) + table.insert(ReturnObjects,newobject) + farpobcount = farpobcount + 1 + end + + -- Vehicle if any + if VehicleTemplate and type(VehicleTemplate) == "string" then + local vcoordinate = farplocation:Translate(radius,farpobcount*30) + local heading = vcoordinate:HeadingTo(farplocation) + local vehicles = SPAWN:NewWithAlias(VehicleTemplate,"FARP Vehicles - "..Name) + vehicles:InitGroupHeading(heading) + vehicles:InitCountry(Country) + vehicles:InitCoalition(Coalition) + vehicles:InitDelayOff() + local spawnedvehicle = vehicles:SpawnFromCoordinate(vcoordinate) + table.insert(ReturnObjects,spawnedvehicle) + end + + local newWH = STORAGE:New(Name) + if liquids and liquids > 0 then + -- Storage fill-up + newWH:SetLiquid(STORAGE.Liquid.DIESEL,liquids) -- kgs to tons + newWH:SetLiquid(STORAGE.Liquid.GASOLINE,liquids) + newWH:SetLiquid(STORAGE.Liquid.JETFUEL,liquids) + newWH:SetLiquid(STORAGE.Liquid.MW50,liquids) + end + + if equip and equip > 0 then + for cat,nitem in pairs(ENUMS.Storage.weapons) do + for name,item in pairs(nitem) do + newWH:SetItem(item,equip) + end + end + end + + local ADFName + if ADF and type(ADF) == "number" then + local ADFFreq = ADF*1000 -- KHz to Hz + local Sound = "l10n/DEFAULT/beacon.ogg" + local vec3 = farplocation:GetVec3() + ADFName = Name .. " ADF "..tostring(ADF).."KHz" + --BASE:I(string.format("Adding FARP Beacon %d KHz Name %s",ADF,ADFName)) + trigger.action.radioTransmission(Sound, vec3, 0, true, ADFFreq, 250, ADFName) + end + + return ReturnObjects, ADFName +end + +--- Converts a Vec2 to a Vec3. +-- @param vec the 2D vector +-- @param y optional new y axis (altitude) value. If omitted it's 0. +function UTILS.Vec2toVec3(vec,y) + if not vec.z then + if vec.alt and not y then + y = vec.alt + elseif not y then + y = 0 + end + return {x = vec.x, y = y, z = vec.y} + else + return {x = vec.x, y = vec.y, z = vec.z} -- it was already Vec3, actually. + end +end + +--- Get the correction needed for true north in radians +-- @param gPoint The map point vec2 or vec3 +-- @return number correction +function UTILS.GetNorthCorrection(gPoint) + local point = UTILS.DeepCopy(gPoint) + if not point.z then --Vec2; convert to Vec3 + point.z = point.y + point.y = 0 + end + local lat, lon = coord.LOtoLL(point) + local north_posit = coord.LLtoLO(lat + 1, lon) + return math.atan2(north_posit.z - point.z, north_posit.x - point.x) +end + +--- Convert time in seconds to a DHMS table `{d = days, h = hours, m = minutes, s = seconds}` +-- @param timeInSec Time in Seconds +-- @return #table Table with DHMS data +function UTILS.GetDHMS(timeInSec) + if timeInSec and type(timeInSec) == 'number' then + local tbl = {d = 0, h = 0, m = 0, s = 0} + if timeInSec > 86400 then + while timeInSec > 86400 do + tbl.d = tbl.d + 1 + timeInSec = timeInSec - 86400 + end + end + if timeInSec > 3600 then + while timeInSec > 3600 do + tbl.h = tbl.h + 1 + timeInSec = timeInSec - 3600 + end + end + if timeInSec > 60 then + while timeInSec > 60 do + tbl.m = tbl.m + 1 + timeInSec = timeInSec - 60 + end + end + tbl.s = timeInSec + return tbl + else + BASE:E("No number handed!") + return + end +end + +--- Returns heading-error corrected direction in radians. +-- True-north corrected direction from point along vector vec. +-- @param vec Vec3 Starting point +-- @param point Vec2 Direction +-- @return direction corrected direction from point. +function UTILS.GetDirectionRadians(vec, point) + local dir = math.atan2(vec.z, vec.x) + if point then + dir = dir + UTILS.GetNorthCorrection(point) + end + if dir < 0 then + dir = dir + 2 * math.pi -- put dir in range of 0 to 2*pi + end + return dir +end + +--- Raycasting a point in polygon. Code from http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm +-- @param point Vec2 or Vec3 to test +-- @param #table poly Polygon Table of Vec2/3 point forming the Polygon +-- @param #number maxalt Altitude limit (optional) +-- @param #boolean outcome +function UTILS.IsPointInPolygon(point, poly, maxalt) + point = UTILS.Vec2toVec3(point) + local px = point.x + local pz = point.z + local cn = 0 + local newpoly = UTILS.DeepCopy(poly) + + if not maxalt or (point.y <= maxalt) then + local polysize = #newpoly + newpoly[#newpoly + 1] = newpoly[1] + + newpoly[1] = UTILS.Vec2toVec3(newpoly[1]) + + for k = 1, polysize do + newpoly[k+1] = UTILS.Vec2toVec3(newpoly[k+1]) + if ((newpoly[k].z <= pz) and (newpoly[k+1].z > pz)) or ((newpoly[k].z > pz) and (newpoly[k+1].z <= pz)) then + local vt = (pz - newpoly[k].z) / (newpoly[k+1].z - newpoly[k].z) + if (px < newpoly[k].x + vt*(newpoly[k+1].x - newpoly[k].x)) then + cn = cn + 1 + end + end + end + + return cn%2 == 1 + else + return false + end +end + +--- Vector scalar multiplication. +-- @param vec Vec3 vector to multiply +-- @param #number mult scalar multiplicator +-- @return Vec3 new vector multiplied with the given scalar +function UTILS.ScalarMult(vec, mult) + return {x = vec.x*mult, y = vec.y*mult, z = vec.z*mult} +end + +--- Utilities weather class for fog mainly. +-- @type UTILS.Weather +UTILS.Weather = {} + +--- Returns the current fog thickness in meters. Returns zero if fog is not present. +function UTILS.Weather.GetFogThickness() + return world.weather.getFogThickness() +end + +--- Sets the fog to the desired thickness in meters at sea level. +-- @param #number Thickness Thickness in meters. +-- Any fog animation will be discarded. +-- Valid range : 100 to 5000 meters +function UTILS.Weather.SetFogThickness(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 5000 then value = 5000 end + return world.weather.setFogThickness(value) +end + +--- Removes the fog. +function UTILS.Weather.RemoveFog() + return world.weather.setFogThickness(0) +end + +--- Gets the maximum visibility distance of the current fog setting. +-- Returns 0 if no fog is present. +function UTILS.Weather.GetFogVisibilityDistanceMax() + return world.weather.getFogVisibilityDistance() +end + +--- Sets the maximum visibility at sea level in meters. +-- @param #number Thickness Thickness in meters. +-- Limit: 100 to 100000 +function UTILS.Weather.SetFogVisibilityDistance(Thickness) + local value = Thickness + if value < 100 then value = 100 + elseif value > 100000 then value = 100000 end + return world.weather.setFogVisibilityDistance(value) +end + +--- Uses data from the passed table to change the fog visibility and thickness over a desired timeframe. This allows for a gradual increase/decrease of fog values rather than abruptly applying the values. +-- Animation Key Format: {time, visibility, thickness} +-- @param #table AnimationKeys Table of AnimationKey tables +-- @usage +-- Time: in seconds 0 to infinity +-- Time is relative to when the function was called. Time value for each key must be larger than the previous key. If time is set to 0 then the fog will be applied to the corresponding visibility and thickness values at that key. Any time value greater than 0 will result in the current fog being inherited and changed to the first key. +-- Visibility: in meters 100 to 100000 +-- Thickness: in meters 100 to 5000 +-- The speed at which the visibility and thickness changes is based on the time between keys and the values that visibility and thickness are being set to. +-- +-- When the function is passed an empty table {} or nil the fog animation will be discarded and whatever the current thickness and visibility are set to will remain. +-- +-- The following will set the fog in the mission to disappear in 1 minute. +-- +-- UTILS.Weather.SetFogAnimation({ {60, 0, 0} }) +-- +-- The following will take 1 hour to get to the first fog setting, it will maintain that fog setting for another hour, then lightly removes the fog over the 2nd and 3rd hour, the completely removes the fog after 3 hours and 3 minutes from when the function was called. +-- +-- UTILS.Weather.SetFogAnimation({ +-- {3600, 10000, 3000}, -- one hour to get to that fog setting +-- {7200, 10000, 3000}, -- will maintain for 2 hours +-- {10800, 20000, 2000}, -- at 3 hours visibility will have been increased while thickness decreases slightly +-- {12600, 0, 0}, -- at 3:30 after the function was called the fog will be completely removed. +-- }) +-- +function UTILS.Weather.SetFogAnimation(AnimationKeys) + return world.weather.setFogAnimation(AnimationKeys) +end + +--- The fog animation will be discarded and whatever the current thickness and visibility are set to will remain +function UTILS.Weather.StopFogAnimation() + return world.weather.setFogAnimation({}) +end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 4a9c7e0f1..df6a95ee0 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -64,6 +64,11 @@ -- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, -- the first letter of the method is also capitalized. So, by example, the DCS Airbase method DCSWrapper.Airbase#Airbase.getName() -- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). +-- +-- ## Note on the "H" heli pads in the Syria map: +-- +-- As of the time of writing (Oct 2024, DCS DCS 2.9.8.1107), these 143 objects have the **same name and object ID**, which makes them unusable in Moose, e.g. you cannot find a specific one for spawning etc. +-- Waiting for Ugra and ED to fix this issue. -- -- @field #AIRBASE AIRBASE AIRBASE = { @@ -250,6 +255,9 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Villacoublay -- * AIRBASE.Normandy.Vrigny -- * AIRBASE.Normandy.West_Malling +-- * AIRBASE.Normandy.Eastchurch +-- * AIRBASE.Normandy.Headcorn +-- * AIRBASE.Normandy.Hawkinge -- -- @field Normandy AIRBASE.Normandy = { @@ -332,6 +340,9 @@ AIRBASE.Normandy = { ["Villacoublay"] = "Villacoublay", ["Vrigny"] = "Vrigny", ["West_Malling"] = "West Malling", + ["Eastchurch"] = "Eastchurch", + ["Headcorn"] = "Headcorn", + ["Hawkinge"] = "Hawkinge", } --- Airbases of the Persion Gulf Map: @@ -452,6 +463,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Gaziantep -- * AIRBASE.Syria.Gazipasa -- * AIRBASE.Syria.Gecitkale +-- * AIRBASE.Syria.H -- * AIRBASE.Syria.H3 -- * AIRBASE.Syria.H3_Northwest -- * AIRBASE.Syria.H3_Southwest @@ -499,6 +511,10 @@ AIRBASE.TheChannel = { -- * AIRBASE.Syria.Tha_lah -- * AIRBASE.Syria.Tiyas -- * AIRBASE.Syria.Wujah_Al_Hajar +-- * AIRBASE.Syria.Ben_Gurion +-- * AIRBASE.Syria.Hatzor +-- * AIRBASE.Syria.Palmashim +-- * AIRBASE.Syria.Tel_Nof -- --@field Syria AIRBASE.Syria={ @@ -520,6 +536,7 @@ AIRBASE.Syria={ ["Gaziantep"] = "Gaziantep", ["Gazipasa"] = "Gazipasa", ["Gecitkale"] = "Gecitkale", + ["H"] = "H", ["H3"] = "H3", ["H3_Northwest"] = "H3 Northwest", ["H3_Southwest"] = "H3 Southwest", @@ -567,6 +584,10 @@ AIRBASE.Syria={ ["Tha_lah"] = "Tha'lah", ["Tiyas"] = "Tiyas", ["Wujah_Al_Hajar"] = "Wujah Al Hajar", + ["Ben_Gurion"] = "Ben Gurion", + ["Hatzor"] = "Hatzor", + ["Palmashim"] = "Palmashim", + ["Tel_Nof"] = "Tel Nof", } --- Airbases of the Mariana Islands map: @@ -595,7 +616,6 @@ AIRBASE.MarianaIslands = { --- Airbases of the South Atlantic map: -- -- * AIRBASE.SouthAtlantic.Almirante_Schroeders --- * AIRBASE.SouthAtlantic.Caleta_Tortel -- * AIRBASE.SouthAtlantic.Comandante_Luis_Piedrabuena -- * AIRBASE.SouthAtlantic.Cullen -- * AIRBASE.SouthAtlantic.El_Calafate @@ -626,7 +646,6 @@ AIRBASE.MarianaIslands = { --@field SouthAtlantic AIRBASE.SouthAtlantic={ ["Almirante_Schroeders"] = "Almirante Schroeders", - ["Caleta_Tortel"] = "Caleta Tortel", ["Comandante_Luis_Piedrabuena"] = "Comandante Luis Piedrabuena", ["Cullen"] = "Cullen", ["El_Calafate"] = "El Calafate", @@ -659,100 +678,237 @@ AIRBASE.SouthAtlantic={ -- -- * AIRBASE.Sinai.Abu_Rudeis -- * AIRBASE.Sinai.Abu_Suwayr +-- * AIRBASE.Sinai.Al_Bahr_al_Ahmar -- * AIRBASE.Sinai.Al_Ismailiyah +-- * AIRBASE.Sinai.Al_Khatatbah -- * AIRBASE.Sinai.Al_Mansurah +-- * AIRBASE.Sinai.Al_Rahmaniyah_Air_Base -- * AIRBASE.Sinai.As_Salihiyah -- * AIRBASE.Sinai.AzZaqaziq -- * AIRBASE.Sinai.Baluza -- * AIRBASE.Sinai.Ben_Gurion +-- * AIRBASE.Sinai.Beni_Suef -- * AIRBASE.Sinai.Bilbeis_Air_Base -- * AIRBASE.Sinai.Bir_Hasanah +-- * AIRBASE.Sinai.Birma_Air_Base +-- * AIRBASE.Sinai.Borj_El_Arab_International_Airport -- * AIRBASE.Sinai.Cairo_International_Airport -- * AIRBASE.Sinai.Cairo_West -- * AIRBASE.Sinai.Difarsuwar_Airfield -- * AIRBASE.Sinai.El_Arish -- * AIRBASE.Sinai.El_Gora +-- * AIRBASE.Sinai.El_Minya -- * AIRBASE.Sinai.Fayed +-- * AIRBASE.Sinai.Gebel_El_Basur_Air_Base -- * AIRBASE.Sinai.Hatzerim -- * AIRBASE.Sinai.Hatzor +-- * AIRBASE.Sinai.Hurghada_International_Airport -- * AIRBASE.Sinai.Inshas_Airbase +-- * AIRBASE.Sinai.Jiyanklis_Air_Base -- * AIRBASE.Sinai.Kedem -- * AIRBASE.Sinai.Kibrit_Air_Base +-- * AIRBASE.Sinai.Kom_Awshim -- * AIRBASE.Sinai.Melez -- * AIRBASE.Sinai.Nevatim -- * AIRBASE.Sinai.Ovda --- * AIRBASE.Sinai.Palmahim +-- * AIRBASE.Sinai.Palmachim +-- * AIRBASE.Sinai.Quwaysina -- * AIRBASE.Sinai.Ramon_Airbase +-- * AIRBASE.Sinai.Ramon_International_Airport -- * AIRBASE.Sinai.Sde_Dov +-- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport -- * AIRBASE.Sinai.St_Catherine -- * AIRBASE.Sinai.Tel_Nof +-- * AIRBASE.Sinai.Wadi_Abu_Rish -- * AIRBASE.Sinai.Wadi_al_Jandali -- -- @field Sinai AIRBASE.Sinai = { ["Abu_Rudeis"] = "Abu Rudeis", ["Abu_Suwayr"] = "Abu Suwayr", + ["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar", ["Al_Ismailiyah"] = "Al Ismailiyah", + ["Al_Khatatbah"] = "Al Khatatbah", ["Al_Mansurah"] = "Al Mansurah", + ["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base", ["As_Salihiyah"] = "As Salihiyah", ["AzZaqaziq"] = "AzZaqaziq", ["Baluza"] = "Baluza", ["Ben_Gurion"] = "Ben-Gurion", + ["Beni_Suef"] = "Beni Suef", ["Bilbeis_Air_Base"] = "Bilbeis Air Base", ["Bir_Hasanah"] = "Bir Hasanah", + ["Birma_Air_Base"] = "Birma Air Base", + ["Borj_El_Arab_International_Airport"] = "Borj El Arab International Airport", ["Cairo_International_Airport"] = "Cairo International Airport", ["Cairo_West"] = "Cairo West", ["Difarsuwar_Airfield"] = "Difarsuwar Airfield", ["El_Arish"] = "El Arish", ["El_Gora"] = "El Gora", + ["El_Minya"] = "El Minya", ["Fayed"] = "Fayed", + ["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base", ["Hatzerim"] = "Hatzerim", ["Hatzor"] = "Hatzor", + ["Hurghada_International_Airport"] = "Hurghada International Airport", ["Inshas_Airbase"] = "Inshas Airbase", + ["Jiyanklis_Air_Base"] = "Jiyanklis Air Base", ["Kedem"] = "Kedem", ["Kibrit_Air_Base"] = "Kibrit Air Base", + ["Kom_Awshim"] = "Kom Awshim", ["Melez"] = "Melez", ["Nevatim"] = "Nevatim", ["Ovda"] = "Ovda", - ["Palmahim"] = "Palmahim", + ["Palmachim"] = "Palmachim", + ["Quwaysina"] = "Quwaysina", ["Ramon_Airbase"] = "Ramon Airbase", + ["Ramon_International_Airport"] = "Ramon International Airport", ["Sde_Dov"] = "Sde Dov", + ["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport", ["St_Catherine"] = "St Catherine", ["Tel_Nof"] = "Tel Nof", + ["Wadi_Abu_Rish"] = "Wadi Abu Rish", ["Wadi_al_Jandali"] = "Wadi al Jandali", } --- Airbases of the Kola map -- -- * AIRBASE.Kola.Banak --- * AIRBASE.Kola.Bas_100 -- * AIRBASE.Kola.Bodo +-- * AIRBASE.Kola.Ivalo -- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Kalixfors +-- * AIRBASE.Kola.Kallax -- * AIRBASE.Kola.Kemi_Tornio +-- * AIRBASE.Kola.Kirkenes -- * AIRBASE.Kola.Kiruna +-- * AIRBASE.Kola.Kuusamo -- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Murmansk_International -- * AIRBASE.Kola.Olenya -- * AIRBASE.Kola.Rovaniemi -- * AIRBASE.Kola.Severomorsk_1 -- * AIRBASE.Kola.Severomorsk_3 --- +-- * AIRBASE.Kola.Vidsel +-- * AIRBASE.Kola.Vuojarvi +-- * AIRBASE.Kola.Andoya +-- * AIRBASE.Kola.Alakourtti +-- * AIRBASE.Kola.Kittila +-- * AIRBASE.Kola.Bardufoss +-- -- @field Kola AIRBASE.Kola = { ["Banak"] = "Banak", - ["Bas_100"] = "Bas 100", ["Bodo"] = "Bodo", + ["Ivalo"] = "Ivalo", ["Jokkmokk"] = "Jokkmokk", ["Kalixfors"] = "Kalixfors", + ["Kallax"] = "Kallax", ["Kemi_Tornio"] = "Kemi Tornio", + ["Kirkenes"] = "Kirkenes", ["Kiruna"] = "Kiruna", + ["Kuusamo"] = "Kuusamo", ["Monchegorsk"] = "Monchegorsk", ["Murmansk_International"] = "Murmansk International", ["Olenya"] = "Olenya", ["Rovaniemi"] = "Rovaniemi", ["Severomorsk_1"] = "Severomorsk-1", ["Severomorsk_3"] = "Severomorsk-3", + ["Vidsel"] = "Vidsel", + ["Vuojarvi"] = "Vuojarvi", + ["Andoya"] = "Andoya", + ["Alakourtti"] = "Alakourtti", + ["Kittila"] = "Kittila", + ["Bardufoss"] = "Bardufoss", +} + +--- Airbases of the Afghanistan map +-- +-- * AIRBASE.Afghanistan.Bost +-- * AIRBASE.Afghanistan.Bagram +-- * AIRBASE.Afghanistan.Bamyan +-- * AIRBASE.Afghanistan.Camp_Bastion +-- * AIRBASE.Afghanistan.Camp_Bastion_Heliport +-- * AIRBASE.Afghanistan.Chaghcharan +-- * AIRBASE.Afghanistan.Dwyer +-- * AIRBASE.Afghanistan.Farah +-- * AIRBASE.Afghanistan.Herat +-- * AIRBASE.Afghanistan.Gardez +-- * AIRBASE.Afghanistan.Ghazni_Heliport +-- * AIRBASE.Afghanistan.Jalalabad +-- * AIRBASE.Afghanistan.Kabul +-- * AIRBASE.Afghanistan.Kandahar +-- * AIRBASE.Afghanistan.Kandahar_Heliport +-- * AIRBASE.Afghanistan.Khost +-- * AIRBASE.Afghanistan.Khost_Heliport +-- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi +-- * AIRBASE.Afghanistan.Nimroz +-- * AIRBASE.Afghanistan.Qala_i_Naw +-- * AIRBASE.Afghanistan.Shindand +-- * AIRBASE.Afghanistan.Shindand_Heliport +-- * AIRBASE.Afghanistan.Tarinkot +-- * AIRBASE.Afghanistan.Urgoon_Heliport +-- +-- @field Afghanistan +AIRBASE.Afghanistan = { + ["Bagram"] = "Bagram", + ["Bamyan"] = "Bamyan", + ["Bost"] = "Bost", + ["Camp_Bastion"] = "Camp Bastion", + ["Camp_Bastion_Heliport"] = "Camp Bastion Heliport", + ["Chaghcharan"] = "Chaghcharan", + ["Dwyer"] = "Dwyer", + ["Farah"] = "Farah", + ["Gardez"] = "Gardez", + ["Ghazni_Heliport"] = "Ghazni Heliport", + ["Herat"] = "Herat", + ["Jalalabad"] = "Jalalabad", + ["Kabul"] = "Kabul", + ["Kandahar"] = "Kandahar", + ["Kandahar_Heliport"] = "Kandahar Heliport", + ["Khost"] = "Khost", + ["Khost_Heliport"] = "Khost Heliport", + ["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi", + ["Nimroz"] = "Nimroz", + ["Qala_i_Naw"] = "Qala i Naw", + ["Sharana"] = "Sharana", + ["Shindand"] = "Shindand", + ["Shindand_Heliport"] = "Shindand Heliport", + ["Tarinkot"] = "Tarinkot", + ["Urgoon_Heliport"] = "Urgoon Heliport", +} + +--- Airbases of the Iraq map +-- +-- * AIRBASE.Iraq.Baghdad_International_Airport +-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport +-- * AIRBASE.Iraq.Al_Sahra_Airport +-- * AIRBASE.Iraq.Erbil_International_Airpor +-- * AIRBASE.Iraq.Al_Taji_Airport +-- * AIRBASE.Iraq.Al_Asad_Airbase +-- * AIRBASE.Iraq.Al_Salam_Airbase +-- * AIRBASE.Iraq.Balad_Airbase +-- * AIRBASE.Iraq.Kirkuk_International_Airport +-- * AIRBASE.Iraq.Bashur_Airport +-- * AIRBASE.Iraq.Al_Taquddum_Airport +-- * AIRBASE.Iraq.Qayyarah_Airfield_West +-- * AIRBASE.Iraq.K1_Base +-- +-- @field Iraq +AIRBASE.Iraq = { + ["Baghdad_International_Airport"] = "Baghdad International Airport", + ["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport", + ["Al_Sahra_Airport"] = "Al-Sahra Airport", + ["Erbil_International_Airport"] = "Erbil International Airport", + ["Al_Taji_Airport"] = "Al-Taji Airport", + ["Al_Asad_Airbase"] = "Al-Asad Airbase", + ["Al_Salam_Airbase"] = "Al-Salam Airbase", + ["Balad_Airbase"] = "Balad Airbase", + ["Kirkuk_International_Airport"] = "Kirkuk International Airport", + ["Bashur_Airport"] = "Bashur Airport", + ["Al_Taquddum_Airport"] = "Al-Taquddum Airport", + ["Qayyarah_Airfield_West"] = "Qayyarah Airfield West", + ["K1_Base"] = "K1 Base", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". @@ -791,11 +947,12 @@ AIRBASE.Kola = { -- @field #number HelicopterOnly 40: Special spots for Helicopers. -- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map. -- @field #number OpenMed 72: Open/Shelter air airplane only. +-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't -- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. -- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. -- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig. --- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. --- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't +-- @field #number FighterAircraft 244: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. +-- @field #number FighterAircraftSmall 344: Combines Shelter, SmallsizeFighter, OpenMed and OpenBig spots. So effectively all spots usable by small fixed wing aircraft. AIRBASE.TerminalType = { Runway=16, HelicopterOnly=40, @@ -806,6 +963,7 @@ AIRBASE.TerminalType = { OpenMedOrBig=176, HelicopterUsable=216, FighterAircraft=244, + FighterAircraftSmall=344, } --- Status of a parking spot. @@ -894,40 +1052,55 @@ function AIRBASE:Register(AirbaseName) --end -- Set category. - if self.category==Airbase.Category.AIRDROME then - self.isAirdrome=true - elseif self.category==Airbase.Category.HELIPAD then +if self.category==Airbase.Category.AIRDROME then + self.isAirdrome=true +elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then + self.isHelipad=true + self.category=Airbase.Category.HELIPAD +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 - 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!") + self.isShip=false + self.category=Airbase.Category.HELIPAD + _DATABASE:AddStatic(AirbaseName) end +else + self:E("ERROR: Unknown airbase category!") +end -- Init Runways. self:_InitRunways() - + + -- Number of runways + local Nrunways=#self.runways + -- Set the active runways based on wind direction. - if self.isAirdrome then + if Nrunways>0 then self:SetActiveRunway() end -- Init parking spots. self:_InitParkingSpots() + + -- Some heliports identify as airdromes in the airbase category. This is buggy in the descriptors category but also in the getCategory() and getCategoryEx() functions. + -- Well, thinking about it, this is actually not that "buggy" since these are really helicopter airdromes, which do not have an automatic parking spot routine. + -- I am still changing the category but marking it as airdrome and heliport at the same time via isAirdrome=true and isHelipad=true (important in SPAWN.SpawnAtAirbase). + -- The main reason for changing the category is to be able to filter airdromes from helipads, e.g. in SET_AIRBASE. + if self.category==Airbase.Category.AIRDROME and (Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly]) then + --self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName)) + self.category=Airbase.Category.HELIPAD + self.isAirdrome=true + self.isHelipad=true + end -- Get 2D position vector. local vec2=self:GetVec2() -- Init coordinate. self:GetCoordinate() - + -- Storage. self.storage=_DATABASE:AddStorage(AirbaseName) @@ -950,6 +1123,46 @@ function AIRBASE:Register(AirbaseName) return self end +--- Get the category of this airbase. This is only a debug function because DCS 2.9 incorrectly returns heliports as airdromes. +-- @param #AIRBASE self +function AIRBASE:_GetCategory() + + local name=self.AirbaseName + + local static=StaticObject.getByName(name) + local airbase=Airbase.getByName(name) + local unit=Unit.getByName(name) + + local text=string.format("\n=====================================================") + text=text..string.format("\nAirbase %s:", name) + if static then + local oc, uc=static:getCategory() + local ex=static:getCategoryEx() + text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d", oc, uc, ex) + --text=text..UTILS.PrintTableToLog(static:getDesc(), nil, true) + text=text..string.format("\n--------------------------------------------------") + end + if unit then + local oc, uc=unit:getCategory() + local ex=unit:getCategoryEx() + text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d", oc, uc, ex) + --text=text..UTILS.PrintTableToLog(unit:getDesc(), nil, true) + text=text..string.format("\n--------------------------------------------------") + end + if airbase then + local oc, uc=airbase:getCategory() + local ex=airbase:getCategoryEx() + text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d", oc, uc, ex) + text=text..string.format("\n--------------------------------------------------") + text=text..UTILS.PrintTableToLog(airbase:getDesc(), nil, true) + end + + text=text..string.format("\n=====================================================") + + + env.info(text) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Reference methods ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1629,7 +1842,7 @@ function AIRBASE:_InitParkingSpots() self.NparkingTotal=self.NparkingTotal+1 for _,terminalType in pairs(AIRBASE.TerminalType) do - if self._CheckTerminalType(terminalType, park.TerminalType) then + if self._CheckTerminalType(park.TerminalType, terminalType) then self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1 end end @@ -1637,6 +1850,9 @@ function AIRBASE:_InitParkingSpots() self.parkingByID[park.TerminalID]=park table.insert(self.parking, park) end + + -- Runways are not included in total number of parking spots + self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway] return self end @@ -2060,9 +2276,13 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) match=true end elseif termtype==AIRBASE.TerminalType.FighterAircraft then - if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then + if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then match=true end + elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then + if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then + match=true + end end return match @@ -2120,11 +2340,6 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Runway table. local Runways={} - if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self.runways={} - return {} - end - --- Function to create a runway data table. local function _createRunway(name, course, width, length, center) @@ -2210,7 +2425,7 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Debug info. self:T2(runways) - if runways then + if runways and #runways>0 then -- Loop over runways. for _,rwy in pairs(runways) do @@ -2243,6 +2458,12 @@ function AIRBASE:_InitRunways(IncludeInverse) end end + + else + + -- No runways + self.runways={} + return {} end diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index f91b7a47c..d39f0bb30 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -201,6 +201,13 @@ function CLIENT:AddPlayer(PlayerName) return self end +--- Get number of associated players. +-- @param #CLIENT self +-- @return #number Count +function CLIENT:CountPlayers() + return #self.Players or 0 +end + --- Get player name(s). -- @param #CLIENT self -- @return #table List of player names or an empty table `{}`. @@ -306,7 +313,7 @@ function CLIENT:IsMultiSeated() return false end ---- Checks for a client alive event and calls a function on a continuous basis. +--- Checks for a client alive event and calls a function on a continuous basis. Does **NOT** work for dynamic spawn client slots! -- @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. @@ -325,7 +332,7 @@ end -- @param #CLIENT self function CLIENT:_AliveCheckScheduler( SchedulerName ) - self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) + self:T2( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } ) if self:IsAlive() then @@ -608,4 +615,3 @@ function CLIENT:GetPlayerInfo(Attribute) return nil end end - diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 2817147e4..b9f5a0d86 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -58,7 +58,7 @@ -- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. -- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving. -- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable. --- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only. +-- * @{#CONTROLLABLE.TaskLandAtVec2}: (AIR HELICOPTER) Landing at the ground. For helicopters only. -- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS). -- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude. -- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed. @@ -174,7 +174,10 @@ -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- -- ## 5.5) Air-2-Air missile attack range: --- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. +-- +-- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs +-- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT. -- -- @field #CONTROLLABLE CONTROLLABLE = { @@ -899,7 +902,11 @@ function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay ) groupId = self:GetID(), }, } - + + --if self:IsGround() then + --CommandEPLRS.params.groupId = self:GetID() + --end + if Delay and Delay > 0 then SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay ) else @@ -934,7 +941,7 @@ function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay) end ---- Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequency) +--- Set radio frequency. See [DCS command SetFrequency](https://wiki.hoggitworld.com/view/DCS_command_setFrequency) -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. @@ -953,7 +960,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay ) } if Delay and Delay > 0 then - SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power } ) + SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power },Delay ) else self:SetCommand( CommandSetFrequency ) end @@ -961,12 +968,12 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay ) return self end ---- [AIR] Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit) +--- [AIR] Set radio frequency. See [DCS command SetFrequencyForUnit](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit) -- @param #CONTROLLABLE self -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. -- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10. --- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. +-- @param #number UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. -- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) @@ -980,13 +987,72 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit }, } if Delay and Delay>0 then - SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID}) + SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay) else self:SetCommand(CommandSetFrequencyForUnit) end return self end +--- [AIR] Set smoke on or off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #boolean OnOff Set to true for on and false for off. Defaults to true. +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay) + local switch = (OnOff == nil) and true or OnOff + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = switch + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke on. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeON(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = true + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay) + else + self:SetCommand(command) + end + return self +end + +--- [AIR] Set smoke off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off) +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay the command by this many seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandSmokeOFF(Delay) + local command = { + id = 'SMOKE_ON_OFF', + params = { + value = false + } + } + if Delay and Delay>0 then + SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay) + else + self:SetCommand(command) + end + return self +end + --- Set EPLRS data link on/off. -- @param #CONTROLLABLE self -- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off. @@ -1006,13 +1072,17 @@ function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx ) groupId = self:GetID(), }, } - + + --if self:IsGround() then + --CommandEPLRS.params.groupId = self:GetID() + --end + return self:TaskWrappedAction( CommandEPLRS, idx or 1 ) end -- TASKS FOR AIR CONTROLLABLES ---- (AIR) Attack a Controllable. +--- (AIR + GROUND) Attack a Controllable. -- @param #CONTROLLABLE self -- @param Wrapper.Group#GROUP AttackGroup The Group to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. @@ -1060,7 +1130,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At return DCSTask end ---- (AIR) Attack the Unit. +--- (AIR + GROUND) Attack the Unit. -- @param #CONTROLLABLE self -- @param Wrapper.Unit#UNIT AttackUnit The UNIT to be attacked -- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. Default false. @@ -1148,10 +1218,10 @@ function CONTROLLABLE:TaskStrafing( Vec2, AttackQty, Length, WeaponType, WeaponE id = 'Strafing', params = { point = Vec2, -- req - weaponType = WeaponType or 1073741822, + weaponType = WeaponType or 805337088, -- Default 805337088 corresponds to guns/cannons (805306368) + any rocket (30720). You can set other types but then the AI uses even bombs for a strafing run! expend = WeaponExpend or "Auto", attackQty = AttackQty or 1, -- req - attackQtyLimit = AttackQty >1 and true or false, + attackQtyLimit = AttackQty~=nil and true or false, direction = Direction and math.rad(Direction) or 0, directionEnabled = Direction and true or false, groupAttack = GroupAttack or false, @@ -1516,8 +1586,10 @@ end -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 The point where to land. -- @param #number Duration The duration in seconds to stay on the ground. +-- @param #boolean CombatLanding (optional) If true, set the Combat Landing option. +-- @param #number DirectionAfterLand (optional) Heading after landing in degrees. -- @return #CONTROLLABLE self -function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration ) +function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration , CombatLanding, DirectionAfterLand) local DCSTask = { id = 'Land', @@ -1525,9 +1597,15 @@ function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration ) point = Vec2, durationFlag = Duration and true or false, duration = Duration, + combatLandingFlag = CombatLanding == true and true or false, }, } - + + if DirectionAfterLand ~= nil and type(DirectionAfterLand) == "number" then + DCSTask.params.directionEnabled = true + DCSTask.params.direction = math.rad(DirectionAfterLand) + end + return DCSTask end @@ -1535,13 +1613,16 @@ end -- @param #CONTROLLABLE self -- @param Core.Zone#ZONE Zone The zone where to land. -- @param #number Duration The duration in seconds to stay on the ground. +-- @param #boolean RandomPoint (optional) If true,land at a random point inside of the zone. +-- @param #boolean CombatLanding (optional) If true, set the Combat Landing option. +-- @param #number DirectionAfterLand (optional) Heading after landing in degrees. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) +function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint, CombatLanding, DirectionAfterLand ) -- Get landing point local Point = RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2() - local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration ) + local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration, CombatLanding, DirectionAfterLand) return DCSTask end @@ -1755,8 +1836,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, return DCSTask end --- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES - --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self -- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. @@ -2983,7 +3062,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad if DCSControllable then local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil - local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil + local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil @@ -3017,26 +3096,27 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad return nil end ---- Check if a target is detected. +--- Check if a DCS object (unit or static) is detected by the controllable. +-- Note that after a target is detected it remains "detected" for a certain amount of time, even if the controllable cannot "see" the target any more with it's sensors. -- 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. -- If **at least one** detection method is specified, only the methods set to *true* will be used. -- @param #CONTROLLABLE self -- @param DCS#Object DCSObject The DCS object that is checked. --- @param #CONTROLLABLE self -- @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. --- @return #boolean True if target is detected. --- @return #boolean True if target is visible by line of sight. --- @return #number Mission time when target was detected. --- @return #boolean True if target type is known. --- @return #boolean True if distance to target is known. --- @return DCS#Vec3 Last known position vector of the target. --- @return DCS#Vec3 Last known velocity vector of the target. +-- @return #boolean `true` if target is detected. +-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`). +-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`). +-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`). +-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. +-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. +-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) self:F2( self.ControllableName ) @@ -3045,7 +3125,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, if DCSControllable then local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil - local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil + local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil @@ -3053,10 +3133,10 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, local Controller = self:_GetController() - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity = Controller:isTargetDetected( DCSObject, DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity + return TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity end return nil @@ -3064,6 +3144,7 @@ end --- Check if a certain UNIT is detected by the controllable. -- 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. -- If **at least one** detection method is specified, only the methods set to *true* will be used. -- @param #CONTROLLABLE self @@ -3074,13 +3155,13 @@ end -- @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. --- @return #boolean True if target is detected. --- @return #boolean True if target is visible by line of sight. --- @return #number Mission time when target was detected. --- @return #boolean True if target type is known. --- @return #boolean True if distance to target is known. --- @return DCS#Vec3 Last known position vector of the target. --- @return DCS#Vec3 Last known velocity vector of the target. +-- @return #boolean `true` if target is detected. +-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`). +-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`). +-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`). +-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. +-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. +-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned. function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) self:F2( self.ControllableName ) @@ -3800,6 +3881,48 @@ function CONTROLLABLE:OptionProhibitAfterburner( Prohibit ) return self end +--- [Ground] Allows AI radar units to take defensive actions to avoid anti radiation missiles. Units are allowed to shut radar off and displace. +-- @param #CONTROLLABLE self +-- @param #number Seconds Can be - nil, 0 or false = switch off this option, any positive number = number of seconds the escape sequency runs. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionEvasionOfARM(Seconds) + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsGround() then + if Seconds == nil then Seconds = false end + Controller:setOption( AI.Option.Ground.id.EVASION_OF_ARM, Seconds) + end + + end + + return self +end + +--- [Ground] Option that defines the vehicle spacing when in an on road and off road formation. +-- @param #CONTROLLABLE self +-- @param #number meters Can be zero to 100 meters. Defaults to 50 meters. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionFormationInterval(meters) + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsGround() then + if meters == nil or meters > 100 or meters < 0 then meters = 50 end + Controller:setOption( 30, meters) + end + + end + + return self +end + --- [Air] Defines the usage of Electronic Counter Measures by airborne forces. -- @param #CONTROLLABLE self -- @param #number ECMvalue Can be - 0=Never on, 1=if locked by radar, 2=if detected by radar, 3=always on, defaults to 1 @@ -4197,6 +4320,9 @@ function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, short self:F2( { self.ControllableName } ) local _coord = self:GetCoordinate() + if not _coord then + return self + end local _radius = radius or 500 local _speed = speed or 20 local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 ) @@ -4246,7 +4372,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds ) end --- Returns if the unit is a submarine. --- @param #POSITIONABLE self +-- @param #CONTROLLABLE self -- @return #boolean Submarines attributes result. function CONTROLLABLE:IsSubmarine() self:F2() @@ -5566,3 +5692,166 @@ function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation return self end + +--- IR Marker courtesy Florian Brinker (fbrinker) + +--- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP. +-- @param #CONTROLLABLE self +-- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on. +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. Defaults to 60 seconds. +-- @return #CONTROLLABLE self +function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime) + self:T2("NewIRMarker") + if self:IsInstanceOf("GROUP") then + if self.IRMarkerGroup == true then return end + self.IRMarkerGroup = true + self.IRMarkerUnit = false + elseif self:IsInstanceOf("UNIT") then + if self.IRMarkerUnit == true then return end + self.IRMarkerGroup = false + self.IRMarkerUnit = true + end + + self.Runtime = Runtime or 60 + if EnableImmediately and EnableImmediately == true then + self:EnableIRMarker(Runtime) + end + + return self +end + +--- [GROUND] Enable the IR marker. +-- @param #CONTROLLABLE self +-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`. +-- @return #CONTROLLABLE self +function CONTROLLABLE:EnableIRMarker(Runtime) + self:T2("EnableIRMarker") + if self.IRMarkerGroup == nil then + self:NewIRMarker(true,Runtime) + return + end + + if self:IsInstanceOf("GROUP") then + self:EnableIRMarkerForGroup(Runtime) + return + end + + if self.timer and self.timer:IsRunning() then return self end + + local Runtime = Runtime or self.Runtime + self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self) + self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized + self.IRMarkerUnit = true + + return self +end + +--- [GROUND] Disable the IR marker. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:DisableIRMarker() + self:T2("DisableIRMarker") + if self:IsInstanceOf("GROUP") then + self:DisableIRMarkerForGroup() + return + end + + if self.spot then + self.spot = nil + end + if self.timer and self.timer:IsRunning() then + self.timer:Stop() + self.timer = nil + end + + if self:IsInstanceOf("GROUP") then + self.IRMarkerGroup = nil + elseif self:IsInstanceOf("UNIT") then + self.IRMarkerUnit = nil + end + + return self +end + +--- [GROUND] Enable the IR markers for a whole group. +-- @param #CONTROLLABLE self +-- @param #number Runtime Runtime of the marker in seconds +-- @return #CONTROLLABLE self +function CONTROLLABLE:EnableIRMarkerForGroup(Runtime) + self:T2("EnableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") + then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + _unit:EnableIRMarker(Runtime) + end + self.IRMarkerGroup = true + end + return self +end + +--- [GROUND] Disable the IR markers for a whole group. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:DisableIRMarkerForGroup() + self:T2("DisableIRMarkerForGroup") + if self:IsInstanceOf("GROUP") then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + _unit:DisableIRMarker() + end + self.IRMarkerGroup = nil + end + return self +end + +--- [GROUND] Check if an IR Spot exists. +-- @param #CONTROLLABLE self +-- @return #boolean outcome +function CONTROLLABLE:HasIRMarker() + self:T2("HasIRMarker") + if self:IsInstanceOf("GROUP") then + local units = self:GetUnits() or {} + for _,_unit in pairs(units) do + if _unit.timer and _unit.timer:IsRunning() then return true end + end + elseif self.timer and self.timer:IsRunning() then return true end + return false +end + +--- [Internal] This method is called by the scheduler to blink the IR marker. +function CONTROLLABLE._StopSpot(spot) + if spot then + spot:destroy() + end +end + +--- [Internal] This method is called by the scheduler after enabling the IR marker. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:_MarkerBlink() + self:T2("_MarkerBlink") + if self:IsAlive() ~= true then + self:DisableIRMarker() + return + end + + self.timer.dT = 1 - (math.random(1, 2) / 10 / 2) -- randomize the blinking by a small amount + + local _, _, unitBBHeight, _ = self:GetObjectSize() + local unitPos = self:GetPositionVec3() + + if self.timer:IsRunning() then + self:T2("Create Spot") + local spot = Spot.createInfraRed( + self.DCSUnit, + { x = 0, y = (unitBBHeight + 1), z = 0 }, + { x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z } + ) + self.spot = spot + local offTimer = nil + local offTimer = TIMER:New(CONTROLLABLE._StopSpot, spot) + offTimer:Start(0.5) + end + return self +end diff --git a/Moose Development/Moose/Wrapper/DynamicCargo.lua b/Moose Development/Moose/Wrapper/DynamicCargo.lua new file mode 100644 index 000000000..db0d79834 --- /dev/null +++ b/Moose Development/Moose/Wrapper/DynamicCargo.lua @@ -0,0 +1,535 @@ +--- **Wrapper** - Dynamic Cargo create from the F8 menu. +-- +-- ## Main Features: +-- +-- * Convenient access to Ground Crew created cargo items. +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/). +-- +-- === +-- +-- ### Author: **Applevangelist**; additional checks **Chesster** +-- +-- === +-- @module Wrapper.DynamicCargo +-- @image Wrapper_Storage.png + + +--- DYNAMICCARGO class. +-- @type DYNAMICCARGO +-- @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 Wrapper.Storage#STORAGE warehouse The STORAGE object. +-- @field #string version. +-- @field #string CargoState. +-- @field #table DCS#Vec3 LastPosition. +-- @field #number Interval Check Interval. 20 secs default. +-- @field #boolean testing +-- @field Core.Timer#TIMER timer Timmer to run intervals +-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state. +-- @extends Wrapper.Positionable#POSITIONABLE + +--- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx +-- +-- === +-- +-- # The DYNAMICCARGO Concept +-- +-- The DYNAMICCARGO class offers an easy-to-use wrapper interface to all DCS API functions of DCS dynamically spawned cargo crates. +-- We named the class DYNAMICCARGO, because the name WAREHOUSE is already taken by another MOOSE class.. +-- +-- # Constructor +-- +-- @field #DYNAMICCARGO +DYNAMICCARGO = { + ClassName = "DYNAMICCARGO", + verbose = 0, + testing = false, + Interval = 10, + +} + +--- Liquid types. +-- @type DYNAMICCARGO.Liquid +-- @field #number JETFUEL Jet fuel (0). +-- @field #number GASOLINE Aviation gasoline (1). +-- @field #number MW50 MW50 (2). +-- @field #number DIESEL Diesel (3). +DYNAMICCARGO.Liquid = { + JETFUEL = 0, + GASOLINE = 1, + MW50 = 2, + DIESEL = 3, +} + +--- Liquid Names for the static cargo resource table. +-- @type DYNAMICCARGO.LiquidName +-- @field #number JETFUEL "jet_fuel". +-- @field #number GASOLINE "gasoline". +-- @field #number MW50 "methanol_mixture". +-- @field #number DIESEL "diesel". +DYNAMICCARGO.LiquidName = { + GASOLINE = "gasoline", + DIESEL = "diesel", + MW50 = "methanol_mixture", + JETFUEL = "jet_fuel", +} + +--- Storage types. +-- @type DYNAMICCARGO.Type +-- @field #number WEAPONS weapons. +-- @field #number LIQUIDS liquids. Also see #list<#DYNAMICCARGO.Liquid> for types of liquids. +-- @field #number AIRCRAFT aircraft. +DYNAMICCARGO.Type = { + WEAPONS = "weapons", + LIQUIDS = "liquids", + AIRCRAFT = "aircrafts", +} + +--- State types +-- @type DYNAMICCARGO.State +-- @field #string NEW +-- @field #string LOADED +-- @field #string UNLOADED +-- @field #string REMOVED +DYNAMICCARGO.State = { + NEW = "NEW", + LOADED = "LOADED", + UNLOADED = "UNLOADED", + REMOVED = "REMOVED", +} + +--- Helo types possible. +-- @type DYNAMICCARGO.AircraftTypes +DYNAMICCARGO.AircraftTypes = { + ["CH-47Fbl1"] = "CH-47Fbl1", +} + +--- Helo types possible. +-- @type DYNAMICCARGO.AircraftDimensions +DYNAMICCARGO.AircraftDimensions = { + -- CH-47 model start coordinate is quite exactly in the middle of the model, so half values here + ["CH-47Fbl1"] = { + ["width"] = 4, + ["height"] = 6, + ["length"] = 11, + ["ropelength"] = 30, + }, +} + +--- DYNAMICCARGO class version. +-- @field #string version +DYNAMICCARGO.version="0.0.7" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot... + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new DYNAMICCARGO object from the DCS static cargo object. +-- @param #DYNAMICCARGO self +-- @param #string CargoName Name of the Cargo. +-- @return #DYNAMICCARGO self +function DYNAMICCARGO:Register(CargoName) + + -- Inherit everything from a BASE class. + local self=BASE:Inherit(self, POSITIONABLE:New(CargoName)) -- #DYNAMICCARGO + + self.StaticName = CargoName + + self.LastPosition = self:GetCoordinate() + + self.CargoState = DYNAMICCARGO.State.NEW + + self.Interval = DYNAMICCARGO.Interval or 10 + + local DCSObject = self:GetDCSObject() + + if DCSObject then + local warehouse = STORAGE:NewFromDynamicCargo(CargoName) + self.warehouse = warehouse + end + + self.lid = string.format("DYNAMICCARGO %s", CargoName) + + self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None" + + self.timer = TIMER:New(DYNAMICCARGO._UpdatePosition,self) + self.timer:Start(self.Interval,self.Interval) + + if not _DYNAMICCARGO_HELOS then + _DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart() + end + + if self.testing then + BASE:TraceOn() + BASE:TraceClass("DYNAMICCARGO") + end + + return self +end + +--- Get DCS object. +-- @param #DYNAMICCARGO self +-- @return DCS static object +function DYNAMICCARGO:GetDCSObject() + local DCSStatic = StaticObject.getByName( self.StaticName ) or Unit.getByName( self.StaticName ) + if DCSStatic then + return DCSStatic + end + return nil +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User API Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get last known owner name of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return #string Owner +function DYNAMICCARGO:GetLastOwner() + return self.Owner +end + +--- Returns true if the cargo is new and has never been loaded into a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsNew() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.NEW then + return true + else + return false + end +end + +--- Returns true if the cargo been loaded into a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsLoaded() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.LOADED then + return true + else + return false + end +end + +--- Returns true if the cargo has been unloaded from a Helo. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsUnloaded() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then + return true + else + return false + end +end + +--- Returns true if the cargo has been removed. +-- @param #DYNAMICCARGO self +-- @return #boolean Outcome +function DYNAMICCARGO:IsRemoved() + if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then + return true + else + return false + end +end + +--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one. +-- @param #DYNAMICCARGO self +-- @return #number crate number, always one +function DYNAMICCARGO:GetCratesNeeded() + return 1 +end + +--- [CTLD] Get this DYNAMICCARGO drop state. True if DYNAMICCARGO.State.UNLOADED +-- @param #DYNAMICCARGO self +-- @return #boolean Dropped +function DYNAMICCARGO:WasDropped() + return self.CargoState == DYNAMICCARGO.State.UNLOADED and true or false +end + +--- [CTLD] Get CTLD_CARGO.Enum type of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return #string Type, only one at the moment is CTLD_CARGO.Enum.GCLOADABLE +function DYNAMICCARGO:GetType() + return CTLD_CARGO.Enum.GCLOADABLE +end + + +--- Find last known position of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return DCS#Vec3 Position in 3D space +function DYNAMICCARGO:GetLastPosition() + return self.LastPosition +end + +--- Find current state of this DYNAMICCARGO +-- @param #DYNAMICCARGO self +-- @return string The current state +function DYNAMICCARGO:GetState() + return self.CargoState +end + +--- Find a DYNAMICCARGO in the **_DATABASE** using the name associated with it. +-- @param #DYNAMICCARGO self +-- @param #string Name The dynamic cargo name +-- @return #DYNAMICCARGO self +function DYNAMICCARGO:FindByName( Name ) + local storage = _DATABASE:FindDynamicCargo( Name ) + return storage +end + +--- Find the first(!) DYNAMICCARGO matching using patterns. Note that this is **a lot** slower than `:FindByName()`! +-- @param #DYNAMICCARGO self +-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA. +-- @return #DYNAMICCARGO The DYNAMICCARGO. +-- @usage +-- -- Find a dynamic cargo with a partial dynamic cargo name +-- local grp = DYNAMICCARGO:FindByMatching( "Apple" ) +-- -- will return e.g. a dynamic cargo named "Apple|08:00|PKG08" +-- +-- -- using a pattern +-- local grp = DYNAMICCARGO:FindByMatching( ".%d.%d$" ) +-- -- will return the first dynamic cargo found ending in "-1-1" to "-9-9", but not e.g. "-10-1" +function DYNAMICCARGO:FindByMatching( Pattern ) + local GroupFound = nil + + for name,static in pairs(_DATABASE.DYNAMICCARGO) do + if string.match(name, Pattern ) then + GroupFound = static + break + end + end + + return GroupFound +end + +--- Find all DYNAMICCARGO objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`! +-- @param #DYNAMICCARGO self +-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA. +-- @return #table Groups Table of matching #DYNAMICCARGO objects found +-- @usage +-- -- Find all dynamic cargo with a partial dynamic cargo name +-- local grptable = DYNAMICCARGO:FindAllByMatching( "Apple" ) +-- -- will return all dynamic cargos with "Apple" in the name +-- +-- -- using a pattern +-- local grp = DYNAMICCARGO:FindAllByMatching( ".%d.%d$" ) +-- -- will return the all dynamic cargos found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10" +function DYNAMICCARGO:FindAllByMatching( Pattern ) + local GroupsFound = {} + + for name,static in pairs(_DATABASE.DYNAMICCARGO) do + if string.match(name, Pattern ) then + GroupsFound[#GroupsFound+1] = static + end + end + + return GroupsFound +end + +--- Get the #STORAGE object from this dynamic cargo. +-- @param #DYNAMICCARGO self +-- @return Wrapper.Storage#STORAGE Storage The #STORAGE object +function DYNAMICCARGO:GetStorageObject() + return self.warehouse +end + +--- Get the weight in kgs from this dynamic cargo. +-- @param #DYNAMICCARGO self +-- @return #number Weight in kgs. +function DYNAMICCARGO:GetCargoWeight() + local DCSObject = self:GetDCSObject() + if DCSObject then + local weight = DCSObject:getCargoWeight() + return weight + else + return 0 + end +end + +--- Get the cargo display name from this dynamic cargo. +-- @param #DYNAMICCARGO self +-- @return #string The display name +function DYNAMICCARGO:GetCargoDisplayName() + local DCSObject = self:GetDCSObject() + if DCSObject then + local weight = DCSObject:getCargoDisplayName() + return weight + else + return self.StaticName + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- [Internal] _Get helo hovering intel +-- @param #DYNAMICCARGO self +-- @param Wrapper.Unit#UNIT Unit The Unit to test +-- @param #number ropelength Ropelength to test +-- @return #boolean Outcome +function DYNAMICCARGO:_HeloHovering(Unit,ropelength) + local DCSUnit = Unit:GetDCSObject() --DCS#Unit + local hovering = false + local Height = 0 + if DCSUnit then + local UnitInAir = DCSUnit:inAir() + local UnitCategory = DCSUnit:getDesc().category + if UnitInAir == true and UnitCategory == 1 then + local VelocityVec3 = DCSUnit:getVelocity() + local Velocity = UTILS.VecNorm(VelocityVec3) + local Coordinate = DCSUnit:getPoint() + local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z }) + Height = Coordinate.y - LandHeight + if Velocity < 1 and Height <= ropelength and Height > 6 then -- hover lower than ropelength but higher than the normal FARP height. + hovering = true + end + end + return hovering, Height + end + return false +end + +--- [Internal] _Get Possible Player Helo Nearby +-- @param #DYNAMICCARGO self +-- @param Core.Point#COORDINATE pos +-- @param #boolean loading If true measure distance for loading else for unloading +-- @return #boolean Success +-- @return Wrapper.Client#CLIENT Helo +-- @return #string PlayerName +function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading) + local set = _DYNAMICCARGO_HELOS:GetAliveSet() + local success = false + local Helo = nil + local Playername = nil + for _,_helo in pairs (set or {}) do + local helo = _helo -- Wrapper.Client#CLIENT + local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None" + self:T(self.lid.." Checking: "..name) + local hpos = helo:GetCoordinate() + -- TODO Check unloading via sling load? + local typename = helo:GetTypeName() + local dimensions = DYNAMICCARGO.AircraftDimensions[typename] + local hovering, height = self:_HeloHovering(helo,dimensions.ropelength) + local helolanded = not helo:InAir() + self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering)) + if hpos and typename and dimensions then + local delta2D = hpos:Get2DDistance(pos) + local delta3D = hpos:Get3DDistance(pos) + if self.testing then + self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D)) + self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength)) + self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height)) + end + -- unloading from ground + if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again. + success = true + Helo = helo + Playername = name + end + -- unloading from hover/rope + if loading~=true and delta3D > dimensions.ropelength then + success = true + Helo = helo + Playername = name + end + -- loading + if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling + success = true + Helo = helo + Playername = name + end + end + end + return success,Helo,Playername +end + +--- [Internal] Update internal states. +-- @param #DYNAMICCARGO self +-- @return #DYNAMICCARGO self +function DYNAMICCARGO:_UpdatePosition() + self:T(self.lid.." _UpdatePositionAndState") + if self:IsAlive() then + local pos = self:GetCoordinate() + if self.testing then + self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z)) + self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z)) + end + if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded + --------------- + -- LOAD Cargo + --------------- + if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then + local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true) + self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername)) + self.CargoState = DYNAMICCARGO.State.LOADED + self.Owner = playername + _DATABASE:CreateEventDynamicCargoLoaded(self) + end + --------------- + -- UNLOAD Cargo + --------------- + -- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly + elseif self.CargoState == DYNAMICCARGO.State.LOADED then + -- TODO add checker if we are in flight somehow + -- ensure not just the helo is moving + local count = _DYNAMICCARGO_HELOS:CountAlive() + -- Testing + local landheight = pos:GetLandHeight() + local agl = pos.y-landheight + agl = UTILS.Round(agl,2) + self:T(self.lid.." AGL: "..agl or -1) + local isunloaded = true + local client + local playername = self.Owner + if count > 0 then + self:T(self.lid.." Possible alive helos: "..count or -1) + isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false) + if isunloaded then + self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername)) + self.CargoState = DYNAMICCARGO.State.UNLOADED + self.Owner = playername + _DATABASE:CreateEventDynamicCargoUnloaded(self) + end + end + end + self.LastPosition = pos + --end + else + --------------- + -- REMOVED Cargo + --------------- + if self.timer and self.timer:IsRunning() then self.timer:Stop() end + self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED") + self.CargoState = DYNAMICCARGO.State.REMOVED + _DATABASE:CreateEventDynamicCargoRemoved(self) + end + return self +end + +--- [Internal] Track helos for loaded/unloaded decision making. +-- @param Wrapper.Client#CLIENT client +-- @return #boolean IsIn +function DYNAMICCARGO._FilterHeloTypes(client) + if not client then return false end + local typename = client:GetTypeName() + local isinclude = DYNAMICCARGO.AircraftTypes[typename] ~= nil and true or false + return isinclude +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index e458b27d0..db102dc8f 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -360,14 +360,25 @@ end -- @return DCS#Group The DCS Group. function GROUP:GetDCSObject() - -- Get DCS group. - local DCSGroup = Group.getByName( self.GroupName ) + --if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) then - if DCSGroup then - return DCSGroup - end + -- Get DCS group. + local DCSGroup = Group.getByName( self.GroupName ) - self:T2(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) + if DCSGroup then + self.LastCallDCSObject = timer.getTime() + self.DCSObject = DCSGroup + return DCSGroup + -- else + -- self.DCSObject = nil + -- self.LastCallDCSObject = nil + end + + --else + --return self.DCSObject + --end + + --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.GroupName))) return nil end @@ -375,7 +386,7 @@ end -- @param Wrapper.Positionable#POSITIONABLE self -- @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 ) + --self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() @@ -383,7 +394,7 @@ function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3 local unit = DCSPositionable:getUnits()[1] if unit then local PositionablePosition = unit:getPosition().p - self:T3( PositionablePosition ) + --self:T3( PositionablePosition ) return PositionablePosition end end @@ -403,7 +414,7 @@ end -- @param #GROUP self -- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore. function GROUP:IsAlive() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() -- DCS#Group @@ -412,7 +423,7 @@ function GROUP:IsAlive() local DCSUnit = DCSGroup:getUnit(1) -- DCS#Unit if DCSUnit then local GroupIsAlive = DCSUnit:isActive() - self:T3( GroupIsAlive ) + --self:T3( GroupIsAlive ) return GroupIsAlive end end @@ -425,7 +436,7 @@ end -- @param #GROUP self -- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive. function GROUP:IsActive() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() -- DCS#Group @@ -468,26 +479,30 @@ end -- Ship:Destroy( false ) -- Don't generate an event upon destruction. -- function GROUP:Destroy( GenerateEvent, delay ) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) if delay and delay>0 then self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else - local DCSGroup = self:GetDCSObject() + --local DCSGroup = self:GetDCSObject() + local DCSGroup = Group.getByName( self.GroupName ) if DCSGroup then for Index, UnitData in pairs( DCSGroup:getUnits() ) do if GenerateEvent and GenerateEvent == true then if self:IsAir() then self:CreateEventCrash( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),UnitData) else self:CreateEventDead( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),UnitData) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), UnitData ) + --self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),UnitData) end end USERFLAG:New( self:GetName() ):Set( 100 ) @@ -511,12 +526,12 @@ end -- @param #GROUP self -- @return DCS#Group.Category The category ID. function GROUP:GetCategory() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) + --self:T3( GroupCategory ) return GroupCategory end @@ -527,7 +542,7 @@ end -- @param #GROUP self -- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship, Train. function GROUP:GetCategoryName() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -539,7 +554,7 @@ function GROUP:GetCategoryName() [Group.Category.TRAIN] = "Train", } local GroupCategory = DCSGroup:getCategory() - self:T3( GroupCategory ) + --self:T3( GroupCategory ) return CategoryNames[GroupCategory] end @@ -547,20 +562,22 @@ function GROUP:GetCategoryName() return nil end - --- Returns the coalition of the DCS Group. -- @param #GROUP self -- @return DCS#coalition.side The coalition side of the DCS Group. function GROUP:GetCoalition() - self:F2( self.GroupName ) - - local DCSGroup = self:GetDCSObject() - if DCSGroup then - local GroupCoalition = DCSGroup:getCoalition() - self:T3( GroupCoalition ) - return GroupCoalition + --self:F2( self.GroupName ) + if self.GroupCoalition ~= nil then + return self.GroupCoalition + else + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local GroupCoalition = DCSGroup:getCoalition() + --self:T3( GroupCoalition ) + self.GroupCoalition = GroupCoalition + return GroupCoalition + end end - return nil end @@ -568,12 +585,12 @@ end -- @param #GROUP self -- @return DCS#country.id The country identifier or nil if the DCS Group is not existing or alive. function GROUP:GetCountry() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCountry = DCSGroup:getUnit(1):getCountry() - self:T3( GroupCountry ) + --self:T3( GroupCountry ) return GroupCountry end @@ -625,7 +642,7 @@ end -- @param #GROUP self -- @return #number Speed in km/h. function GROUP:GetSpeedMax() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -658,7 +675,7 @@ end -- @param #GROUP self -- @return #number Range in meters. function GROUP:GetRange() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -689,7 +706,7 @@ end -- @param #GROUP self -- @return #table of Wrapper.Unit#UNIT objects, indexed by number. function GROUP:GetUnits() - self:F2( { self.GroupName } ) + --self:F2( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -706,7 +723,7 @@ function GROUP:GetUnits() Units[#Units+1]=unit end end - self:T3( Units ) + --self:T3( Units ) return Units end @@ -717,7 +734,7 @@ end -- @param #GROUP self -- @return #list The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}. function GROUP:GetPlayerUnits() - self:F2( { self.GroupName } ) + --self:F2( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -729,7 +746,7 @@ function GROUP:GetPlayerUnits() Units[#Units+1] = PlayerUnit end end - self:T3( Units ) + --self:T3( Units ) return Units end @@ -740,7 +757,11 @@ end -- @param #GROUP self -- @return #boolean If true, group is associated with a client or player slot. function GROUP:IsPlayer() - return self:GetUnit(1):IsPlayer() + local unit = self:GetUnit(1) + if unit then + return unit:IsPlayer() + end + return false end --- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit. @@ -828,7 +849,7 @@ end -- @param #GROUP self -- @return #number Number of alive units. If DCS group is nil, 0 is returned. function GROUP:CountAliveUnits() - self:F3( { self.GroupName } ) + --self:F3( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -850,7 +871,7 @@ end -- @param #GROUP self -- @return Wrapper.Unit#UNIT First unit alive. function GROUP:GetFirstUnitAlive() - self:F3({self.GroupName}) + --self:F3({self.GroupName}) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -870,7 +891,7 @@ end -- @param #GROUP self -- @return Wrapper.Unit#UNIT First unit or nil if it does not exist. function GROUP:GetFirstUnit() - self:F3({self.GroupName}) + --self:F3({self.GroupName}) local DCSGroup = self:GetDCSObject() if DCSGroup then @@ -885,7 +906,7 @@ end -- @param Wrapper.Group#GROUP self -- @return DCS#Vec3 The velocity Vec3 vector or `#nil` if the GROUP is not existing or alive. function GROUP:GetVelocityVec3() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() @@ -919,7 +940,7 @@ end -- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. -- @return #number The altitude of the group or nil if is not existing or alive. function GROUP:GetAltitude(FromGround) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) return self:GetHeight(FromGround) end @@ -928,7 +949,7 @@ end -- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. -- @return #number The height of the group or nil if is not existing or alive. function GROUP:GetHeight( FromGround ) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() @@ -964,12 +985,12 @@ end -- @param #GROUP self -- @return #number The DCS Group initial size. function GROUP:GetInitialSize() - self:F3( { self.GroupName } ) + --self:F3( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupInitialSize = DCSGroup:getInitialSize() - self:T3( GroupInitialSize ) + --self:T3( GroupInitialSize ) return GroupInitialSize end @@ -981,12 +1002,12 @@ end -- @param #GROUP self -- @return #table The DCS Units. function GROUP:GetDCSUnits() - self:F2( { self.GroupName } ) + --self:F2( { self.GroupName } ) local DCSGroup = self:GetDCSObject() if DCSGroup then local DCSUnits = DCSGroup:getUnits() - self:T3( DCSUnits ) + --self:T3( DCSUnits ) return DCSUnits end @@ -999,7 +1020,7 @@ end -- @param #number delay Delay in seconds, before the group is activated. -- @return #GROUP self function GROUP:Activate(delay) - self:F2( { self.GroupName } ) + --self:F2( { self.GroupName } ) if delay and delay>0 then self:ScheduleOnce(delay, GROUP.Activate, self) else @@ -1008,18 +1029,32 @@ function GROUP:Activate(delay) return self end +--- Deactivates an activated GROUP. +-- @param #GROUP self +-- @param #number delay Delay in seconds, before the group is activated. +-- @return #GROUP self +function GROUP:Deactivate(delay) + --self:F2( { self.GroupName } ) + if delay and delay>0 then + self:ScheduleOnce(delay, GROUP.Deactivate, self) + else + trigger.action.deactivateGroup( self:GetDCSObject() ) + end + return self +end + --- Gets the type name of the group. -- @param #GROUP self -- @return #string The type name of the group. function GROUP:GetTypeName() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) + --self:T3( GroupTypeName ) return( GroupTypeName ) end @@ -1030,13 +1065,13 @@ end --@param #GROUP self --@return #string NatoReportingName or "Bogey" if unknown. function GROUP:GetNatoReportingName() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupTypeName = DCSGroup:getUnit(1):getTypeName() - self:T3( GroupTypeName ) + --self:T3( GroupTypeName ) return UTILS.GetReportingName(GroupTypeName) end @@ -1048,13 +1083,13 @@ end -- @param #GROUP self -- @return #string The player name of the group. function GROUP:GetPlayerName() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local PlayerName = DCSGroup:getUnit(1):getPlayerName() - self:T3( PlayerName ) + --self:T3( PlayerName ) return( PlayerName ) end @@ -1066,13 +1101,13 @@ end -- @param #GROUP self -- @return #string The CallSign of the first DCS Unit of the DCS Group. function GROUP:GetCallsign() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCallSign = DCSGroup:getUnit(1):getCallsign() - self:T3( GroupCallSign ) + --self:T3( GroupCallSign ) return GroupCallSign end @@ -1144,40 +1179,22 @@ function GROUP:GetAverageVec3() end end ---- Returns the current VECTOR of the GROUP. +--- Returns a COORDINATE object indicating the point in 2D of the first UNIT of the GROUP within the mission. -- @param #GROUP self --- @return Core.Vector#VECTOR Current VECTOR of the first Unit of the GROUP. Can return `nil` if no unit can be found. -function GROUP:GetVector() - - -- Get first unit. - local unit=self:GetUnit(1) - - if unit then - local vec3=unit:GetVec3() - local vector=VECTOR:New(vec3.x, vec3.y, vec3.z) - return vector - end - - self:E("ERROR: Cannot get VECTOR of group "..tostring(self.GroupName)) - return nil -end - ---- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. --- @param #GROUP self --- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP. +-- @return Core.Point#COORDINATE The 3D point vector of the first DCS Unit of the GROUP. -- @return #nil The first UNIT is not existing or alive. function GROUP:GetPointVec2() - self:F2(self.GroupName) + --self:F2(self.GroupName) local FirstUnit = self:GetUnit(1) if FirstUnit then local FirstUnitPointVec2 = FirstUnit:GetPointVec2() - self:T3(FirstUnitPointVec2) + --self:T3(FirstUnitPointVec2) return FirstUnitPointVec2 end - BASE:E( { "Cannot GetPointVec2", Group = self, Alive = self:IsAlive() } ) + BASE:E( { "Cannot get COORDINATE", Group = self, Alive = self:IsAlive() } ) return nil end @@ -1208,7 +1225,17 @@ end -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - local Units = self:GetUnits() or {} + -- First try to get the 3D vector of the group. This uses + local vec3=self:GetVec3() + local coord + if vec3 then + coord=COORDINATE:NewFromVec3(vec3) + coord.Heading = self:GetHeading() or 0 + return coord + end + + -- No luck try units and add Heading data + local Units = self:GetUnits() or {} for _,_unit in pairs(Units) do local FirstUnit = _unit -- Wrapper.Unit#UNIT @@ -1218,15 +1245,15 @@ function GROUP:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate() if FirstUnitCoordinate then - local Heading = self:GetHeading() + local Heading = self:GetHeading() or 0 FirstUnitCoordinate.Heading = Heading return FirstUnitCoordinate end end end - -- no luck, try the API way + -- no luck, try the API way local DCSGroup = Group.getByName(self.GroupName) if DCSGroup then local DCSUnits = DCSGroup:getUnits() or {} @@ -1237,14 +1264,19 @@ function GROUP:GetCoordinate() if point then --self:I(point) local coord = COORDINATE:NewFromVec3(point) + coord.Heading = 0 + local munit = UNIT:Find(_unit) + if munit then + coord.Heading = munit:GetHeading() or 0 + end return coord end end end end - + BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) - + end @@ -1255,13 +1287,13 @@ end -- @usage -- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP function GROUP:GetRandomVec3(Radius) - self:F2(self.GroupName) + --self:F2(self.GroupName) local FirstUnit = self:GetUnit(1) if FirstUnit then local FirstUnitRandomPointVec3 = FirstUnit:GetRandomVec3(Radius) - self:T3(FirstUnitRandomPointVec3) + --self:T3(FirstUnitRandomPointVec3) return FirstUnitRandomPointVec3 end @@ -1274,9 +1306,9 @@ end -- @param #GROUP self -- @return #number Mean heading of the GROUP in degrees or #nil The first UNIT is not existing or alive. function GROUP:GetHeading() - self:F2(self.GroupName) + --self:F2(self.GroupName) - self:F2(self.GroupName) + --self:F2(self.GroupName) local GroupSize = self:GetSize() local HeadingAccumulator = 0 @@ -1305,7 +1337,7 @@ end -- @return #number The fuel state of the unit with the least amount of fuel. -- @return Wrapper.Unit#UNIT reference to #Unit object for further processing. function GROUP:GetFuelMin() - self:F3(self.ControllableName) + --self:F3(self.ControllableName) if not self:GetDCSObject() then BASE:E( { "Cannot GetFuel", Group = self, Alive = self:IsAlive() } ) @@ -1336,7 +1368,7 @@ end -- @return #number The relative amount of fuel (from 0.0 to 1.0). -- @return #nil The GROUP is not existing or alive. function GROUP:GetFuelAvg() - self:F( self.ControllableName ) + --self:F( self.ControllableName ) local DCSControllable = self:GetDCSObject() @@ -1346,7 +1378,7 @@ function GROUP:GetFuelAvg() for UnitID, UnitData in pairs( self:GetUnits() ) do local Unit = UnitData -- Wrapper.Unit#UNIT local UnitFuel = Unit:GetFuel() or 0 - self:F( { Fuel = UnitFuel } ) + --self:F( { Fuel = UnitFuel } ) TotalFuel = TotalFuel + UnitFuel end local GroupFuel = TotalFuel / GroupSize @@ -1376,7 +1408,7 @@ end -- @return #number Number of missiles left. -- @return #number Number of artillery shells left (with explosive mass, included in shells; shells can also be machine gun ammo) function GROUP:GetAmmunition() - self:F( self.ControllableName ) + --self:F( self.ControllableName ) local DCSControllable = self:GetDCSObject() @@ -1447,7 +1479,7 @@ end -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the Group is completely within the @{Core.Zone#ZONE_BASE} function GROUP:IsCompletelyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) + --self:F2( { self.GroupName, Zone } ) if not self:IsAlive() then return false end @@ -1467,7 +1499,7 @@ end -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the Group is partially within the @{Core.Zone#ZONE_BASE} function GROUP:IsPartlyInZone( Zone ) - self:F2( { self.GroupName, Zone } ) + --self:F2( { self.GroupName, Zone } ) local IsOneUnitInZone = false local IsOneUnitOutsideZone = false @@ -1503,7 +1535,7 @@ end -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the Group is not within the @{Core.Zone#ZONE_BASE} function GROUP:IsNotInZone( Zone ) - self:F2( { self.GroupName, Zone } ) + --self:F2( { self.GroupName, Zone } ) if not self:IsAlive() then return true end @@ -1539,7 +1571,7 @@ end -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #number The number of UNITs that are in the @{Core.Zone} function GROUP:CountInZone( Zone ) - self:F2( {self.GroupName, Zone} ) + --self:F2( {self.GroupName, Zone} ) local Count = 0 if not self:IsAlive() then return Count end @@ -1559,13 +1591,13 @@ end -- @param #GROUP self -- @return #boolean Air category evaluation result. function GROUP:IsAir() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local IsAirResult = DCSGroup:getCategory() == Group.Category.AIRPLANE or DCSGroup:getCategory() == Group.Category.HELICOPTER - self:T3( IsAirResult ) + --self:T3( IsAirResult ) return IsAirResult end @@ -1576,13 +1608,13 @@ end -- @param #GROUP self -- @return #boolean true if DCS Group contains Helicopters. function GROUP:IsHelicopter() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) + --self:T2( GroupCategory ) return GroupCategory == Group.Category.HELICOPTER end @@ -1593,13 +1625,13 @@ end -- @param #GROUP self -- @return #boolean true if DCS Group contains AirPlanes. function GROUP:IsAirPlane() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) + --self:T2( GroupCategory ) return GroupCategory == Group.Category.AIRPLANE end @@ -1610,13 +1642,13 @@ end -- @param #GROUP self -- @return #boolean true if DCS Group contains Ground troops. function GROUP:IsGround() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) + --self:T2( GroupCategory ) return GroupCategory == Group.Category.GROUND end @@ -1627,13 +1659,13 @@ end -- @param #GROUP self -- @return #boolean true if DCS Group contains Ships. function GROUP:IsShip() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() if DCSGroup then local GroupCategory = DCSGroup:getCategory() - self:T2( GroupCategory ) + --self:T2( GroupCategory ) return GroupCategory == Group.Category.SHIP end @@ -1645,7 +1677,7 @@ end -- @param #GROUP self -- @return #boolean All units on the ground result. function GROUP:AllOnGround() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() @@ -1658,7 +1690,7 @@ function GROUP:AllOnGround() end end - self:T3( AllOnGroundResult ) + --self:T3( AllOnGroundResult ) return AllOnGroundResult end @@ -1713,7 +1745,7 @@ end -- @param #GROUP self -- @return #number Maximum velocity found. function GROUP:GetMaxVelocity() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() @@ -1741,7 +1773,7 @@ end -- @param #GROUP self -- @return #number Minimum height found. function GROUP:GetMinHeight() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() @@ -1769,7 +1801,7 @@ end -- @param #GROUP self -- @return #number Maximum height found. function GROUP:GetMaxHeight() - self:F2() + --self:F2() local DCSGroup = self:GetDCSObject() @@ -1911,7 +1943,7 @@ end -- @param Core.Point#COORDINATE coordinate Coordinate where the group should be respawned. -- @return #GROUP self function GROUP:InitCoordinate(coordinate) - self:F({coordinate=coordinate}) + --self:F({coordinate=coordinate}) self.InitCoord=coordinate return self end @@ -1921,7 +1953,7 @@ end -- @param #boolean switch If true (or nil), enables the radio comms. If false, disables the radio for the spawned group. -- @return #GROUP self function GROUP:InitRadioCommsOnOff(switch) - self:F({switch=switch}) + --self:F({switch=switch}) if switch==true or switch==nil then self.InitRespawnRadio=true else @@ -1935,7 +1967,7 @@ end -- @param #number frequency The frequency in MHz. -- @return #GROUP self function GROUP:InitRadioFrequency(frequency) - self:F({frequency=frequency}) + --self:F({frequency=frequency}) self.InitRespawnFreq=frequency @@ -1947,7 +1979,7 @@ end -- @param #string modulation Either "FM" or "AM". If no value is given, modulation is set to AM. -- @return #GROUP self function GROUP:InitRadioModulation(modulation) - self:F({modulation=modulation}) + --self:F({modulation=modulation}) if modulation and modulation:lower()=="fm" then self.InitRespawnModu=radio.modulation.FM else @@ -1961,7 +1993,7 @@ end -- @param #string modex Tail number of the first unit. -- @return #GROUP self function GROUP:InitModex(modex) - self:F({modex=modex}) + --self:F({modex=modex}) if modex then self.InitRespawnModex=tonumber(modex) end @@ -1976,15 +2008,11 @@ end -- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units). -- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group. -- - @{#GROUP.InitZone}: Set the respawn @{Core.Zone} for the respawned group. --- - @{#GROUP.InitRandomizeZones}: Randomize the respawn @{Core.Zone} between one of the @{Core.Zone}s given for the respawned group. -- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}. -- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band. --- - @{#GROUP.InitRandomizeTemplates}: Randomize the Template for the respawned group. --- -- -- Notes: -- --- - When InitZone or InitRandomizeZones is not used, the position of the respawned group will be its current position. -- - The current alive group will always be destroyed and respawned using the template definition. -- -- @param Wrapper.Group#GROUP self @@ -2006,10 +2034,24 @@ function GROUP:Respawn( Template, Reset ) end return h end + + local function TransFormRoute(Template,OldPos,NewPos) + if Template.route and Template.route.points then + for _,_point in ipairs(Template.route.points) do + --self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y)) + _point.x = _point.x - OldPos.x + NewPos.x + _point.y = _point.y - OldPos.y + NewPos.y + --self:I(string.format("Point x = %f Point y = %f",_point.x,_point.y)) + end + end + return Template + end -- First check if group is alive. if self:IsAlive() then - + + local OldPos = self:GetVec2() + -- Respawn zone. local Zone = self.InitRespawnZone -- Core.Zone#ZONE @@ -2022,12 +2064,14 @@ function GROUP:Respawn( Template, Reset ) -- X, Y Template.x = Vec3.x Template.y = Vec3.z + + local NewPos = { x = Vec3.x, y = Vec3.z } --Template.x = nil --Template.y = nil -- Debug number of units. - self:F( #Template.units ) + --self:F( #Template.units ) -- Reset position etc? if Reset == true then @@ -2035,10 +2079,10 @@ function GROUP:Respawn( Template, Reset ) -- Loop over units in group. for UnitID, UnitData in pairs( self:GetUnits() ) do local GroupUnit = UnitData -- Wrapper.Unit#UNIT - self:F(GroupUnit:GetName()) + --self:F(GroupUnit:GetName()) if GroupUnit:IsAlive() then - self:I("FF Alive") + --self:I("FF Alive") -- Get unit position vector. local GroupUnitVec3 = GroupUnit:GetVec3() @@ -2049,7 +2093,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec3(From):GetRandomVec3InRadius(self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner) else GroupUnitVec3 = Zone:GetVec3() end @@ -2076,18 +2120,20 @@ function GROUP:Respawn( Template, Reset ) -- Set heading. Template.units[UnitID].heading = _Heading(self.InitRespawnHeading and self.InitRespawnHeading or GroupUnit:GetHeading()) Template.units[UnitID].psi = -Template.units[UnitID].heading - + -- Debug. - self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) + --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) end end + + Template = TransFormRoute(Template,OldPos,NewPos) elseif Reset==false then -- Reset=false or nil -- Loop over template units. for UnitID, TemplateUnitData in pairs( Template.units ) do - self:F( "Reset" ) + --self:F( "Reset" ) -- Position from template. local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y } @@ -2098,7 +2144,7 @@ function GROUP:Respawn( Template, Reset ) GroupUnitVec3 = Zone:GetRandomVec3() else if self.InitRespawnRandomizePositionInner and self.InitRespawnRandomizePositionOuter then - GroupUnitVec3 = POINT_VEC3:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) + GroupUnitVec3 = COORDINATE:NewFromVec2( From ):GetRandomPointVec3InRadius( self.InitRespawnRandomizePositionsOuter, self.InitRespawnRandomizePositionsInner ) else GroupUnitVec3 = Zone:GetVec3() end @@ -2119,11 +2165,13 @@ function GROUP:Respawn( Template, Reset ) -- Heading Template.units[UnitID].heading = self.InitRespawnHeading and self.InitRespawnHeading or TemplateUnitData.heading - + -- Debug. - self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) + --self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) end - + + Template = TransFormRoute(Template,OldPos,NewPos) + else local units=self:GetUnits() @@ -2172,10 +2220,11 @@ function GROUP:Respawn( Template, Reset ) -- Destroy old group. Dont trigger any dead/crash events since this is a respawn. self:Destroy(false) - self:T({Template=Template}) + --UTILS.PrintTableToLog(Template) -- Spawn new group. - _DATABASE:Spawn(Template) + self:ScheduleOnce(0.1,_DATABASE.Spawn,_DATABASE,Template) + --_DATABASE:Spawn(Template) -- Reset events. self:ResetEvents() @@ -2183,6 +2232,29 @@ function GROUP:Respawn( Template, Reset ) return self end +--- Respawn the @{Wrapper.Group} at a @{Core.Point#COORDINATE}. +-- The method will setup the new group template according the Init(Respawn) settings provided for the group. +-- These settings can be provided by calling the relevant Init...() methods of the Group prior. +-- +-- - @{#GROUP.InitHeading}: Set the heading for the units in degrees within the respawned group. +-- - @{#GROUP.InitHeight}: Set the height for the units in meters for the respawned group. (This is applicable for air units). +-- - @{#GROUP.InitRandomizeHeading}: Randomize the headings for the units within the respawned group. +-- - @{#GROUP.InitRandomizePositionZone}: Randomize the positions of the units of the respawned group within the @{Core.Zone}. +-- - @{#GROUP.InitRandomizePositionRadius}: Randomize the positions of the units of the respawned group in a circle band. +-- +-- Notes: +-- +-- - When no coordinate is given, the position of the respawned group will be its current position. +-- - The current alive group will always be destroyed first. +-- - The new group will have all of its original units and health restored. +-- +-- @param Wrapper.Group#GROUP self +-- @param Core.Point#COORDINATE Coordinate Where to respawn the group. Can be handed as a @{Core.Zone#ZONE_BASE} object. +-- @return Wrapper.Group#GROUP self +function GROUP:Teleport(Coordinate) + self:InitZone(Coordinate) + return self:Respawn(nil,false) +end --- Respawn a group at an airbase. -- Note that the group has to be on parking spots at the airbase already in order for this to work. @@ -2193,7 +2265,7 @@ end -- @param #boolean Uncontrolled (Optional) If true, spawn in uncontrolled state. -- @return Wrapper.Group#GROUP Group spawned at airbase or nil if group could not be spawned. function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- R2.4 - self:F2( { SpawnTemplate, Takeoff, Uncontrolled} ) + --self:F2( { SpawnTemplate, Takeoff, Uncontrolled} ) if self and self:IsAlive() then @@ -2201,7 +2273,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- local airbase=self:GetCoordinate():GetClosestAirbase() if airbase then - self:F2("Closest airbase = "..airbase:GetName()) + --self:F2("Closest airbase = "..airbase:GetName()) else self:E("ERROR: could not find closest airbase!") return nil @@ -2252,7 +2324,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- local Parkingspot, TermialID, Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) --Parkingspot:MarkToAll("parking spot") - self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID))) + --self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID))) -- Get unit coordinates for respawning position. local uc=unit:GetCoordinate() @@ -2314,7 +2386,7 @@ end -- @param #GROUP self -- @return #table The MissionTemplate function GROUP:GetTaskMission() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) end @@ -2323,9 +2395,12 @@ end -- @param #GROUP self -- @return #table The mission route defined by points. function GROUP:GetTaskRoute() - self:F2( self.GroupName ) - - return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) + --self:F2( self.GroupName ) + if _DATABASE.Templates.Groups[self.GroupName].Template and _DATABASE.Templates.Groups[self.GroupName].Template.route and _DATABASE.Templates.Groups[self.GroupName].Template.route.points then + return UTILS.DeepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) + else + return {} + end end --- Return the route of a group by using the global _DATABASE object (an instance of @{Core.Database#DATABASE}). @@ -2335,7 +2410,7 @@ end -- @param #boolean Randomize Randomization of the route, when true. -- @param #number Radius When randomization is on, the randomization is within the radius. function GROUP:CopyRoute( Begin, End, Randomize, Radius ) - self:F2( { Begin, End } ) + --self:F2( { Begin, End } ) local Points = {} @@ -2347,7 +2422,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius ) GroupName = self:GetName() end - self:T3( { GroupName } ) + --self:T3( { GroupName } ) local Template = _DATABASE.Templates.Groups[GroupName].Template @@ -2393,7 +2468,7 @@ function GROUP:CalculateThreatLevelA2G() end end - self:T3( MaxThreatLevelA2G ) + --self:T3( MaxThreatLevelA2G ) return MaxThreatLevelA2G end @@ -2420,7 +2495,7 @@ end -- @param Wrapper.Group#GROUP self -- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. function GROUP:InAir() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() @@ -2428,7 +2503,7 @@ function GROUP:InAir() local DCSUnit = DCSGroup:getUnit(1) if DCSUnit then local GroupInAir = DCSGroup:getUnit(1):inAir() - self:T3( GroupInAir ) + --self:T3( GroupInAir ) return GroupInAir end end @@ -2441,7 +2516,7 @@ end -- @param #boolean AllUnits (Optional) If true, check whether all units of the group are airborne. -- @return #boolean True if at least one (optionally all) unit(s) is(are) airborne or false otherwise. Nil if no unit exists or is alive. function GROUP:IsAirborne(AllUnits) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) -- Get all units of the group. local units=self:GetUnits() @@ -2652,7 +2727,7 @@ do -- Route methods -- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected. -- @return #GROUP self function GROUP:RouteRTB( RTBAirbase, Speed ) - self:F( { RTBAirbase:GetName(), Speed } ) + --self:F( { RTBAirbase:GetName(), Speed } ) local DCSGroup = self:GetDCSObject() @@ -2678,7 +2753,7 @@ do -- Route methods --local Points={PointFrom, PointAirbase, PointLanding} -- Debug info. - self:T3(Points) + --self:T3(Points) -- Get group template. local Template=self:GetTemplate() @@ -2741,7 +2816,7 @@ do -- Event Handling self:EventDispatcher():Reset( self ) - for UnitID, UnitData in pairs( self:GetUnits() ) do + for UnitID, UnitData in pairs( self:GetUnits() or {}) do UnitData:ResetEvents() end @@ -2763,7 +2838,7 @@ do -- Players local PlayerNames = {} local Units = self:GetUnits() - for UnitID, UnitData in pairs( Units ) do + for UnitID, UnitData in pairs( Units or {}) do local Unit = UnitData -- Wrapper.Unit#UNIT local PlayerName = Unit:GetPlayerName() if PlayerName and PlayerName ~= "" then @@ -2774,7 +2849,7 @@ do -- Players end if HasPlayers == true then - self:F2( PlayerNames ) + --self:F2( PlayerNames ) return PlayerNames end @@ -2808,7 +2883,7 @@ end -- @param #boolean switch If true, emission is enabled. If false, emission is disabled. -- @return #GROUP self function GROUP:EnableEmission(switch) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local switch = switch or false local DCSUnit = self:GetDCSObject() @@ -2835,7 +2910,7 @@ end -- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled. -- @return #GROUP self function GROUP:CommandSetInvisible(switch) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) if switch==nil then switch=false end @@ -2857,7 +2932,7 @@ end -- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled. -- @return #GROUP self function GROUP:CommandSetImmortal(switch) - self:F2( self.GroupName ) + --self:F2( self.GroupName ) if switch==nil then switch=false end @@ -2870,7 +2945,7 @@ end -- @param #GROUP self -- @return #string Skill String of skill name. function GROUP:GetSkill() - self:F2( self.GroupName ) + --self:F2( self.GroupName ) local unit = self:GetUnit(1) local name = unit:GetName() local skill = _DATABASE.Templates.Units[name].Template.skill or "Random" @@ -2917,8 +2992,10 @@ end -- @param #GROUP self -- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1" -- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns --- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set +-- @param #table CallsignTranslations (Optional) Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set -- callsigns from playername or group name. +-- @param #func CustomFunction (Optional) For player names only(!). If given, this function will return the callsign. Needs to take the groupname and the playername as first arguments. +-- @param #arg ... (Optional) Comma separated arguments to add to the CustomFunction call after groupname and playername. -- @return #string Callsign -- @usage -- -- suppose there are three groups with one (client) unit each: @@ -2939,9 +3016,13 @@ end -- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set -- -- Apollo for Slot 3 -- -- Bengal-4 for Slot 4 - -function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) - --self:I("GetCustomCallSign") +-- +-- -- Using a custom function (for player units **only**): +-- -- Imagine your playernames are looking like so: "[Squadname] | Cpt Apple" and you only want to have the last word as callsign, i.e. "Apple" here. Then this custom function will return this: +-- local callsign = mygroup:GetCustomCallSign(true,false,nil,function(groupname,playername) return string.match(playername,"([%a]+)$") end) +-- +function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations,CustomFunction,...) + self:T("GetCustomCallSign") local callsign = "Ghost 1" if self:IsAlive() then @@ -2954,7 +3035,18 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9 local callnumberminor = string.char(string.byte(callnumber,2)) -- 1 local personalized = false - + --local playername = IsPlayer == true and self:GetPlayerName() or shortcallsign + local playername = shortcallsign + + if IsPlayer then playername = self:GetPlayerName() end + + self:T2("GetCustomCallSign outcome = "..playername) + if CustomFunction and IsPlayer then + local arguments = arg or {} + local callsign = CustomFunction(groupname,playername,unpack(arguments)) + return callsign + end + -- prioritize bespoke callsigns over parsing, prefer parsing over default callsigns if CallsignTranslations and CallsignTranslations[callsignroot] then callsignroot = CallsignTranslations[callsignroot] @@ -2966,9 +3058,9 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) shortcallsign = string.match(groupname,"#%s*([%a]+)") or "Ghost" -- Ghostrider end personalized = true - elseif IsPlayer and string.find(self:GetPlayerName(),"|") then + elseif IsPlayer and string.find(playername,"|") then -- personalized flight name in group naming - shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider + shortcallsign = string.match(playername,"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider personalized = true end @@ -3068,7 +3160,7 @@ function GROUP:IsSAM() local units = self:GetUnits() for _,_unit in pairs(units or {}) do local unit = _unit -- Wrapper.Unit#UNIT - if unit:HasSEAD() and unit:IsGround() and (not unit:HasAttribute("Mobile AAA")) then + if unit:IsSAM() then issam = true break end @@ -3078,18 +3170,16 @@ end --- [GROUND] Determine if a GROUP has a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. -- @param #GROUP self --- @return #boolean IsSAM True if AAA, else false +-- @return #boolean IsAAA True if AAA, else false function GROUP:IsAAA() - local issam = false + local isAAA = false local units = self:GetUnits() for _,_unit in pairs(units or {}) do local unit = _unit -- Wrapper.Unit#UNIT - local desc = unit:GetDesc() or {} - local attr = desc.attributes or {} - if unit:HasSEAD() then return false end - if attr["AAA"] or attr["SAM related"] then - issam = true + if unit:IsAAA() then + isAAA = true + break end end - return issam + return isAAA end diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 1710289d7..8561d5b2b 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -11,7 +11,8 @@ -- @module Wrapper.Identifiable -- @image MOOSE.JPG ---- @type IDENTIFIABLE +--- +-- @type IDENTIFIABLE -- @extends Wrapper.Object#OBJECT -- @field #string IdentifiableName The name of the identifiable. @@ -111,19 +112,28 @@ end -- * Object.Category.SCENERY = 5 -- * Object.Category.Cargo = 6 -- +-- For UNITs this returns a second value, one of +-- +-- Unit.Category.AIRPLANE = 0 +-- Unit.Category.HELICOPTER = 1 +-- Unit.Category.GROUND_UNIT = 2 +-- Unit.Category.SHIP = 3 +-- Unit.Category.STRUCTURE = 4 +-- -- @param #IDENTIFIABLE self -- @return DCS#Object.Category The category ID, i.e. a number. +-- @return DCS#Unit.Category The unit category ID, i.e. a number. For units only. function IDENTIFIABLE:GetCategory() self:F2( self.ObjectName ) local DCSObject = self:GetDCSObject() if DCSObject then - local ObjectCategory = DCSObject:getCategory() + local ObjectCategory, UnitCategory = DCSObject:getCategory() self:T3( ObjectCategory ) - return ObjectCategory + return ObjectCategory, UnitCategory end - return nil + return nil,nil end diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua index af3863fa8..716954717 100644 --- a/Moose Development/Moose/Wrapper/Net.lua +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -43,7 +43,7 @@ do -- @field #NET NET = { ClassName = "NET", - Version = "0.1.3", + Version = "0.1.4", BlockTime = 600, BlockedPilots = {}, BlockedUCIDs = {}, @@ -67,6 +67,9 @@ function NET:New() self.KnownPilots = {} self:SetBlockMessage() self:SetUnblockMessage() + self.BlockedSides = {} + self.BlockedSides[1] = false + self.BlockedSides[2] = false -- Start State. self:SetStartState("Stopped") @@ -160,11 +163,12 @@ end -- @param #string PlayerSlot -- @return #boolean IsBlocked function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) + self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot}) local blocked = false local TNow = timer.getTime() -- UCID if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then - return true + blocked = true end -- ID/Name if PlayerID and not Name then @@ -172,16 +176,18 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot) end -- Name if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then - return true + blocked = true end -- Side - if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then - return true + self:T({time = self.BlockedSides[PlayerSide]}) + if PlayerSide and type(self.BlockedSides[PlayerSide]) == "number" and TNow < self.BlockedSides[PlayerSide] then + blocked = true end -- Slot if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then - return true + blocked = true end + self:T("IsAnyBlocked: "..tostring(blocked)) return blocked end @@ -200,19 +206,27 @@ function NET:_EventHandler(EventData) local ucid = self:GetPlayerUCID(nil,name) or "none" local PlayerID = self:GetPlayerIDByName(name) or "none" local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit) + if not PlayerSide then PlayerSide = EventData.IniCoalition end + if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() or -1 end local TNow = timer.getTime() - self:T(self.lid.."Event for: "..name.." | UCID: "..ucid) + --self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot) -- Joining if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id) -- Check for blockages local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot) - - if blocked and PlayerID and tonumber(PlayerID) ~= 1 then + if blocked and PlayerID then -- and tonumber(PlayerID) ~= 1 then + self:T("Player blocked") -- block pilot - local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' ) + local outcome = net.force_player_slot(tonumber(PlayerID), PlayerSide, data.IniUnit:GetID() ) + self:T({Blocked_worked=outcome}) + if outcome == false then + local unit = data.IniUnit + local sched = TIMER:New(unit.Destroy,unit,3):Start(3) + self:__PlayerBlocked(5,unit,name,1) + end else local client = CLIENT:FindByPlayerName(name) or data.IniUnit if not self.KnownPilots[name] or (self.KnownPilots[name] and TNow-self.KnownPilots[name].timestamp > 3) then @@ -225,6 +239,7 @@ function NET:_EventHandler(EventData) slot = PlayerSlot, timestamp = TNow, } + --UTILS.PrintTableToLog(self.KnownPilots[name]) end return self end @@ -350,11 +365,10 @@ end --- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players -- @param #NET self --- @param #number side The side to block - 1 : Red, 2 : Blue +-- @param #number Side The side to block - 1 : Red, 2 : Blue -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining. -- @return #NET self function NET:BlockSide(Side,Seconds) - self:T({Side,Seconds}) local addon = Seconds or self.BlockTime if Side == 1 or Side == 2 then self.BlockedSides[Side] = timer.getTime()+addon @@ -367,10 +381,9 @@ end -- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining. -- @return #NET self function NET:UnblockSide(Side,Seconds) - self:T({Side,Seconds}) local addon = Seconds or self.BlockTime if Side == 1 or Side == 2 then - self.BlockedSides[Side] = nil + self.BlockedSides[Side] = false end return self end @@ -485,8 +498,11 @@ end -- @param Wrapper.Client#CLIENT Client The client -- @return #number PlayerID or nil function NET:GetPlayerIDFromClient(Client) + self:T("GetPlayerIDFromClient") + self:T({Client=Client}) if Client then local name = Client:GetPlayerName() + self:T({name=name}) local id = self:GetPlayerIDByName(name) return id else @@ -528,6 +544,7 @@ function NET:SendChatToPlayer(Message, ToPlayer, FromPlayer) return self end +--[[ not in 2.97 MSE any longer --- Load a specific mission. -- @param #NET self -- @param #string Path and Mission @@ -550,6 +567,7 @@ function NET:LoadNextMission() outcome = net.load_next_mission() return outcome end +--]] --- Return a table of players currently connected to the server. -- @param #NET self @@ -680,16 +698,19 @@ end -- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue -- @return #number SlotID function NET:GetSlot(Client) + self:T("NET.GetSlot") local PlayerID = self:GetPlayerIDFromClient(Client) + self:T("NET.GetSlot PlayerID = "..tostring(PlayerID)) if PlayerID then local side,slot = net.get_slot(tonumber(PlayerID)) + self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot)) return side,slot else return nil,nil end end ---- Force the slot for a specific client. +--- Force the slot for a specific client. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)! -- @param #NET self -- @param Wrapper.Client#CLIENT Client The client -- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue @@ -697,19 +718,22 @@ end -- @return #boolean Success function NET:ForceSlot(Client,SideID,SlotID) local PlayerID = self:GetPlayerIDFromClient(Client) - if PlayerID and tonumber(PlayerID) ~= 1 then - return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' ) + local SlotID = SlotID or Client:GetID() + if PlayerID then -- and tonumber(PlayerID) ~= 1 then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) else return false end end ---- Force a client back to spectators. +--- Force a client back to spectators. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)! -- @param #NET self -- @param Wrapper.Client#CLIENT Client The client -- @return #boolean Succes function NET:ReturnToSpectators(Client) local outcome = self:ForceSlot(Client,0) + -- workaround + local sched = TIMER:New(Client.Destroy,Client,1):Start(1) return outcome end @@ -779,7 +803,7 @@ function NET:onafterStatus(From,Event,To) local function HouseHold(tavolo) local TNow = timer.getTime() for _,entry in pairs (tavolo) do - if entry >= TNow then entry = nil end + if type(entry) == "number" and entry >= TNow then entry = false end end end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index f181cbd23..41857a215 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -16,7 +16,7 @@ --- @type POSITIONABLE -- @field Core.Point#COORDINATE coordinate Coordinate object. --- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. +-- @field Core.Point#COORDINATE pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent ) if GenerateEvent and GenerateEvent == true then if self:IsAir() then + --self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject) self:CreateEventCrash( timer.getTime(), DCSObject ) else + --self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject) self:CreateEventDead( timer.getTime(), DCSObject ) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) + --self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject) end USERFLAG:New( UnitGroupName ):Set( 100 ) @@ -142,7 +145,11 @@ function POSITIONABLE:GetPosition() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - + + if self:IsInstanceOf("GROUP") then + DCSPositionable = self:GetFirstUnitAlive():GetDCSObject() + end + if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() self:T3( PositionablePosition ) @@ -277,9 +284,9 @@ function POSITIONABLE:GetVec2() return nil end ---- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) @@ -289,20 +296,20 @@ function POSITIONABLE:GetPointVec2() if DCSPositionable then local PositionableVec3 = DCSPositionable:getPosition().p - local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) + local PositionablePointVec2 = COORDINATE:NewFromVec3( PositionableVec3 ) -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end - self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) + self:E( { "Cannot Coordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end ---- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. +--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self --- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. +-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() @@ -322,8 +329,8 @@ function POSITIONABLE:GetPointVec3() else - -- Create a new POINT_VEC3 object. - self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) + -- Create a new COORDINATE object. + self.pointvec3 = COORDINATE:NewFromVec3( PositionableVec3 ) end @@ -671,7 +678,7 @@ function POSITIONABLE:GetBoundingRadius( MinDist ) return math.max( math.max( CX, CZ ), boxmin ) end - BASE:E( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } ) + BASE:T( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } ) return nil end @@ -1853,6 +1860,7 @@ do -- Cargo ["HL_KORD"] = 6*POSITIONABLE.DefaultInfantryWeight, ["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers, + ["MaxxPro_MRAP"] = 7*POSITIONABLE.DefaultInfantryWeight, } } diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index c643d1a88..9c6b02516 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -1,13 +1,13 @@ --- **Wrapper** - SCENERY models scenery within the DCS simulator. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- ### Contributions: **Applevangelist**, **funkyfranky** --- +-- -- === --- +-- -- @module Wrapper.Scenery -- @image Wrapper_Scenery.JPG @@ -23,13 +23,13 @@ --- Wrapper class to handle Scenery objects that are defined on the map. --- +-- -- The @{Wrapper.Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects: --- +-- -- * Wraps the DCS Scenery objects. -- * Support all DCS Scenery APIs. -- * Enhance with Scenery specific APIs not in the DCS API set. --- +-- -- @field #SCENERY SCENERY = { ClassName = "SCENERY", @@ -43,23 +43,23 @@ SCENERY = { function SCENERY:Register( SceneryName, SceneryObject ) local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) ) - + self.SceneryName = tostring(SceneryName) - + self.SceneryObject = SceneryObject - - if self.SceneryObject then - self.Life0 = self.SceneryObject:getLife() + + if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions + self.Life0 = self.SceneryObject:getLife() or 0 else self.Life0 = 0 end - + self.Properties = {} - + return self end ---- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. +--- Returns the value of the scenery with the given PropertyName, or nil if no matching property exists. -- @param #SCENERY self -- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved. -- @return #string The Value of the QuadZone Property from the scenery assignment with the given PropertyName, or nil if absent. @@ -67,6 +67,14 @@ function SCENERY:GetProperty(PropertyName) return self.Properties[PropertyName] end +--- Checks if the value of the scenery with the given PropertyName exists. +-- @param #SCENERY self +-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved. +-- @return #boolean Outcome True if it exists, else false. +function SCENERY:HasProperty(PropertyName) + return self.Properties[PropertyName] ~= nil and true or false +end + --- Returns the scenery Properties table. -- @param #SCENERY self -- @return #table The Key:Value table of QuadZone properties of the zone from the scenery assignment . @@ -83,6 +91,7 @@ function SCENERY:SetProperty(PropertyName, PropertyValue) self.Properties[PropertyName] = PropertyValue return self end + --- Obtain object name. --@param #SCENERY self --@return #string Name @@ -97,15 +106,15 @@ function SCENERY:GetDCSObject() return self.SceneryObject end ---- Get current life points from the SCENERY Object. --- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120% --- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success +--- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points. +-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120% +-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success -- criteria for a bombing task. --@param #SCENERY self --@return #number life function SCENERY:GetLife() local life = 0 - if self.SceneryObject then + if self.SceneryObject and self.SceneryObject.getLife then life = self.SceneryObject:getLife() if life > self.Life0 then self.Life0 = math.floor(life * 1.2) @@ -121,7 +130,7 @@ function SCENERY:GetLife0() return self.Life0 or 0 end ---- Check if SCENERY Object is alive. +--- Check if SCENERY Object is alive. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100). --@return #number life @@ -133,7 +142,7 @@ function SCENERY:IsAlive(Threshold) end end ---- Check if SCENERY Object is dead. +--- Check if SCENERY Object is dead. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100). --@return #number life @@ -145,12 +154,13 @@ function SCENERY:IsDead(Threshold) end end ---- Get SCENERY relative life in percent, e.g. 75. +--- Get SCENERY relative life in percent, e.g. 75. Note - Some scenery objects always have 0 life points. --@param #SCENERY self --@return #number rlife function SCENERY:GetRelativeLife() local life = self:GetLife() local life0 = self:GetLife0() + if life == 0 or life0 == 0 then return 0 end local rlife = math.floor((life/life0)*100) return rlife end @@ -175,7 +185,7 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role) local radius = Radius or 100 local name = Name or "unknown" local scenery = nil - + --- -- @param Core.Point#COORDINATE coordinate -- @param #number radius @@ -199,13 +209,13 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role) end return nil end - + if Coordinate then --BASE:I("Coordinate Scenery Scan") scenery = SceneryScan(Coordinate, radius, name) end - return scenery + return scenery end --- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first @@ -215,7 +225,7 @@ end --@param Core.Zone#ZONE_BASE Zone Where to find the scenery object. Can be handed as zone name. --@param #number Radius (optional) Search radius around coordinate, defaults to 100 --@return #SCENERY Scenery Object or `nil` if it cannot be found -function SCENERY:FindByNameInZone(Name, Zone, Radius) +function SCENERY:FindByNameInZone(Name, Zone, Radius) local radius = Radius or 100 local name = Name or "unknown" if type(Zone) == "string" then @@ -270,7 +280,7 @@ end function SCENERY:FindAllByZoneName( ZoneName ) local zone = ZoneName -- Core.Zone#ZONE_RADIUS if type(ZoneName) == "string" then - zone = ZONE:FindByName(ZoneName) + zone = ZONE:FindByName(ZoneName) end local _id = zone:GetProperty('OBJECT ID') --local properties = zone:GetAllProperties() or {} @@ -290,7 +300,7 @@ function SCENERY:FindAllByZoneName( ZoneName ) return {obj} else return nil - end + end end end diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 916b78a84..7c2436641 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -61,6 +61,8 @@ function STATIC:Register( StaticName ) if DCSStatic then local Life0 = DCSStatic:getLife() or 1 self.Life0 = Life0 + else + self:E(string.format("Static object %s does not exist!", tostring(self.StaticName))) end return self @@ -178,7 +180,7 @@ end -- @param #STATIC self -- @return DCS static object function STATIC:GetDCSObject() - local DCSStatic = StaticObject.getByName( self.StaticName ) + local DCSStatic = StaticObject.getByName( self.StaticName ) if DCSStatic then return DCSStatic @@ -330,3 +332,26 @@ function STATIC:FindAllByMatching( Pattern ) return GroupsFound end + +--- Get the Wrapper.Storage#STORAGE object of an static if it is used as cargo and has been set up as storage object. +-- @param #STATIC self +-- @return Wrapper.Storage#STORAGE Storage or `nil` if not fund or set up. +function STATIC:GetStaticStorage() + local name = self:GetName() + local storage = STORAGE:NewFromStaticCargo(name) + return storage +end + +--- Get the Cargo Weight of a static object in kgs. Returns -1 if not found. +-- @param #STATIC self +-- @return #number Mass Weight in kgs. +function STATIC:GetCargoWeight() + local DCSObject = StaticObject.getByName(self.StaticName ) + local mass = -1 + if DCSObject then + mass = DCSObject:getCargoWeight() or 0 + local masstxt = DCSObject:getCargoDisplayName() or "none" + --BASE:I("GetCargoWeight "..tostring(mass).." MassText "..masstxt) + end + return mass +end diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 04210136a..4b445613c 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -26,6 +26,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field DCS#Warehouse warehouse The DCS warehouse object. -- @field DCS#Airbase airbase The DCS airbase object. +-- @field Core.Timer#TIMER SaverTimer The TIMER for autosave. -- @extends Core.Base#BASE --- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx @@ -124,6 +125,39 @@ -- UTILS.PrintTableToLog(liquids) -- UTILS.PrintTableToLog(weapons) -- +-- # Weapons Helper Enumerater +-- +-- The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`. +-- +-- # Persistence +-- +-- The contents of the storage can be saved to and read from disk. For this to function, `io` and `lfs` need to be desanitized in `MissionScripting.lua`. +-- +-- ## Save once +-- +-- ### To save once, e.g. this is sufficient: +-- +-- -- Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be saved. +-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\" +-- local Filename = "Batumi" +-- storage:SaveToFile(Path,Filename) +-- +-- ### Autosave +-- +-- storage:StartAutoSave(Path,Filename,300,true) -- save every 300 secs/5 mins starting in 5 mins, load the existing storage - if any - first if the last parameter is **not** `false`. +-- +-- ### Stop Autosave +-- +-- storage:StopAutoSave() -- stop the scheduler. +-- +-- ### Load back with e.g. +-- +-- -- Filenames searched for the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be loaded. +-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\" +-- local Filename = "Batumi" +-- storage:LoadFromFile(Path,Filename) +-- +-- -- @field #STORAGE STORAGE = { ClassName = "STORAGE", @@ -143,22 +177,46 @@ STORAGE.Liquid = { DIESEL = 3, } +--- Liquid Names for the static cargo resource table. +-- @type STORAGE.LiquidName +-- @field #number JETFUEL "jet_fuel". +-- @field #number GASOLINE "gasoline". +-- @field #number MW50 "methanol_mixture". +-- @field #number DIESEL "diesel". +STORAGE.LiquidName = { + GASOLINE = "gasoline", + DIESEL = "diesel", + MW50 = "methanol_mixture", + JETFUEL = "jet_fuel", +} + +--- Storage types. +-- @type STORAGE.Type +-- @field #number WEAPONS weapons. +-- @field #number LIQUIDS liquids. Also see #list<#STORAGE.Liquid> for types of liquids. +-- @field #number AIRCRAFT aircraft. +STORAGE.Type = { + WEAPONS = "weapons", + LIQUIDS = "liquids", + AIRCRAFT = "aircrafts", +} + --- STORAGE class version. -- @field #string version -STORAGE.version="0.0.1" +STORAGE.version="0.1.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: A lot... --- TODO: Persistence +-- DONE: Persistence ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new STORAGE object from the DCS weapon object. +--- Create a new STORAGE object from the DCS airbase object. -- @param #STORAGE self -- @param #string AirbaseName Name of the airbase. -- @return #STORAGE self @@ -169,17 +227,57 @@ function STORAGE:New(AirbaseName) self.airbase=Airbase.getByName(AirbaseName) - if Airbase.getWarehouse then + if Airbase.getWarehouse and self.airbase then self.warehouse=self.airbase:getWarehouse() end - self.lid = string.format("STORAGE %s", AirbaseName) + self.lid = string.format("STORAGE %s | ", AirbaseName) + + return self +end + +--- Create a new STORAGE object from an DCS static cargo object. +-- @param #STORAGE self +-- @param #string StaticCargoName Unit name of the static. +-- @return #STORAGE self +function STORAGE:NewFromStaticCargo(StaticCargoName) + + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #STORAGE + + self.airbase=StaticObject.getByName(StaticCargoName) + + if Airbase.getWarehouse then + self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) + end + + self.lid = string.format("STORAGE %s | ", StaticCargoName) + + return self +end + +--- Create a new STORAGE object from a Wrapper.DynamicCargo#DYNAMICCARGO object. +-- @param #STORAGE self +-- @param #string DynamicCargoName Unit name of the dynamic cargo. +-- @return #STORAGE self +function STORAGE:NewFromDynamicCargo(DynamicCargoName) + + -- Inherit everything from BASE class. + local self=BASE:Inherit(self, BASE:New()) -- #STORAGE + + self.airbase=Unit.getByName(DynamicCargoName) or StaticObject.getByName(DynamicCargoName) + + if Airbase.getWarehouse then + self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase) + end + + self.lid = string.format("STORAGE %s | ", DynamicCargoName) return self end ---- Find a STORAGE in the **_DATABASE** using the name associated airbase. +--- Airbases only - Find a STORAGE in the **_DATABASE** using the name associated airbase. -- @param #STORAGE self -- @param #string AirbaseName The Airbase Name. -- @return #STORAGE self @@ -198,6 +296,10 @@ end -- @return #STORAGE self function STORAGE:SetVerbosity(VerbosityLevel) self.verbose=VerbosityLevel or 0 + if self.verbose > 1 then + BASE:TraceOn() + BASE:TraceClass("STORAGE") + end return self end @@ -406,7 +508,7 @@ end --- Returns whether a given type of aircraft, liquid, weapon is set to be unlimited. -- @param #STORAGE self -- @param #string Type Name of aircraft, weapon or equipment or type of liquid (as `#number`). --- @return #boolen If `true` the given type is unlimited or `false` otherwise. +-- @return #boolean If `true` the given type is unlimited or `false` otherwise. function STORAGE:IsUnlimited(Type) -- Get current amount of type. @@ -423,7 +525,7 @@ function STORAGE:IsUnlimited(Type) local n=self:GetAmount(Type) -- If amount did not change, it is unlimited. - unlimited=n==N + unlimited=unlimited or n > 2^29 or n==N -- Add item back. if not unlimited then @@ -431,7 +533,7 @@ function STORAGE:IsUnlimited(Type) end -- Debug info. - self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) + self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) end return unlimited @@ -440,7 +542,7 @@ end --- Returns whether a given type of aircraft, liquid, weapon is set to be limited. -- @param #STORAGE self -- @param #number Type Type of liquid or name of aircraft, weapon or equipment. --- @return #boolen If `true` the given type is limited or `false` otherwise. +-- @return #boolean If `true` the given type is limited or `false` otherwise. function STORAGE:IsLimited(Type) local limited=not self:IsUnlimited(Type) @@ -450,7 +552,7 @@ end --- Returns whether aircraft are unlimited. -- @param #STORAGE self --- @return #boolen If `true` aircraft are unlimited or `false` otherwise. +-- @return #boolean If `true` aircraft are unlimited or `false` otherwise. function STORAGE:IsUnlimitedAircraft() -- We test with a specific type but if it is unlimited, than all aircraft are. @@ -461,7 +563,7 @@ end --- Returns whether liquids are unlimited. -- @param #STORAGE self --- @return #boolen If `true` liquids are unlimited or `false` otherwise. +-- @return #boolean If `true` liquids are unlimited or `false` otherwise. function STORAGE:IsUnlimitedLiquids() -- We test with a specific type but if it is unlimited, than all are. @@ -472,7 +574,7 @@ end --- Returns whether weapons and equipment are unlimited. -- @param #STORAGE self --- @return #boolen If `true` weapons and equipment are unlimited or `false` otherwise. +-- @return #boolean If `true` weapons and equipment are unlimited or `false` otherwise. function STORAGE:IsUnlimitedWeapons() -- We test with a specific type but if it is unlimited, than all are. @@ -483,7 +585,7 @@ end --- Returns whether aircraft are limited. -- @param #STORAGE self --- @return #boolen If `true` aircraft are limited or `false` otherwise. +-- @return #boolean If `true` aircraft are limited or `false` otherwise. function STORAGE:IsLimitedAircraft() -- We test with a specific type but if it is limited, than all are. @@ -494,7 +596,7 @@ end --- Returns whether liquids are limited. -- @param #STORAGE self --- @return #boolen If `true` liquids are limited or `false` otherwise. +-- @return #boolean If `true` liquids are limited or `false` otherwise. function STORAGE:IsLimitedLiquids() -- We test with a specific type but if it is limited, than all are. @@ -505,7 +607,7 @@ end --- Returns whether weapons and equipment are limited. -- @param #STORAGE self --- @return #boolen If `true` liquids are limited or `false` otherwise. +-- @return #boolean If `true` liquids are limited or `false` otherwise. function STORAGE:IsLimitedWeapons() -- We test with a specific type but if it is limited, than all are. @@ -527,6 +629,247 @@ function STORAGE:GetInventory(Item) return inventory.aircraft, inventory.liquids, inventory.weapon end +--- Save the contents of a STORAGE to files in CSV format. Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working. +-- @param #STORAGE self +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The base name of the files. Existing files will be overwritten. +-- @return #STORAGE self +function STORAGE:SaveToFile(Path,Filename) + + if not io then + BASE:E("ERROR: io not desanitized. Can't save the files.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.") + end + + local ac, lq, wp = self:GetInventory() + local DataAircraft = "" + local DataLiquids = "" + local DataWeapons = "" + + if #lq > 0 then + DataLiquids = DataLiquids .."Liquids in Storage:\n" + for key,amount in pairs(lq) do + DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n" + end + UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids) + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") + end + end + + if UTILS.TableLength(ac) > 0 then + DataAircraft = DataAircraft .."Aircraft in Storage:\n" + for key,amount in pairs(ac) do + DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n" + end + UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft) + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") + end + end + + if UTILS.TableLength(wp) > 0 then + DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n" + + for _,_category in pairs(ENUMS.Storage.weapons) do + for _,_key in pairs(_category) do + local amount = self:GetAmount(_key) + if type(_key) == "table" then + _key = "{"..table.concat(_key,",").."}" + end + DataWeapons = DataWeapons..tostring(_key).."="..tostring(amount).."\n" + end + end + + -- Gazelle table keys + for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n" + end + -- CH47 + for key,amount in pairs(ENUMS.Storage.weapons.CH47) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.CH47[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n" + end + -- UH1H + for key,amount in pairs(ENUMS.Storage.weapons.UH1H) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n" + end + -- OH58D + for key,amount in pairs(ENUMS.Storage.weapons.OH58) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.OH58[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n" + end + -- AH64D + for key,amount in pairs(ENUMS.Storage.weapons.AH64D) do + amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key]) + DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n" + end + UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons) + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") + end + end + + return self +end + +--- Load the contents of a STORAGE from files. Filenames searched for are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working. +-- @param #STORAGE self +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @return #STORAGE self +function STORAGE:LoadFromFile(Path,Filename) + + if not io then + BASE:E("ERROR: io not desanitized. Can't read the files.") + return false + end + + -- Check default path. + if Path==nil and not lfs then + BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.") + end + + --Liquids + if self:IsLimitedLiquids() then + local Ok,Liquids = UTILS.LoadFromFile(Path,Filename.."_Liquids.csv") + if Ok then + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv") + end + for _id,_line in pairs(Liquids) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local lqno = tonumber(tbl[1]) + local lqam = tonumber(tbl[2]) + self:SetLiquid(lqno,lqam) + end + end + else + self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv")) + end + end + + --Aircraft + if self:IsLimitedAircraft() then + local Ok,Aircraft = UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv") + if Ok then + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv") + end + for _id,_line in pairs(Aircraft) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local acname = tbl[1] + local acnumber = tonumber(tbl[2]) + self:SetAmount(acname,acnumber) + end + end + else + self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv")) + end + end + + --Weapons + if self:IsLimitedWeapons() then + local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv") + if Ok then + if self.verbose and self.verbose > 0 then + self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv") + end + for _id,_line in pairs(Weapons) do + if string.find(_line,"Storage") == nil then + local tbl=UTILS.Split(_line,"=") + local wpname = tbl[1] + local wpnumber = tonumber(tbl[2]) + if string.find(wpname,"{") == 1 then + --self:I("Found a table: "..wpname) + wpname = string.gsub(wpname,"{","") + wpname = string.gsub(wpname,"}","") + local tbl = UTILS.Split(wpname,",") + local wptbl = {} + for _id,_key in ipairs(tbl) do + table.insert(wptbl,_id,_key) + end + self:SetAmount(wptbl,wpnumber) + else + self:SetAmount(wpname,wpnumber) + end + end + end + else + self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv")) + end + end + + return self +end + +--- Start a STORAGE autosave process. +-- @param #STORAGE self +-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. +-- @param #string Filename The name of the file. +-- @param #number Interval The interval, start after this many seconds and repeat every interval seconds. Defaults to 300. +-- @param #boolean LoadOnce If LoadOnce is true or nil, we try to load saved storage first. +-- @return #STORAGE self +function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce) + if LoadOnce ~= false then + self:LoadFromFile(Path,Filename) + end + local interval = Interval or 300 + self.SaverTimer = TIMER:New(STORAGE.SaveToFile,self,Path,Filename) + self.SaverTimer:Start(interval,interval) + return self +end + +--- Stop a running STORAGE autosave process. +-- @param #STORAGE self +-- @return #STORAGE self +function STORAGE:StopAutoSave() + if self.SaverTimer and self.SaverTimer:IsRunning() then + self.SaverTimer:Stop() + self.SaverTimer = nil + end + return self +end + +--- Try to find the #STORAGE object of one of the many "H"-Helipads in Syria. You need to put a (small, round) zone on top of it, because the name is not unique(!). +-- @param #STORAGE self +-- @param #string ZoneName The name of the zone where to find the helipad. +-- @return #STORAGE self or nil if not found. +function STORAGE:FindSyriaHHelipadWarehouse(ZoneName) + local findzone = ZONE:New(ZoneName) + local base = world.getAirbases() + for i = 1, #base do + local info = {} + --info.desc = Airbase.getDesc(base[i]) + info.callsign = Airbase.getCallsign(base[i]) + info.id = Airbase.getID(base[i]) + --info.cat = Airbase.getCategory(base[i]) + info.point = Airbase.getPoint(base[i]) + info.coordinate = COORDINATE:NewFromVec3(info.point) + info.DCSObject = base[i] + --if Airbase.getUnit(base[i]) then + --info.unitId = Airbase.getUnit(base[i]):getID() + --end + if info.callsign == "H" and findzone:IsCoordinateInZone(info.coordinate) then + info.warehouse = info.DCSObject:getWarehouse() + info.Storage = STORAGE:New(info.callsign..info.id) + info.Storage.airbase = info.DCSObject + info.Storage.warehouse = info.warehouse + return info.Storage + end + end +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Private Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 497d9fb1a..10d59ed81 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -92,10 +92,10 @@ -- -- @field #UNIT UNIT = { - ClassName="UNIT", - UnitName=nil, - GroupName=nil, - DCSUnit = nil, + ClassName = "UNIT", + UnitName = nil, + GroupName = nil, + DCSUnit = nil, } @@ -108,33 +108,34 @@ UNIT = { -- Registration. - + --- Create a new UNIT from DCSUnit. -- @param #UNIT self -- @param #string UnitName The name of the DCS unit. -- @return #UNIT self -function UNIT:Register( UnitName ) +function UNIT:Register(UnitName) - -- Inherit CONTROLLABLE. - local self = BASE:Inherit( self, CONTROLLABLE:New( UnitName ) ) --#UNIT - - -- Set unit name. - self.UnitName = UnitName - - local unit=Unit.getByName(self.UnitName) - - if unit then - local group = unit:getGroup() - if group then - self.GroupName=group:getName() + -- Inherit CONTROLLABLE. + local self = BASE:Inherit(self, CONTROLLABLE:New(UnitName)) --#UNIT + + -- Set unit name. + self.UnitName = UnitName + + local unit = Unit.getByName(self.UnitName) + + if unit then + local group = unit:getGroup() + if group then + self.GroupName = group:getName() + self.groupId = group:getID() + end + self.DCSUnit = unit end - self.DCSUnit = unit - end - - -- Set event prio. - self:SetEventPriority( 3 ) - - return self + + -- Set event prio. + self:SetEventPriority(3) + + return self end -- Reference methods. @@ -143,23 +144,23 @@ end -- @param #UNIT self -- @param DCS#Unit DCSUnit An existing DCS Unit object reference. -- @return #UNIT self -function UNIT:Find( DCSUnit ) - if DCSUnit then - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound - end - return nil +function UNIT:Find(DCSUnit) + if DCSUnit then + local UnitName = DCSUnit:getName() + local UnitFound = _DATABASE:FindUnit(UnitName) + return UnitFound + end + return nil end --- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. -- @param #UNIT self -- @param #string UnitName The Unit Name. -- @return #UNIT self -function UNIT:FindByName( UnitName ) - - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound +function UNIT:FindByName(UnitName) + + local UnitFound = _DATABASE:FindUnit(UnitName) + return UnitFound end --- Find the first(!) UNIT matching using patterns. Note that this is **a lot** slower than `:FindByName()`! @@ -174,17 +175,17 @@ end -- -- using a pattern -- local unit = UNIT:FindByMatching( ".%d.%d$" ) -- -- will return the first group found ending in "-1-1" to "-9-9", but not e.g. "-10-1" -function UNIT:FindByMatching( Pattern ) - local GroupFound = nil - - for name,group in pairs(_DATABASE.UNITS) do - if string.match(name, Pattern ) then - GroupFound = group - break +function UNIT:FindByMatching(Pattern) + local GroupFound = nil + + for name, group in pairs(_DATABASE.UNITS) do + if string.match(name, Pattern) then + GroupFound = group + break + end end - end - - return GroupFound + + return GroupFound end --- Find all UNIT objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`! @@ -199,26 +200,27 @@ end -- -- using a pattern -- local unittable = UNIT:FindAllByMatching( ".%d.%d$" ) -- -- will return the all units found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10" -function UNIT:FindAllByMatching( Pattern ) - local GroupsFound = {} - - for name,group in pairs(_DATABASE.UNITS) do - if string.match(name, Pattern ) then - GroupsFound[#GroupsFound+1] = group +function UNIT:FindAllByMatching(Pattern) + local GroupsFound = {} + + for name, group in pairs(_DATABASE.UNITS) do + if string.match(name, Pattern) then + GroupsFound[#GroupsFound + 1] = group + end end - end - - return GroupsFound + + return GroupsFound end --- Return the name of the UNIT. -- @param #UNIT self -- @return #string The UNIT name. function UNIT:Name() - - return self.UnitName + + return self.UnitName end +--[[ --- Get the DCS unit object. -- @param #UNIT self -- @return DCS#Unit The DCS unit object. @@ -230,34 +232,60 @@ function UNIT:GetDCSObject() return DCSUnit end - --if self.DCSUnit then - --return self.DCSUnit - --end - return nil end +--]] + +--- Returns the DCS Unit. +-- @param #UNIT self +-- @return DCS#Unit The DCS Group. +function UNIT:GetDCSObject() + + -- FF: Added checks that DCSObject exists because otherwise there were problems when respawning the unit right after it was initially spawned (e.g. teleport in OPSGROUP). + -- Got "Unit does not exit" after coalition.addGroup() when trying to access unit data because LastCallDCSObject<=1. + if (not self.LastCallDCSObject) or (self.LastCallDCSObject and timer.getTime() - self.LastCallDCSObject > 1) or (self.DCSObject == nil) or (self.DCSObject:isExist() == false) then + + -- Get DCS group. + local DCSUnit = Unit.getByName(self.UnitName) + + if DCSUnit then + self.LastCallDCSObject = timer.getTime() + self.DCSObject = DCSUnit + return DCSUnit + else + self.DCSObject = nil + self.LastCallDCSObject = nil + end + + else + return self.DCSObject + end + + --self:E(string.format("ERROR: Could not get DCS group object of group %s because DCS object could not be found!", tostring(self.UnitName))) + return nil +end --- Returns the unit altitude above sea level in meters. -- @param Wrapper.Unit#UNIT self -- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level. -- @return #number The height of the group or nil if is not existing or alive. function UNIT:GetAltitude(FromGround) - - local DCSUnit = Unit.getByName( self.UnitName ) - if DCSUnit then - local altitude = 0 - local point = DCSUnit:getPoint() --DCS#Vec3 - altitude = point.y - if FromGround then - local land = land.getHeight( { x = point.x, y = point.z } ) or 0 - altitude = altitude - land + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local altitude = 0 + local point = DCSUnit:getPoint() --DCS#Vec3 + altitude = point.y + if FromGround then + local land = land.getHeight({ x = point.x, y = point.z }) or 0 + altitude = altitude - land + end + return altitude end - return altitude - end - return nil - + return nil + end --- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group. @@ -271,85 +299,85 @@ end -- @param #UNIT self -- @param Core.Point#COORDINATE Coordinate The position where to Spawn the new Unit at. -- @param #number Heading The heading of the unit respawn. -function UNIT:ReSpawnAt( Coordinate, Heading ) +function UNIT:ReSpawnAt(Coordinate, Heading) - self:T( self:Name() ) - local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) ) - self:T( SpawnGroupTemplate ) + --self:T( self:Name() ) + local SpawnGroupTemplate = UTILS.DeepCopy(_DATABASE:GetGroupTemplateFromUnitName(self:Name())) + --self:T( SpawnGroupTemplate ) - local SpawnGroup = self:GetGroup() - self:T( { SpawnGroup = SpawnGroup } ) - - if SpawnGroup then - - local Vec3 = SpawnGroup:GetVec3() - SpawnGroupTemplate.x = Coordinate.x - SpawnGroupTemplate.y = Coordinate.z - - self:F( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do - local GroupUnit = UnitData -- #UNIT - self:F( GroupUnit:GetName() ) - if GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y - SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x - SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z - SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading - self:F( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) - end - end - end - - for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do - self:T( { UnitTemplateData.name, self:Name() } ) - SpawnGroupTemplate.units[UnitTemplateID].unitId = nil - if UnitTemplateData.name == self:Name() then - self:T("Adjusting") - SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y - SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x - SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z - SpawnGroupTemplate.units[UnitTemplateID].heading = Heading - self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) - else - self:F( SpawnGroupTemplate.units[UnitTemplateID].name ) - local GroupUnit = UNIT:FindByName( SpawnGroupTemplate.units[UnitTemplateID].name ) -- #UNIT - if GroupUnit and GroupUnit:IsAlive() then - local GroupUnitVec3 = GroupUnit:GetVec3() - local GroupUnitHeading = GroupUnit:GetHeading() - UnitTemplateData.alt = GroupUnitVec3.y - UnitTemplateData.x = GroupUnitVec3.x - UnitTemplateData.y = GroupUnitVec3.z - UnitTemplateData.heading = GroupUnitHeading - else - if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then - self:T("nilling") - SpawnGroupTemplate.units[UnitTemplateID].delete = true + local SpawnGroup = self:GetGroup() + --self:T( { SpawnGroup = SpawnGroup } ) + + if SpawnGroup then + + local Vec3 = SpawnGroup:GetVec3() + SpawnGroupTemplate.x = Coordinate.x + SpawnGroupTemplate.y = Coordinate.z + + --self:F( #SpawnGroupTemplate.units ) + for UnitID, UnitData in pairs(SpawnGroup:GetUnits() or {}) do + local GroupUnit = UnitData -- #UNIT + --self:F( GroupUnit:GetName() ) + if GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + SpawnGroupTemplate.units[UnitID].alt = GroupUnitVec3.y + SpawnGroupTemplate.units[UnitID].x = GroupUnitVec3.x + SpawnGroupTemplate.units[UnitID].y = GroupUnitVec3.z + SpawnGroupTemplate.units[UnitID].heading = GroupUnitHeading + --self:F( { UnitID, SpawnGroupTemplate.units[UnitID], SpawnGroupTemplate.units[UnitID] } ) + end end - end end - end - -- Remove obscolete units from the group structure - local i = 1 - while i <= #SpawnGroupTemplate.units do - - local UnitTemplateData = SpawnGroupTemplate.units[i] - self:T( UnitTemplateData.name ) - - if UnitTemplateData.delete then - table.remove( SpawnGroupTemplate.units, i ) - else - i = i + 1 + for UnitTemplateID, UnitTemplateData in pairs(SpawnGroupTemplate.units) do + --self:T( { UnitTemplateData.name, self:Name() } ) + SpawnGroupTemplate.units[UnitTemplateID].unitId = nil + if UnitTemplateData.name == self:Name() then + --self:T("Adjusting") + SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y + SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x + SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z + SpawnGroupTemplate.units[UnitTemplateID].heading = Heading + --self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } ) + else + --self:F( SpawnGroupTemplate.units[UnitTemplateID].name ) + local GroupUnit = UNIT:FindByName(SpawnGroupTemplate.units[UnitTemplateID].name) -- #UNIT + if GroupUnit and GroupUnit:IsAlive() then + local GroupUnitVec3 = GroupUnit:GetVec3() + local GroupUnitHeading = GroupUnit:GetHeading() + UnitTemplateData.alt = GroupUnitVec3.y + UnitTemplateData.x = GroupUnitVec3.x + UnitTemplateData.y = GroupUnitVec3.z + UnitTemplateData.heading = GroupUnitHeading + else + if SpawnGroupTemplate.units[UnitTemplateID].name ~= self:Name() then + --self:T("nilling") + SpawnGroupTemplate.units[UnitTemplateID].delete = true + end + end + end end - end - - SpawnGroupTemplate.groupId = nil - - self:T( SpawnGroupTemplate ) - _DATABASE:Spawn( SpawnGroupTemplate ) + -- Remove obscolete units from the group structure + local i = 1 + while i <= #SpawnGroupTemplate.units do + + local UnitTemplateData = SpawnGroupTemplate.units[i] + --self:T( UnitTemplateData.name ) + + if UnitTemplateData.delete then + table.remove(SpawnGroupTemplate.units, i) + else + i = i + 1 + end + end + + SpawnGroupTemplate.groupId = nil + + --self:T( SpawnGroupTemplate ) + + _DATABASE:Spawn(SpawnGroupTemplate) end @@ -358,17 +386,17 @@ end -- @param #UNIT self -- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive. function UNIT:IsActive() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - local UnitIsActive = DCSUnit:isActive() - return UnitIsActive - end + local DCSUnit = self:GetDCSObject() - return nil + if DCSUnit then + + local UnitIsActive = DCSUnit:isActive() + return UnitIsActive + end + + return nil end --- Returns if the unit is exists in the mission. @@ -378,14 +406,14 @@ end -- @return #boolean Returns `true` if unit exists in the mission. function UNIT:IsExist() - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit then - local exists = DCSUnit:isExist() - return exists - end - - return nil + local DCSUnit = self:GetDCSObject() -- DCS#Unit + + if DCSUnit then + local exists = DCSUnit:isExist() + return exists + end + + return nil end --- Returns if the Unit is alive. @@ -395,73 +423,86 @@ end -- @param #UNIT self -- @return #boolean Returns `true` if Unit is alive and active, `false` if it exists but is not active and `nil` if the object does not exist or DCS `isExist` function returns false. function UNIT:IsAlive() - self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit and DCSUnit:isExist() then - local UnitIsAlive = DCSUnit:isActive() - return UnitIsAlive - end - - return nil + local DCSUnit = self:GetDCSObject() -- DCS#Unit + + if DCSUnit and DCSUnit:isExist() then + local UnitIsAlive = DCSUnit:isActive() + return UnitIsAlive + end + + return nil end --- Returns if the Unit is dead. -- @param #UNIT self -- @return #boolean `true` if Unit is dead, else false or nil if the unit does not exist function UNIT:IsDead() - return not self:IsAlive() + return not self:IsAlive() end --- Returns the Unit's callsign - the localized string. -- @param #UNIT self -- @return #string The Callsign of the Unit. function UNIT:GetCallsign() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCallSign = DCSUnit:getCallsign() - if UnitCallSign == "" then - UnitCallSign = DCSUnit:getName() + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCallSign = DCSUnit:getCallsign() + if UnitCallSign == "" then + UnitCallSign = DCSUnit:getName() + end + return UnitCallSign end - return UnitCallSign - end - - self:F( self.ClassName .. " " .. self.UnitName .. " not found!" ) - return nil + + --self:F( self.ClassName .. " " .. self.UnitName .. " not found!" ) + return nil end --- Check if an (air) unit is a client or player slot. Information is retrieved from the group template. -- @param #UNIT self -- @return #boolean If true, unit is associated with a client or player slot. function UNIT:IsPlayer() - - -- Get group. - local group=self:GetGroup() - - if not group then return false end - - -- Units of template group. - local template = group:GetTemplate() - - if (template == nil) or (template.units == nil ) then return false end - - local units=template.units - - -- Get numbers. - for _,unit in pairs(units) do - - -- Check if unit name matach and skill is Client or Player. - if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then - return true + + -- Get group. + local group = self:GetGroup() + + if not group then + return false end - end - - return false + -- Units of template group. + local template = group:GetTemplate() + + if (template == nil) or (template.units == nil) then + local DCSObject = self:GetDCSObject() + if DCSObject then + if DCSObject:getPlayerName() ~= nil then + return true + else + return false + end + else + return false + end + end + + local units = template.units + + -- Get numbers. + for _, unit in pairs(units) do + + -- Check if unit name matach and skill is Client or Player. + if unit.name == self:GetName() and (unit.skill == "Client" or unit.skill == "Player") then + return true + end + + end + + return false end @@ -470,32 +511,32 @@ end -- @return #string Player Name -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPlayerName() - self:F( self.UnitName ) + --self:F( self.UnitName ) - local DCSUnit = self:GetDCSObject() -- DCS#Unit - - if DCSUnit then - - local PlayerName = DCSUnit:getPlayerName() - -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 --- if PlayerName == nil or PlayerName == "" then --- local PlayerCategory = DCSUnit:getDesc().category --- if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then --- PlayerName = "Player" .. DCSUnit:getID() --- end --- end --- -- Good code --- if PlayerName == nil then --- PlayerName = nil --- else --- if PlayerName == "" then --- PlayerName = "Player" .. DCSUnit:getID() --- end --- end - return PlayerName - end + local DCSUnit = self:GetDCSObject() -- DCS#Unit - return nil + if DCSUnit then + + local PlayerName = DCSUnit:getPlayerName() + -- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696 + -- if PlayerName == nil or PlayerName == "" then + -- local PlayerCategory = DCSUnit:getDesc().category + -- if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then + -- PlayerName = "Player" .. DCSUnit:getID() + -- end + -- end + -- -- Good code + -- if PlayerName == nil then + -- PlayerName = nil + -- else + -- if PlayerName == "" then + -- PlayerName = "Player" .. DCSUnit:getID() + -- end + -- end + return PlayerName + end + + return nil end @@ -504,11 +545,11 @@ end -- @return #boolean If true, unit is a player or client aircraft function UNIT:IsClient() - if _DATABASE.CLIENTS[self.UnitName] then - return true - end + if _DATABASE.CLIENTS[self.UnitName] then + return true + end - return false + return false end --- Get the CLIENT of the unit @@ -516,23 +557,23 @@ end -- @return Wrapper.Client#CLIENT function UNIT:GetClient() - local client=_DATABASE.CLIENTS[self.UnitName] + local client = _DATABASE.CLIENTS[self.UnitName] - if client then - return client - end + if client then + return client + end - return nil + return nil end --- [AIRPLANE] Get the NATO reporting name of a UNIT. Currently airplanes only! --@param #UNIT self --@return #string NatoReportingName or "Bogey" if unknown. function UNIT:GetNatoReportingName() - - local typename = self:GetTypeName() - return UTILS.GetReportingName(typename) - + + local typename = self:GetTypeName() + return UTILS.GetReportingName(typename) + end @@ -544,16 +585,16 @@ end -- @return #number The Unit number. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetNumber() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitNumber = DCSUnit:getNumber() - return UnitNumber - end + local DCSUnit = self:GetDCSObject() - return nil + if DCSUnit then + local UnitNumber = DCSUnit:getNumber() + return UnitNumber + end + + return nil end @@ -561,16 +602,16 @@ end -- @param #UNIT self -- @return #number Speed in km/h. function UNIT:GetSpeedMax() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local Desc = self:GetDesc() - - if Desc then - local SpeedMax = Desc.speedMax - return SpeedMax*3.6 - end + local Desc = self:GetDesc() - return 0 + if Desc then + local SpeedMax = Desc.speedMax or 0 + return SpeedMax * 3.6 + end + + return 0 end --- Returns the unit's max range in meters derived from the DCS descriptors. @@ -578,21 +619,21 @@ end -- @param #UNIT self -- @return #number Range in meters. function UNIT:GetRange() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local Desc = self:GetDesc() - - if Desc then - local Range = Desc.range --This is in kilometers (not meters) for some reason. But should check again! - if Range then - Range=Range*1000 -- convert to meters. - else - Range=10000000 --10.000 km if no range + local Desc = self:GetDesc() + + if Desc then + local Range = Desc.range --This is in kilometers (not meters) for some reason. But should check again! + if Range then + Range = Range * 1000 -- convert to meters. + else + Range = 10000000 --10.000 km if no range + end + return Range end - return Range - end - return nil + return nil end --- Check if the unit is refuelable. Also retrieves the refuelling system (boom or probe) if applicable. @@ -600,18 +641,18 @@ end -- @return #boolean If true, unit is refuelable (checks for the attribute "Refuelable"). -- @return #number Refueling system (if any): 0=boom, 1=probe. function UNIT:IsRefuelable() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local refuelable=self:HasAttribute("Refuelable") - - local system=nil - - local Desc=self:GetDesc() - if Desc and Desc.tankerType then - system=Desc.tankerType - end + local refuelable = self:HasAttribute("Refuelable") - return refuelable, system + local system = nil + + local Desc = self:GetDesc() + if Desc and Desc.tankerType then + system = Desc.tankerType + end + + return refuelable, system end --- Check if the unit is a tanker. Also retrieves the refuelling system (boom or probe) if applicable. @@ -619,41 +660,41 @@ end -- @return #boolean If true, unit is a tanker (checks for the attribute "Tankers"). -- @return #number Refueling system (if any): 0=boom, 1=probe. function UNIT:IsTanker() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local tanker=self:HasAttribute("Tankers") - - local system=nil - - if tanker then - - local Desc=self:GetDesc() - if Desc and Desc.tankerType then - system=Desc.tankerType - end - - local typename=self:GetTypeName() - - -- Some hard coded data as this is not in the descriptors... - if typename=="IL-78M" then - system=1 --probe - elseif typename=="KC130" or typename=="KC130J" then - system=1 --probe - elseif typename=="KC135BDA" then - system=1 --probe - elseif typename=="KC135MPRS" then - system=1 --probe - elseif typename=="S-3B Tanker" then - system=1 --probe - elseif typename=="KC_10_Extender" then - system=1 --probe - elseif typename=="KC_10_Extender_D" then - system=0 --boom - end - - end + local tanker = self:HasAttribute("Tankers") - return tanker, system + local system = nil + + if tanker then + + local Desc = self:GetDesc() + if Desc and Desc.tankerType then + system = Desc.tankerType + end + + local typename = self:GetTypeName() + + -- Some hard coded data as this is not in the descriptors... + if typename == "IL-78M" then + system = 1 --probe + elseif typename == "KC130" or typename == "KC130J" then + system = 1 --probe + elseif typename == "KC135BDA" then + system = 1 --probe + elseif typename == "KC135MPRS" then + system = 1 --probe + elseif typename == "S-3B Tanker" then + system = 1 --probe + elseif typename == "KC_10_Extender" then + system = 1 --probe + elseif typename == "KC_10_Extender_D" then + system = 0 --boom + end + + end + + return tanker, system end --- Check if the unit can supply ammo. Currently, we have @@ -668,21 +709,21 @@ end -- @return #boolean If `true`, unit can supply ammo. function UNIT:IsAmmoSupply() - -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. - local typename=self:GetTypeName() - - if typename=="M 818" then - -- Blue ammo truck. - return true - elseif typename=="Ural-375" then - -- Red ammo truck. - return true - elseif typename=="ZIL-135" then - -- Red ammo truck. Checked that it can also provide ammo. - return true - end + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename = self:GetTypeName() - return false + if typename == "M 818" then + -- Blue ammo truck. + return true + elseif typename == "Ural-375" then + -- Red ammo truck. + return true + elseif typename == "ZIL-135" then + -- Red ammo truck. Checked that it can also provide ammo. + return true + end + + return false end --- Check if the unit can supply fuel. Currently, we have @@ -698,41 +739,41 @@ end -- @return #boolean If `true`, unit can supply fuel. function UNIT:IsFuelSupply() - -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. - local typename=self:GetTypeName() - - if typename=="M978 HEMTT Tanker" then - return true - elseif typename=="ATMZ-5" then - return true - elseif typename=="ATMZ-10" then - return true - elseif typename=="ATZ-5" then - return true - end + -- Type name is the only thing we can check. There is no attribute (Sep. 2021) which would tell us. + local typename = self:GetTypeName() - return false + if typename == "M978 HEMTT Tanker" then + return true + elseif typename == "ATMZ-5" then + return true + elseif typename == "ATMZ-10" then + return true + elseif typename == "ATZ-5" then + return true + end + + return false end --- Returns the unit's group if it exists and nil otherwise. -- @param Wrapper.Unit#UNIT self -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. function UNIT:GetGroup() - self:F2( self.UnitName ) - local UnitGroup = GROUP:FindByName(self.GroupName) - if UnitGroup then - return UnitGroup - else - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local grp = DCSUnit:getGroup() - if grp then - local UnitGroup = GROUP:FindByName( grp:getName() ) + --self:F2( self.UnitName ) + local UnitGroup = GROUP:FindByName(self.GroupName) + if UnitGroup then return UnitGroup - end + else + local DCSUnit = self:GetDCSObject() + if DCSUnit then + local grp = DCSUnit:getGroup() + if grp then + local UnitGroup = GROUP:FindByName(grp:getName()) + return UnitGroup + end + end end - end - return nil + return nil end --- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. @@ -742,39 +783,39 @@ end -- @return #string The name of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. function UNIT:GetPrefix() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitPrefix = string.match( self.UnitName, ".*#" ):sub( 1, -2 ) - self:T3( UnitPrefix ) - return UnitPrefix - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitPrefix = string.match(self.UnitName, ".*#"):sub(1, -2) + --self:T3( UnitPrefix ) + return UnitPrefix + end + + return nil end --- Returns the Unit's ammunition. -- @param #UNIT self -- @return DCS#Unit.Ammo Table with ammuntion of the unit (or nil). This can be a complex table! function UNIT:GetAmmo() - self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - --local status, unitammo = pcall( - -- function() + --self:F2( self.UnitName ) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + --local status, unitammo = pcall( + -- function() -- local UnitAmmo = DCSUnit:getAmmo() -- return UnitAmmo - --end - --) - --if status then - --return unitammo - --end - local UnitAmmo = DCSUnit:getAmmo() - return UnitAmmo - end - return nil + --end + --) + --if status then + --return unitammo + --end + local UnitAmmo = DCSUnit:getAmmo() + return UnitAmmo + end + return nil end @@ -783,11 +824,11 @@ end -- @param #number mass to set cargo to -- @return #UNIT self function UNIT:SetUnitInternalCargo(mass) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) - end - return self + local DCSUnit = self:GetDCSObject() + if DCSUnit then + trigger.action.setUnitInternalCargo(DCSUnit:getName(), mass) + end + return self end --- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has. @@ -802,165 +843,177 @@ end -- @return #number Number of tank HE shells left (for tanks, if applicable) function UNIT:GetAmmunition() - -- Init counter. - local nammo=0 - local nshells=0 - local nrockets=0 - local nmissiles=0 - local nbombs=0 - local narti=0 - local nAPshells = 0 - local nHEshells = 0 + -- Init counter. + local nammo = 0 + local nshells = 0 + local nrockets = 0 + local nmissiles = 0 + local nbombs = 0 + local narti = 0 + local nAPshells = 0 + local nHEshells = 0 - local unit=self + local unit = self - -- Get ammo table. - local ammotable=unit:GetAmmo() + -- Get ammo table. + local ammotable = unit:GetAmmo() - if ammotable then + if ammotable then - local weapons=#ammotable - - -- Loop over all weapons. - for w=1,weapons do + local weapons = #ammotable - -- Number of current weapon. - local Nammo=ammotable[w]["count"] + -- Loop over all weapons. + for w = 1, weapons do - -- Type name of current weapon. - local Tammo=ammotable[w]["desc"]["typeName"] + -- Number of current weapon. + local Nammo = ammotable[w]["count"] - --local _weaponString = UTILS.Split(Tammo,"%.") - --local _weaponName = _weaponString[#_weaponString] + -- Type name of current weapon. + local Tammo = ammotable[w]["desc"]["typeName"] - -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 - local Category=ammotable[w].desc.category + --local _weaponString = UTILS.Split(Tammo,"%.") + --local _weaponName = _weaponString[#_weaponString] - -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 - local MissileCategory=nil - if Category==Weapon.Category.MISSILE then - MissileCategory=ammotable[w].desc.missileCategory - end + -- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3 + local Category = ammotable[w].desc.category - -- We are specifically looking for shells or rockets here. - if Category==Weapon.Category.SHELL then + -- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6 + local MissileCategory = nil + if Category == Weapon.Category.MISSILE then + MissileCategory = ammotable[w].desc.missileCategory + end + + -- We are specifically looking for shells or rockets here. + if Category == Weapon.Category.SHELL then + + -- Add up all shells. + nshells = nshells + Nammo + + if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass > 0 then + narti = narti + Nammo + end + + if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName, "_AP", 1, true) then + nAPshells = nAPshells + Nammo + end + + if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName, "_HE", 1, true) then + nHEshells = nHEshells + Nammo + end + + elseif Category == Weapon.Category.ROCKET then + + -- Add up all rockets. + nrockets = nrockets + Nammo + + elseif Category == Weapon.Category.BOMB then + + -- Add up all rockets. + nbombs = nbombs + Nammo + + elseif Category == Weapon.Category.MISSILE then + + + -- Add up all missiles (category 5) + if MissileCategory == Weapon.MissileCategory.AAM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.ANTI_SHIP then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.BM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.OTHER then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.SAM then + nmissiles = nmissiles + Nammo + elseif MissileCategory == Weapon.MissileCategory.CRUISE then + nmissiles = nmissiles + Nammo + end + + end - -- Add up all shells. - nshells=nshells+Nammo - - if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass > 0 then - narti=narti+Nammo end - - if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_AP",1,true) then - nAPshells = nAPshells+Nammo - end - - if ammotable[w].desc.typeName and string.find(ammotable[w].desc.typeName,"_HE",1,true) then - nHEshells = nHEshells+Nammo - end - - elseif Category==Weapon.Category.ROCKET then - - -- Add up all rockets. - nrockets=nrockets+Nammo - - elseif Category==Weapon.Category.BOMB then - - -- Add up all rockets. - nbombs=nbombs+Nammo - - elseif Category==Weapon.Category.MISSILE then - - - -- Add up all missiles (category 5) - if MissileCategory==Weapon.MissileCategory.AAM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.BM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.OTHER then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.SAM then - nmissiles=nmissiles+Nammo - elseif MissileCategory==Weapon.MissileCategory.CRUISE then - nmissiles=nmissiles+Nammo - end - - end - end - end - -- Total amount of ammunition. - nammo=nshells+nrockets+nmissiles+nbombs + -- Total amount of ammunition. + nammo = nshells + nrockets + nmissiles + nbombs - return nammo, nshells, nrockets, nbombs, nmissiles, narti, nAPshells, nHEshells + return nammo, nshells, nrockets, nbombs, nmissiles, narti, nAPshells, nHEshells end --- Checks if a tank still has AP shells. -- @param #UNIT self -- @return #boolean HasAPShells function UNIT:HasAPShells() - local _,_,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Get number of AP shells from a tank. -- @param #UNIT self -- @return #number Number of AP shells function UNIT:GetAPShells() - local _,_,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Get number of HE shells from a tank. -- @param #UNIT self -- @return #number Number of HE shells function UNIT:GetHEShells() - local _,_,_,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Checks if a tank still has HE shells. -- @param #UNIT self -- @return #boolean HasHEShells function UNIT:HasHEShells() - local _,_,_,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Checks if an artillery unit still has artillery shells. -- @param #UNIT self -- @return #boolean HasArtiShells function UNIT:HasArtiShells() - local _,_,_,_,_,shells = self:GetAmmunition() - if shells > 0 then return true else return false end + local _, _, _, _, _, shells = self:GetAmmunition() + if shells > 0 then + return true + else + return false + end end --- Get number of artillery shells from an artillery unit. -- @param #UNIT self -- @return #number Number of artillery shells function UNIT:GetArtiShells() - local _,_,_,_,_,shells = self:GetAmmunition() - return shells or 0 + local _, _, _, _, _, shells = self:GetAmmunition() + return shells or 0 end --- Returns the unit sensors. -- @param #UNIT self -- @return DCS#Unit.Sensors Table of sensors. function UNIT:GetSensors() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSensors = DCSUnit:getSensors() - return UnitSensors - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSensors = DCSUnit:getSensors() + return UnitSensors + end + + return nil end -- Need to add here a function per sensortype @@ -969,41 +1022,41 @@ end --- Returns if the unit has sensors of a certain type. -- @param #UNIT self -- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. -function UNIT:HasSensors( ... ) - self:F2( arg ) +function UNIT:HasSensors(...) + --self:F2( arg ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local HasSensors = DCSUnit:hasSensors( unpack( arg ) ) - return HasSensors - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local HasSensors = DCSUnit:hasSensors(unpack(arg)) + return HasSensors + end + + return nil end --- Returns if the unit is SEADable. -- @param #UNIT self -- @return #boolean returns true if the unit is SEADable. function UNIT:HasSEAD() - self:F2() + --self:F2() - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitSEADAttributes = DCSUnit:getDesc().attributes - - local HasSEAD = false - if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or - UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or - UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true - then - HasSEAD = true + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitSEADAttributes = DCSUnit:getDesc().attributes + + local HasSEAD = false + if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or + UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or + UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true + then + HasSEAD = true + end + return HasSEAD end - return HasSEAD - end - - return nil + + return nil end --- Returns two values: @@ -1014,32 +1067,32 @@ end -- @return #boolean Indicates if at least one of the unit's radar(s) is on. -- @return DCS#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. function UNIT:GetRadar() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() - return UnitRadarOn, UnitRadarObject - end - - return nil, nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitRadarOn, UnitRadarObject = DCSUnit:getRadar() + return UnitRadarOn, UnitRadarObject + end + + return nil, nil end --- Returns relative amount of fuel (from 0.0 to 1.0) the UNIT has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. -- @param #UNIT self -- @return #number The relative amount of fuel (from 0.0 to 1.0) or *nil* if the DCS Unit is not existing or alive. function UNIT:GetFuel() - self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitFuel = DCSUnit:getFuel() - return UnitFuel - end - - return nil + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitFuel = DCSUnit:getFuel() + return UnitFuel + end + + return nil end @@ -1047,18 +1100,18 @@ end -- @param #UNIT self -- @return #list A list of one @{Wrapper.Unit}. function UNIT:GetUnits() - self:F3( { self.UnitName } ) - local DCSUnit = self:GetDCSObject() + --self:F3( { self.UnitName } ) + local DCSUnit = self:GetDCSObject() - local Units = {} - - if DCSUnit then - Units[1] = UNIT:Find( DCSUnit ) - self:T3( Units ) - return Units - end + local Units = {} - return nil + if DCSUnit then + Units[1] = UNIT:Find(DCSUnit) + - self:T3(Units) + return Units + end + + return nil end @@ -1066,60 +1119,60 @@ end -- @param #UNIT self -- @return #number The Unit's health value or -1 if unit does not exist any more. function UNIT:GetLife() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit and DCSUnit:isExist() then - local UnitLife = DCSUnit:getLife() - return UnitLife - end - - return -1 + local DCSUnit = self:GetDCSObject() + + if DCSUnit and DCSUnit:isExist() then + local UnitLife = DCSUnit:getLife() + return UnitLife + end + + return -1 end --- Returns the Unit's initial health. -- @param #UNIT self -- @return #number The Unit's initial health value or 0 if unit does not exist any more. function UNIT:GetLife0() - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitLife0 = DCSUnit:getLife0() - return UnitLife0 - end - - return 0 + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitLife0 = DCSUnit:getLife0() + return UnitLife0 + end + + return 0 end --- Returns the unit's relative health. -- @param #UNIT self -- @return #number The Unit's relative health value, i.e. a number in [0,1] or -1 if unit does not exist any more. function UNIT:GetLifeRelative() - self:F2(self.UnitName) + --self:F2(self.UnitName) - if self and self:IsAlive() then - local life0=self:GetLife0() - local lifeN=self:GetLife() - return lifeN/life0 - end - - return -1 + if self and self:IsAlive() then + local life0 = self:GetLife0() + local lifeN = self:GetLife() + return lifeN / life0 + end + + return -1 end --- Returns the unit's relative damage, i.e. 1-life. -- @param #UNIT self -- @return #number The Unit's relative health value, i.e. a number in [0,1] or 1 if unit does not exist any more. function UNIT:GetDamageRelative() - self:F2(self.UnitName) + --self:F2(self.UnitName) - if self and self:IsAlive() then - return 1-self:GetLifeRelative() - end - - return 1 + if self and self:IsAlive() then + return 1 - self:GetLifeRelative() + end + + return 1 end --- Returns the current value for an animation argument on the external model of the given object. @@ -1130,14 +1183,14 @@ end -- @return #number Value of the animation argument [-1, 1]. If draw argument value is invalid for the unit in question a value of 0 will be returned. function UNIT:GetDrawArgumentValue(AnimationArgument) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0) - return value - end - - return 0 + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0) + return value + end + + return 0 end --- Returns the category of the #UNIT from descriptor. Returns one of @@ -1151,38 +1204,38 @@ end -- @param #UNIT self -- @return #number Unit category from `getDesc().category`. function UNIT:GetUnitCategory() - self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - return DCSUnit:getDesc().category - end - - return nil + local DCSUnit = self:GetDCSObject() + if DCSUnit then + return DCSUnit:getDesc().category + end + + return nil end --- Returns the category name of the #UNIT. -- @param #UNIT self -- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship function UNIT:GetCategoryName() - self:F3( self.UnitName ) + --self:F3( self.UnitName ) - local DCSUnit = self:GetDCSObject() - if DCSUnit then - local CategoryNames = { - [Unit.Category.AIRPLANE] = "Airplane", - [Unit.Category.HELICOPTER] = "Helicopter", - [Unit.Category.GROUND_UNIT] = "Ground Unit", - [Unit.Category.SHIP] = "Ship", - [Unit.Category.STRUCTURE] = "Structure", - } - local UnitCategory = DCSUnit:getDesc().category - self:T3( UnitCategory ) + local DCSUnit = self:GetDCSObject() + if DCSUnit then + local CategoryNames = { + [Unit.Category.AIRPLANE] = "Airplane", + [Unit.Category.HELICOPTER] = "Helicopter", + [Unit.Category.GROUND_UNIT] = "Ground Unit", + [Unit.Category.SHIP] = "Ship", + [Unit.Category.STRUCTURE] = "Structure", + } + local UnitCategory = DCSUnit:getDesc().category + --self:T3( UnitCategory ) - return CategoryNames[UnitCategory] - end + return CategoryNames[UnitCategory] + end - return nil + return nil end @@ -1244,125 +1297,150 @@ end function UNIT:GetThreatLevel() - local ThreatLevel = 0 - local ThreatText = "" - - local Descriptor = self:GetDesc() - - if Descriptor then - - local Attributes = Descriptor.attributes - - if self:IsGround() then - - local ThreatLevels = { - [1] = "Unarmed", - [2] = "Infantry", - [3] = "Old Tanks & APCs", - [4] = "Tanks & IFVs without ATGM", - [5] = "Tanks & IFV with ATGM", - [6] = "Modern Tanks", - [7] = "AAA", - [8] = "IR Guided SAMs", - [9] = "SR SAMs", - [10] = "MR SAMs", - [11] = "LR SAMs" - } - - - if Attributes["LR SAM"] then ThreatLevel = 10 - elseif Attributes["MR SAM"] then ThreatLevel = 9 - elseif Attributes["SR SAM"] and - not Attributes["IR Guided SAM"] then ThreatLevel = 8 - elseif ( Attributes["SR SAM"] or Attributes["MANPADS"] ) and - Attributes["IR Guided SAM"] then ThreatLevel = 7 - elseif Attributes["AAA"] then ThreatLevel = 6 - elseif Attributes["Modern Tanks"] then ThreatLevel = 5 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - Attributes["ATGM"] then ThreatLevel = 4 - elseif ( Attributes["Tanks"] or Attributes["IFV"] ) and - not Attributes["ATGM"] then ThreatLevel = 3 - elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then ThreatLevel = 2 - elseif Attributes["Infantry"] or Attributes["EWR"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsAir() then - - local ThreatLevels = { - [1] = "Unarmed", - [2] = "Tanker", - [3] = "AWACS", - [4] = "Transport Helicopter", - [5] = "UAV", - [6] = "Bomber", - [7] = "Strategic Bomber", - [8] = "Attack Helicopter", - [9] = "Battleplane", - [10] = "Multirole Fighter", - [11] = "Fighter" - } - - - if Attributes["Fighters"] then ThreatLevel = 10 - elseif Attributes["Multirole fighters"] then ThreatLevel = 9 - elseif Attributes["Interceptors"] then ThreatLevel = 9 - elseif Attributes["Battleplanes"] then ThreatLevel = 8 - elseif Attributes["Battle airplanes"] then ThreatLevel = 8 - elseif Attributes["Attack helicopters"] then ThreatLevel = 7 - elseif Attributes["Strategic bombers"] then ThreatLevel = 6 - elseif Attributes["Bombers"] then ThreatLevel = 5 - elseif Attributes["UAVs"] then ThreatLevel = 4 - elseif Attributes["Transport helicopters"] then ThreatLevel = 3 - elseif Attributes["AWACS"] then ThreatLevel = 2 - elseif Attributes["Tankers"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - - if self:IsShip() then - - --["Aircraft Carriers"] = {"Heavy armed ships",}, - --["Cruisers"] = {"Heavy armed ships",}, - --["Destroyers"] = {"Heavy armed ships",}, - --["Frigates"] = {"Heavy armed ships",}, - --["Corvettes"] = {"Heavy armed ships",}, - --["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, - --["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, - --["Armed ships"] = {"Ships"}, - --["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, - - local ThreatLevels = { - [1] = "Unarmed ship", - [2] = "Light armed ships", - [3] = "Corvettes", - [4] = "", - [5] = "Frigates", - [6] = "", - [7] = "Cruiser", - [8] = "", - [9] = "Destroyer", - [10] = "", - [11] = "Aircraft Carrier" - } - - - if Attributes["Aircraft Carriers"] then ThreatLevel = 10 - elseif Attributes["Destroyers"] then ThreatLevel = 8 - elseif Attributes["Cruisers"] then ThreatLevel = 6 - elseif Attributes["Frigates"] then ThreatLevel = 4 - elseif Attributes["Corvettes"] then ThreatLevel = 2 - elseif Attributes["Light armed ships"] then ThreatLevel = 1 - end - - ThreatText = ThreatLevels[ThreatLevel+1] - end - end + local ThreatLevel = 0 + local ThreatText = "" - return ThreatLevel, ThreatText + local Descriptor = self:GetDesc() + + if Descriptor then + + local Attributes = Descriptor.attributes + + if self:IsGround() then + + local ThreatLevels = { + [1] = "Unarmed", + [2] = "Infantry", + [3] = "Old Tanks & APCs", + [4] = "Tanks & IFVs without ATGM", + [5] = "Tanks & IFV with ATGM", + [6] = "Modern Tanks", + [7] = "AAA", + [8] = "IR Guided SAMs", + [9] = "SR SAMs", + [10] = "MR SAMs", + [11] = "LR SAMs" + } + + if Attributes["LR SAM"] then + ThreatLevel = 10 + elseif Attributes["MR SAM"] then + ThreatLevel = 9 + elseif Attributes["SR SAM"] and + not Attributes["IR Guided SAM"] then + ThreatLevel = 8 + elseif (Attributes["SR SAM"] or Attributes["MANPADS"]) and + Attributes["IR Guided SAM"] then + ThreatLevel = 7 + elseif Attributes["AAA"] then + ThreatLevel = 6 + elseif Attributes["Modern Tanks"] then + ThreatLevel = 5 + elseif (Attributes["Tanks"] or Attributes["IFV"]) and + Attributes["ATGM"] then + ThreatLevel = 4 + elseif (Attributes["Tanks"] or Attributes["IFV"]) and + not Attributes["ATGM"] then + ThreatLevel = 3 + elseif Attributes["Old Tanks"] or Attributes["APC"] or Attributes["Artillery"] then + ThreatLevel = 2 + elseif Attributes["Infantry"] or Attributes["EWR"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + + if self:IsAir() then + + local ThreatLevels = { + [1] = "Unarmed", + [2] = "Tanker", + [3] = "AWACS", + [4] = "Transport Helicopter", + [5] = "UAV", + [6] = "Bomber", + [7] = "Strategic Bomber", + [8] = "Attack Helicopter", + [9] = "Battleplane", + [10] = "Multirole Fighter", + [11] = "Fighter" + } + + if Attributes["Fighters"] then + ThreatLevel = 10 + elseif Attributes["Multirole fighters"] then + ThreatLevel = 9 + elseif Attributes["Interceptors"] then + ThreatLevel = 9 + elseif Attributes["Battleplanes"] then + ThreatLevel = 8 + elseif Attributes["Battle airplanes"] then + ThreatLevel = 8 + elseif Attributes["Attack helicopters"] then + ThreatLevel = 7 + elseif Attributes["Strategic bombers"] then + ThreatLevel = 6 + elseif Attributes["Bombers"] then + ThreatLevel = 5 + elseif Attributes["UAVs"] then + ThreatLevel = 4 + elseif Attributes["Transport helicopters"] then + ThreatLevel = 3 + elseif Attributes["AWACS"] then + ThreatLevel = 2 + elseif Attributes["Tankers"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + + if self:IsShip() then + + --["Aircraft Carriers"] = {"Heavy armed ships",}, + --["Cruisers"] = {"Heavy armed ships",}, + --["Destroyers"] = {"Heavy armed ships",}, + --["Frigates"] = {"Heavy armed ships",}, + --["Corvettes"] = {"Heavy armed ships",}, + --["Heavy armed ships"] = {"Armed ships", "Armed Air Defence", "HeavyArmoredUnits",}, + --["Light armed ships"] = {"Armed ships","NonArmoredUnits"}, + --["Armed ships"] = {"Ships"}, + --["Unarmed ships"] = {"Ships","HeavyArmoredUnits",}, + + local ThreatLevels = { + [1] = "Unarmed ship", + [2] = "Light armed ships", + [3] = "Corvettes", + [4] = "", + [5] = "Frigates", + [6] = "", + [7] = "Cruiser", + [8] = "", + [9] = "Destroyer", + [10] = "", + [11] = "Aircraft Carrier" + } + + if Attributes["Aircraft Carriers"] then + ThreatLevel = 10 + elseif Attributes["Destroyers"] then + ThreatLevel = 8 + elseif Attributes["Cruisers"] then + ThreatLevel = 6 + elseif Attributes["Frigates"] then + ThreatLevel = 4 + elseif Attributes["Corvettes"] then + ThreatLevel = 2 + elseif Attributes["Light armed ships"] then + ThreatLevel = 1 + end + + ThreatText = ThreatLevels[ThreatLevel + 1] + end + end + + return ThreatLevel, ThreatText end @@ -1373,25 +1451,25 @@ end -- @return #UNIT self function UNIT:Explode(power, delay) - -- Default. - power=power or 100 - - local DCSUnit = self:GetDCSObject() - if DCSUnit then - - -- Check if delay or not. - if delay and delay>0 then - -- Delayed call. - SCHEDULER:New(nil, self.Explode, {self, power}, delay) - else - -- Create an explotion at the coordinate of the unit. - self:GetCoordinate():Explosion(power) + -- Default. + power = power or 100 + + local DCSUnit = self:GetDCSObject() + if DCSUnit then + + -- Check if delay or not. + if delay and delay > 0 then + -- Delayed call. + SCHEDULER:New(nil, self.Explode, { self, power }, delay) + else + -- Create an explotion at the coordinate of the unit. + self:GetCoordinate():Explosion(power) + end + + return self end - - return self - end - - return nil + + return nil end -- Is functions @@ -1404,25 +1482,25 @@ end -- @param Radius The radius in meters with the DCS Unit in the centre. -- @return true If the other DCS Unit is within the radius of the 2D point of the DCS Unit. -- @return #nil The DCS Unit is not existing or alive. -function UNIT:OtherUnitInRadius( AwaitUnit, Radius ) - self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) +function UNIT:OtherUnitInRadius(AwaitUnit, Radius) + --self:F2( { self.UnitName, AwaitUnit.UnitName, Radius } ) - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitVec3 = self:GetVec3() - local AwaitUnitVec3 = AwaitUnit:GetVec3() - - if (((UnitVec3.x - AwaitUnitVec3.x)^2 + (UnitVec3.z - AwaitUnitVec3.z)^2)^0.5 <= Radius) then - self:T3( "true" ) - return true - else - self:T3( "false" ) - return false + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitVec3 = self:GetVec3() + local AwaitUnitVec3 = AwaitUnit:GetVec3() + + if (((UnitVec3.x - AwaitUnitVec3.x) ^ 2 + (UnitVec3.z - AwaitUnitVec3.z) ^ 2) ^ 0.5 <= Radius) then + --self:T3( "true" ) + return true + else + --self:T3( "false" ) + return false + end end - end - return nil + return nil end @@ -1434,22 +1512,22 @@ end --- Returns if the unit is a friendly unit. -- @param #UNIT self -- @return #boolean IsFriendly evaluation result. -function UNIT:IsFriendly( FriendlyCoalition ) - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitCoalition = DCSUnit:getCoalition() - self:T3( { UnitCoalition, FriendlyCoalition } ) - - local IsFriendlyResult = ( UnitCoalition == FriendlyCoalition ) - - self:F( IsFriendlyResult ) - return IsFriendlyResult - end - - return nil +function UNIT:IsFriendly(FriendlyCoalition) + --self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitCoalition = DCSUnit:getCoalition() + --self:T3( { UnitCoalition, FriendlyCoalition } ) + + local IsFriendlyResult = (UnitCoalition == FriendlyCoalition) + + --self:F( IsFriendlyResult ) + return IsFriendlyResult + end + + return nil end --- Returns if the unit is of a ship category. @@ -1457,21 +1535,21 @@ end -- @param #UNIT self -- @return #boolean Ship category evaluation result. function UNIT:IsShip() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) - - local IsShipResult = ( UnitDescriptor.category == Unit.Category.SHIP ) - - self:T3( IsShipResult ) - return IsShipResult - end - - return nil + --self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + --self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) + + local IsShipResult = (UnitDescriptor.category == Unit.Category.SHIP) + + --self:T3( IsShipResult ) + return IsShipResult + end + + return nil end --- Returns true if the UNIT is in the air. @@ -1479,143 +1557,147 @@ end -- @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(NoHeloCheck) - self:F2( self.UnitName ) + --self:F2( self.UnitName ) - -- Get DCS unit object. - local DCSUnit = self:GetDCSObject() --DCS#Unit - - if DCSUnit then + -- Get DCS unit object. + local DCSUnit = self:GetDCSObject() --DCS#Unit - -- Get DCS result of whether unit is in air or not. - local UnitInAir = DCSUnit:inAir() + if DCSUnit then - -- Get unit category. - local UnitCategory = DCSUnit:getDesc().category + -- Get DCS result of whether unit is in air or not. + local UnitInAir = DCSUnit:inAir() - -- 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 and (not NoHeloCheck) then - local VelocityVec3 = DCSUnit:getVelocity() - local Velocity = UTILS.VecNorm(VelocityVec3) - local Coordinate = DCSUnit:getPoint() - local LandHeight = land.getHeight( { x = Coordinate.x, y = Coordinate.z } ) - local Height = Coordinate.y - LandHeight - if Velocity < 1 and Height <= 60 then - UnitInAir = false - end - end - - self:T3( UnitInAir ) - return UnitInAir - end - - return nil -end + -- Get unit category. + local UnitCategory = DCSUnit:getDesc().category -do -- Event Handling - - --- Subscribe to a DCS Event. - -- @param #UNIT self - -- @param Core.Event#EVENTS EventID Event ID. - -- @param #function EventFunction (Optional) The function to be called when the event occurs for the unit. - -- @return #UNIT self - function UNIT:HandleEvent(EventID, EventFunction) - - self:EventDispatcher():OnEventForUnit(self:GetName(), EventFunction, self, EventID) - - return self - end - - --- UnSubscribe to a DCS event. - -- @param #UNIT self - -- @param Core.Event#EVENTS EventID Event ID. - -- @return #UNIT self - function UNIT:UnHandleEvent(EventID) - - --self:EventDispatcher():RemoveForUnit( self:GetName(), self, EventID ) - - -- Fixes issue #1365 https://github.com/FlightControl-Master/MOOSE/issues/1365 - self:EventDispatcher():RemoveEvent(self, EventID) - - return self - end - - --- Reset the subscriptions. - -- @param #UNIT self - -- @return #UNIT - function UNIT:ResetEvents() - - self:EventDispatcher():Reset( self ) - - return self - end - -end - -do -- Detection - - --- Returns if a unit is detecting the TargetUnit. - -- @param #UNIT self - -- @param #UNIT TargetUnit - -- @return #boolean true If the TargetUnit is detected by the unit, otherwise false. - function UNIT:IsDetected( TargetUnit ) --R2.1 - - local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = self:IsTargetDetected( TargetUnit:GetDCSObject() ) - - return TargetIsDetected - end - - --- Returns if a unit has Line of Sight (LOS) with the TargetUnit. - -- @param #UNIT self - -- @param #UNIT TargetUnit - -- @return #boolean true If the TargetUnit has LOS with the unit, otherwise false. - function UNIT:IsLOS( TargetUnit ) --R2.1 - - local IsLOS = self:GetPointVec3():IsLOS( TargetUnit:GetPointVec3() ) - - return IsLOS - end - - --- Forces the unit to become aware of the specified target, without the unit manually detecting the other unit itself. - -- Applies only to a Unit Controller. Cannot be used at the group level. - -- @param #UNIT self - -- @param #UNIT TargetUnit The unit to be known. - -- @param #boolean TypeKnown The target type is known. If *false*, the type is not known. - -- @param #boolean DistanceKnown The distance to the target is known. If *false*, distance is unknown. - function UNIT:KnowUnit(TargetUnit, TypeKnown, DistanceKnown) - - -- Defaults. - if TypeKnown~=false then - TypeKnown=true - end - if DistanceKnown~=false then - DistanceKnown=true - end - - local DCSControllable = self:GetDCSObject() - - if DCSControllable then - - local Controller = DCSControllable:getController() --self:_GetController() - - if Controller then - - local object=TargetUnit:GetDCSObject() - - if object then - - self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s", self:GetName(), TargetUnit:GetName(), tostring(TypeKnown), tostring(DistanceKnown))) - - Controller:knowTarget(object, TypeKnown, DistanceKnown) - + -- 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 acknowledge 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 and (not NoHeloCheck) then + local VelocityVec3 = DCSUnit:getVelocity() + local Velocity = UTILS.VecNorm(VelocityVec3) + local Coordinate = DCSUnit:getPoint() + local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z }) + local Height = Coordinate.y - LandHeight + if Velocity < 1 and Height <= 60 then + UnitInAir = false + end end - - end - + + --self:T3( UnitInAir ) + return UnitInAir + end + + return nil +end + +do + -- Event Handling + + --- Subscribe to a DCS Event. + -- @param #UNIT self + -- @param Core.Event#EVENTS EventID Event ID. + -- @param #function EventFunction (Optional) The function to be called when the event occurs for the unit. + -- @return #UNIT self + function UNIT:HandleEvent(EventID, EventFunction) + + self:EventDispatcher():OnEventForUnit(self:GetName(), EventFunction, self, EventID) + + return self + end + + --- UnSubscribe to a DCS event. + -- @param #UNIT self + -- @param Core.Event#EVENTS EventID Event ID. + -- @return #UNIT self + function UNIT:UnHandleEvent(EventID) + + --self:EventDispatcher():RemoveForUnit( self:GetName(), self, EventID ) + + -- Fixes issue #1365 https://github.com/FlightControl-Master/MOOSE/issues/1365 + self:EventDispatcher():RemoveEvent(self, EventID) + + return self + end + + --- Reset the subscriptions. + -- @param #UNIT self + -- @return #UNIT + function UNIT:ResetEvents() + + self:EventDispatcher():Reset(self) + + return self + end + +end + +do + -- Detection + + --- Returns if a unit is detecting the TargetUnit. + -- @param #UNIT self + -- @param #UNIT TargetUnit + -- @return #boolean true If the TargetUnit is detected by the unit, otherwise false. + function UNIT:IsDetected(TargetUnit) + --R2.1 + + local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = self:IsTargetDetected(TargetUnit:GetDCSObject()) + + return TargetIsDetected + end + + --- Returns if a unit has Line of Sight (LOS) with the TargetUnit. + -- @param #UNIT self + -- @param #UNIT TargetUnit + -- @return #boolean true If the TargetUnit has LOS with the unit, otherwise false. + function UNIT:IsLOS(TargetUnit) + --R2.1 + + local IsLOS = self:GetPointVec3():IsLOS(TargetUnit:GetPointVec3()) + + return IsLOS + end + + --- Forces the unit to become aware of the specified target, without the unit manually detecting the other unit itself. + -- Applies only to a Unit Controller. Cannot be used at the group level. + -- @param #UNIT self + -- @param #UNIT TargetUnit The unit to be known. + -- @param #boolean TypeKnown The target type is known. If *false*, the type is not known. + -- @param #boolean DistanceKnown The distance to the target is known. If *false*, distance is unknown. + function UNIT:KnowUnit(TargetUnit, TypeKnown, DistanceKnown) + + -- Defaults. + if TypeKnown ~= false then + TypeKnown = true + end + if DistanceKnown ~= false then + DistanceKnown = true + end + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local Controller = DCSControllable:getController() --self:_GetController() + + if Controller then + + local object = TargetUnit:GetDCSObject() + + if object then + + self:I(string.format("Unit %s now knows target unit %s. Type known=%s, distance known=%s", self:GetName(), TargetUnit:GetName(), tostring(TypeKnown), tostring(DistanceKnown))) + + Controller:knowTarget(object, TypeKnown, DistanceKnown) + + end + + end + + end + end - - end end @@ -1624,26 +1706,26 @@ end -- @return #table Table of the unit template (deep copy) or #nil. function UNIT:GetTemplate() - local group=self:GetGroup() - - local name=self:GetName() - - if group then - local template=group:GetTemplate() - - if template then - - for _,unit in pairs(template.units) do - - if unit.name==name then - return UTILS.DeepCopy(unit) + local group = self:GetGroup() + + local name = self:GetName() + + if group then + local template = group:GetTemplate() + + if template then + + for _, unit in pairs(template.units) do + + if unit.name == name then + return UTILS.DeepCopy(unit) + end + end + end - end - - end - end - - return nil + end + + return nil end @@ -1659,13 +1741,13 @@ end -- @return #table Payload table (deep copy) or #nil. function UNIT:GetTemplatePayload() - local unit=self:GetTemplate() - - if unit then - return unit.payload - end - - return nil + local unit = self:GetTemplate() + + if unit then + return unit.payload + end + + return nil end --- Get the pylons table from a unit's template. This can be a complex table depending on the weapons the unit is carrying. @@ -1673,13 +1755,13 @@ end -- @return #table Table of pylons (deepcopy) or #nil. function UNIT:GetTemplatePylons() - local payload=self:GetTemplatePayload() - - if payload then - return payload.pylons - end + local payload = self:GetTemplatePayload() - return nil + if payload then + return payload.pylons + end + + return nil end --- Get the fuel of the unit from its template. @@ -1687,13 +1769,13 @@ end -- @return #number Fuel of unit in kg. function UNIT:GetTemplateFuel() - local payload=self:GetTemplatePayload() - - if payload then - return payload.fuel - end + local payload = self:GetTemplatePayload() - return nil + if payload then + return payload.fuel + end + + return nil end --- GROUND - Switch on/off radar emissions of a unit. @@ -1701,29 +1783,32 @@ end -- @param #boolean switch If true, emission is enabled. If false, emission is disabled. -- @return #UNIT self function UNIT:EnableEmission(switch) - self:F2( self.UnitName ) - - local switch = switch or false - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - - DCSUnit:enableEmission(switch) + --self:F2( self.UnitName ) - end + local switch = switch or false - return self + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + + DCSUnit:enableEmission(switch) + + end + + return self end --- Get skill from Unit. -- @param #UNIT self -- @return #string Skill String of skill name. function UNIT:GetSkill() - self:F2( self.UnitName ) - local name = self.UnitName - local skill = _DATABASE.Templates.Units[name].Template.skill or "Random" - return skill + --self:F2( self.UnitName ) + local name = self.UnitName + local skill = "Random" + if _DATABASE.Templates.Units[name] and _DATABASE.Templates.Units[name].Template and _DATABASE.Templates.Units[name].Template.skill then + skill = _DATABASE.Templates.Units[name].Template.skill or "Random" + end + return skill end --- Get Link16 STN or SADL TN and other datalink info from Unit, if any. @@ -1733,28 +1818,110 @@ end -- @return #string VCN Voice Callsign Number or nil if not set/capable. -- @return #string Lead If true, unit is Flight Lead, else false or nil. function UNIT:GetSTN() - self:F2(self.UnitName) - local STN = nil -- STN/TN - local VCL = nil -- VoiceCallsignLabel - local VCN = nil -- VoiceCallsignNumber - local FGL = false -- FlightGroupLeader - local template = self:GetTemplate() - if template.AddPropAircraft then - if template.AddPropAircraft.STN_L16 then - STN = template.AddPropAircraft.STN_L16 - elseif template.AddPropAircraft.SADL_TN then - STN = template.AddPropAircraft.SADL_TN + --self:F2(self.UnitName) + local STN = nil -- STN/TN + local VCL = nil -- VoiceCallsignLabel + local VCN = nil -- VoiceCallsignNumber + local FGL = false -- FlightGroupLeader + local template = self:GetTemplate() + if template then + if template.AddPropAircraft then + if template.AddPropAircraft.STN_L16 then + STN = template.AddPropAircraft.STN_L16 + elseif template.AddPropAircraft.SADL_TN then + STN = template.AddPropAircraft.SADL_TN + end + VCN = template.AddPropAircraft.VoiceCallsignNumber + VCL = template.AddPropAircraft.VoiceCallsignLabel + end + if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then + FGL = template.datalinks.Link16.settings.flightLead + end + -- A10CII + if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then + FGL = template.datalinks.SADL.settings.flightLead + end end - VCN = template.AddPropAircraft.VoiceCallsignNumber - VCL = template.AddPropAircraft.VoiceCallsignLabel - end - if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then - FGL = template.datalinks.Link16.settings.flightLead - end - -- A10CII - if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then - FGL = template.datalinks.SADL.settings.flightLead - end - - return STN, VCL, VCN, FGL + return STN, VCL, VCN, FGL +end + +do + -- AI methods + + --- Turns the AI On or Off for the UNIT. + -- @param #UNIT self + -- @param #boolean AIOnOff The value true turns the AI On, the value false turns the AI Off. + -- @return #UNIT The UNIT. + function UNIT:SetAIOnOff(AIOnOff) + + local DCSUnit = self:GetDCSObject() -- DCS#Group + + if DCSUnit then + local DCSController = DCSUnit:getController() -- DCS#Controller + if DCSController then + DCSController:setOnOff(AIOnOff) + return self + end + end + + return nil + end + + --- Turns the AI On for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOn() + + return self:SetAIOnOff(true) + end + + --- Turns the AI Off for the UNIT. + -- @param #UNIT self + -- @return #UNIT The UNIT. + function UNIT:SetAIOff() + + return self:SetAIOnOff(false) + end + +end + +--- [GROUND] Determine if a UNIT is a SAM unit, i.e. has radar or optical tracker and is no mobile AAA. +-- @param #UNIT self +-- @return #boolean IsSAM True if SAM, else false +function UNIT:IsSAM() + if self:HasSEAD() and self:IsGround() and (not self:HasAttribute("Mobile AAA")) then + return true + end + return false +end + +--- [GROUND] Determine if a UNIT is a EWR unit +-- @param #UNIT self +-- @return #boolean IsEWR True if EWR, else false +function UNIT:IsEWR() + if self:IsGround() then + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local attrs = DCSUnit:getDesc().attributes + return attrs["EWR"] == true + end + end + return false +end + +--- [GROUND] Determine if a UNIT is a AAA unit, i.e. has no radar or optical tracker but the AAA = true or the "Mobile AAA" = true attribute. +-- @param #UNIT self +-- @return #boolean IsAAA True if AAA, else false +function UNIT:IsAAA() + local unit = self -- Wrapper.Unit#UNIT + local desc = unit:GetDesc() or {} + local attr = desc.attributes or {} + if unit:HasSEAD() then + return false + end + if attr["AAA"] or attr["SAM related"] then + return true + end + return false end diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 3e75d8672..0ba988748 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -385,24 +385,26 @@ function WEAPON:GetTarget() --Target name local name=object:getName() - - -- Debug info. - self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category)) - - if category==Object.Category.UNIT then - - target=UNIT:FindByName(name) - - elseif category==Object.Category.STATIC then - - target=STATIC:FindByName(name, false) - - elseif category==Object.Category.SCENERY then - self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) - else - self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) + + if name then + + -- Debug info. + self:T(self.lid..string.format("Got Target Object %s, category=%d", name, category)) + + if category==Object.Category.UNIT then + + target=UNIT:FindByName(name) + + elseif category==Object.Category.STATIC then + + target=STATIC:FindByName(name, false) + + elseif category==Object.Category.SCENERY then + self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) + else + self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) + end end - end end diff --git a/Moose Development/docs-header.py b/Moose Development/docs-header.py index 72dfc6f28..a3f1cc29c 100644 --- a/Moose Development/docs-header.py +++ b/Moose Development/docs-header.py @@ -1,6 +1,7 @@ # import required module from pathlib import Path import os +import codecs # assign directory directory = '.' @@ -15,8 +16,9 @@ with open( os.path.dirname(__file__) + '/docs-header.html', 'r') as file: # that directory files = Path(directory).glob('*.html') for file in files: - # print(file) - with open(file, 'r') as fileread: + #print(file) + #with open(file, 'r') as fileread: + with codecs.open(file, 'r', encoding='utf-8', errors='ignore') as fileread: filedata = fileread.read() # Replace the target string filedata = filedata.replace( '', newhead ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 17c364613..d1263e908 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -3,7 +3,6 @@ Utilities/Utils.lua Utilities/Enums.lua Utilities/Profiler.lua Utilities/Templates.lua -Utilities/STTS.lua Utilities/FiFo.lua Utilities/Socket.lua @@ -17,19 +16,17 @@ 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 -Core/Velocity.lua Core/Message.lua Core/Fsm.lua Core/Spawn.lua Core/SpawnStatic.lua Core/Timer.lua -Core/Goal.lua Core/Spot.lua -Core/TextAndSound.lua +Core/MarkerOps_Base.lua +Core/Astar.lua Core/Condition.lua Core/Pathline.lua Core/ClientMenu.lua @@ -50,27 +47,19 @@ Wrapper/Marker.lua Wrapper/Weapon.lua Wrapper/Net.lua Wrapper/Storage.lua - -Cargo/Cargo.lua -Cargo/CargoUnit.lua -Cargo/CargoSlingload.lua -Cargo/CargoCrate.lua -Cargo/CargoGroup.lua +Wrapper/DynamicCargo.lua Functional/Scoring.lua Functional/CleanUp.lua Functional/Movement.lua Functional/Sead.lua 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 -Functional/ZoneGoal.lua -Functional/ZoneGoalCoalition.lua Functional/ZoneCaptureCoalition.lua Functional/Artillery.lua Functional/Suppression.lua @@ -85,6 +74,7 @@ Functional/AmmoTruck.lua Functional/ZoneGoalCargo.lua Functional/Tiresias.lua Functional/Stratego.lua +Functional/ClientWatch.lua Ops/Airboss.lua Ops/RecoveryTanker.lua @@ -118,46 +108,6 @@ Ops/ArmyGroup.lua Ops/OpsTransport.lua Ops/Target.lua -Navigation/Navaid.lua -Navigation/FlightPlan.lua - -AI/AI_Balancer.lua -AI/AI_Air.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_BAI.lua -AI/AI_A2G_CAS.lua -AI/AI_A2G_SEAD.lua -AI/AI_A2G_Dispatcher.lua -AI/AI_Patrol.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 -Actions/Act_Account.lua -Actions/Act_Assist.lua - Sound/UserSound.lua Sound/SoundOutput.lua Sound/Radio.lua @@ -165,21 +115,4 @@ Sound/RadioQueue.lua Sound/RadioSpeech.lua Sound/SRS.lua -Tasking/CommandCenter.lua -Tasking/Mission.lua -Tasking/Task.lua -Tasking/TaskInfo.lua -Tasking/Task_Manager.lua -Tasking/DetectionManager.lua -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_Transport.lua -Tasking/Task_Cargo_CSAR.lua -Tasking/Task_Cargo_Dispatcher.lua -Tasking/Task_Capture_Zone.lua -Tasking/Task_Capture_Dispatcher.lua - Globals.lua diff --git a/Moose Setup/Moose_Create.lua b/Moose Setup/Moose_Create.lua index a69a34a18..e060d8238 100644 --- a/Moose Setup/Moose_Create.lua +++ b/Moose Setup/Moose_Create.lua @@ -1,4 +1,4 @@ --- This routine is called from the LDT environment to create the Moose.lua file stub for use in .miz files. +--- This routine is called from the LDT environment to create the Moose.lua file stub for use in .miz files. local MooseDynamicStatic = arg[1] local MooseCommitHash = arg[2] diff --git a/docs/archive/classes-core.md b/docs/archive/classes-core.md index 2bf02a4fb..b9869b59e 100644 --- a/docs/archive/classes-core.md +++ b/docs/archive/classes-core.md @@ -169,10 +169,6 @@ Defines an extensive API to manage 3D points in the DCS World 3D simulation spac **Features:** * Provides a COORDINATE class, which allows to manage points in 3D space and perform various operations on it. - * Provides a POINT_VEC2 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a - Lat/Lon and Altitude perspective. - * Provides a POINT_VEC3 class, which is derived from COORDINATE, and allows to manage points in 3D space, but from a - X, Z and Y vector perspective. **The coordinate system classes are essential to understand. Learn this!** diff --git a/docs/beginner/hello-world.md b/docs/beginner/hello-world.md index 48f7bcaf4..d2416493c 100644 --- a/docs/beginner/hello-world.md +++ b/docs/beginner/hello-world.md @@ -172,7 +172,7 @@ compftable in filtering these informations fast. Now it is time to [create your own Hello world] mission. [Saved Games folder]: ../beginner/tipps-and-tricks.md#find-the-saved-games-folder -[hello-world demo mission]: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_MISSIONS/master/Core/Message/001-hello-world.miz +[001-hello-world.miz]: https://raw.githubusercontent.com/FlightControl-Master/MOOSE_MISSIONS/master/Core/Message/001-hello-world.miz [Core.Message]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/Documentation/Core.Message.html [Wikipedia:Class]: https://en.wikipedia.org/wiki/Class_(computer_programming) [create your own Hello world]: hello-world-build.md