Merge branch 'FF/Ops' into FF/OpsDev

This commit is contained in:
Frank 2024-04-26 14:33:05 +02:00
commit 5242754c60
39 changed files with 3693 additions and 1568 deletions

View File

@ -57,6 +57,7 @@ jobs:
- name: Update apt-get (needed for act docker image) - name: Update apt-get (needed for act docker image)
run: | run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get -qq update sudo apt-get -qq update
- name: Install tree - name: Install tree

View File

@ -1151,14 +1151,14 @@ do -- AI_A2A_DISPATCHER
local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured.
self:I( "Captured " .. AirbaseName ) self:T( "Captured " .. AirbaseName )
-- Now search for all squadrons located at the airbase, and sanitize them. -- Now search for all squadrons located at the airbase, and sanitize them.
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
if Squadron.AirbaseName == AirbaseName then if Squadron.AirbaseName == AirbaseName then
Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
Squadron.Captured = true Squadron.Captured = true
self:I( "Squadron " .. SquadronName .. " captured." ) self:T( "Squadron " .. SquadronName .. " captured." )
end end
end end
end end
@ -1828,7 +1828,7 @@ do -- AI_A2A_DISPATCHER
self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 )
self:I( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } ) self:T( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } )
-- Add the CAP to the EWR network. -- Add the CAP to the EWR network.
@ -2085,7 +2085,7 @@ do -- AI_A2A_DISPATCHER
Intercept.EngageCeilingAltitude = EngageCeilingAltitude Intercept.EngageCeilingAltitude = EngageCeilingAltitude
Intercept.EngageAltType = EngageAltType Intercept.EngageAltType = EngageAltType
self:I( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
end end
--- Set squadron GCI. --- Set squadron GCI.
@ -3000,17 +3000,17 @@ do -- AI_A2A_DISPATCHER
for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do
-- We only allow to ENGAGE targets as long as the Units on both sides are balanced. -- We only allow to ENGAGE targets as long as the Units on both sides are balanced.
if AttackerCount > DefenderCount then if AttackerCount > DefenderCount then
--self:I("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:") --self:T("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:")
if AIFriendly then if AIFriendly then
local classname = AIFriendly.ClassName or "No Class Name" local classname = AIFriendly.ClassName or "No Class Name"
local unitname = AIFriendly.IdentifiableName or "No Unit Name" local unitname = AIFriendly.IdentifiableName or "No Unit Name"
--self:I("Class Name: " .. classname) --self:T("Class Name: " .. classname)
--self:I("Unit Name: " .. unitname) --self:T("Unit Name: " .. unitname)
--self:I({AIFriendly}) --self:T({AIFriendly})
end end
local Friendly = nil local Friendly = nil
if AIFriendly and AIFriendly:IsAlive() then if AIFriendly and AIFriendly:IsAlive() then
--self:I("AIFriendly alive, getting GROUP") --self:T("AIFriendly alive, getting GROUP")
Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP
end end
@ -3952,7 +3952,7 @@ end
do do
--- @type AI_A2A_GCICAP -- @type AI_A2A_GCICAP
-- @extends #AI_A2A_DISPATCHER -- @extends #AI_A2A_DISPATCHER
--- Create an automatic air defence system for a coalition setting up GCI and CAP air defenses. --- Create an automatic air defence system for a coalition setting up GCI and CAP air defenses.
@ -4322,23 +4322,23 @@ do
-- Setup squadrons -- Setup squadrons
self:I( { Airbases = AirbaseNames } ) self:T( { Airbases = AirbaseNames } )
self:I( "Defining Templates for Airbases ..." ) self:T( "Defining Templates for Airbases ..." )
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE
local AirbaseName = Airbase:GetName() local AirbaseName = Airbase:GetName()
local AirbaseCoord = Airbase:GetCoordinate() local AirbaseCoord = Airbase:GetCoordinate()
local AirbaseZone = ZONE_RADIUS:New( "Airbase", AirbaseCoord:GetVec2(), 3000 ) local AirbaseZone = ZONE_RADIUS:New( "Airbase", AirbaseCoord:GetVec2(), 3000 )
local Templates = nil local Templates = nil
self:I( { Airbase = AirbaseName } ) self:T( { Airbase = AirbaseName } )
for TemplateID, Template in pairs( self.Templates:GetSet() ) do for TemplateID, Template in pairs( self.Templates:GetSet() ) do
local Template = Template -- Wrapper.Group#GROUP local Template = Template -- Wrapper.Group#GROUP
local TemplateCoord = Template:GetCoordinate() local TemplateCoord = Template:GetCoordinate()
if AirbaseZone:IsVec2InZone( TemplateCoord:GetVec2() ) then if AirbaseZone:IsVec2InZone( TemplateCoord:GetVec2() ) then
Templates = Templates or {} Templates = Templates or {}
table.insert( Templates, Template:GetName() ) table.insert( Templates, Template:GetName() )
self:I( { Template = Template:GetName() } ) self:T( { Template = Template:GetName() } )
end end
end end
if Templates then if Templates then
@ -4354,13 +4354,13 @@ do
self.CAPTemplates:FilterPrefixes( CapPrefixes ) self.CAPTemplates:FilterPrefixes( CapPrefixes )
self.CAPTemplates:FilterOnce() self.CAPTemplates:FilterOnce()
self:I( "Setting up CAP ..." ) self:T( "Setting up CAP ..." )
for CAPID, CAPTemplate in pairs( self.CAPTemplates:GetSet() ) do for CAPID, CAPTemplate in pairs( self.CAPTemplates:GetSet() ) do
local CAPZone = ZONE_POLYGON:New( CAPTemplate:GetName(), CAPTemplate ) local CAPZone = ZONE_POLYGON:New( CAPTemplate:GetName(), CAPTemplate )
-- Now find the closest airbase from the ZONE (start or center) -- Now find the closest airbase from the ZONE (start or center)
local AirbaseDistance = 99999999 local AirbaseDistance = 99999999
local AirbaseClosest = nil -- Wrapper.Airbase#AIRBASE local AirbaseClosest = nil -- Wrapper.Airbase#AIRBASE
self:I( { CAPZoneGroup = CAPID } ) self:T( { CAPZoneGroup = CAPID } )
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE
local AirbaseName = Airbase:GetName() local AirbaseName = Airbase:GetName()
@ -4368,7 +4368,7 @@ do
local Squadron = self.DefenderSquadrons[AirbaseName] local Squadron = self.DefenderSquadrons[AirbaseName]
if Squadron then if Squadron then
local Distance = AirbaseCoord:Get2DDistance( CAPZone:GetCoordinate() ) local Distance = AirbaseCoord:Get2DDistance( CAPZone:GetCoordinate() )
self:I( { AirbaseDistance = Distance } ) self:T( { AirbaseDistance = Distance } )
if Distance < AirbaseDistance then if Distance < AirbaseDistance then
AirbaseDistance = Distance AirbaseDistance = Distance
AirbaseClosest = Airbase AirbaseClosest = Airbase
@ -4376,7 +4376,7 @@ do
end end
end end
if AirbaseClosest then if AirbaseClosest then
self:I( { CAPAirbase = AirbaseClosest:GetName() } ) self:T( { CAPAirbase = AirbaseClosest:GetName() } )
self:SetSquadronCap( AirbaseClosest:GetName(), CAPZone, 6000, 10000, 500, 800, 800, 1200, "RADIO" ) self:SetSquadronCap( AirbaseClosest:GetName(), CAPZone, 6000, 10000, 500, 800, 800, 1200, "RADIO" )
self:SetSquadronCapInterval( AirbaseClosest:GetName(), CapLimit, 300, 600, 1 ) self:SetSquadronCapInterval( AirbaseClosest:GetName(), CapLimit, 300, 600, 1 )
end end
@ -4384,14 +4384,14 @@ do
-- Setup GCI. -- Setup GCI.
-- GCI is setup for all Squadrons. -- GCI is setup for all Squadrons.
self:I( "Setting up GCI ..." ) self:T( "Setting up GCI ..." )
for AirbaseID, AirbaseName in pairs( AirbaseNames ) do for AirbaseID, AirbaseName in pairs( AirbaseNames ) do
local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE local Airbase = _DATABASE:FindAirbase( AirbaseName ) -- Wrapper.Airbase#AIRBASE
local AirbaseName = Airbase:GetName() local AirbaseName = Airbase:GetName()
local Squadron = self.DefenderSquadrons[AirbaseName] local Squadron = self.DefenderSquadrons[AirbaseName]
self:F( { Airbase = AirbaseName } ) self:F( { Airbase = AirbaseName } )
if Squadron then if Squadron then
self:I( { GCIAirbase = AirbaseName } ) self:T( { GCIAirbase = AirbaseName } )
self:SetSquadronGci( AirbaseName, 800, 1200 ) self:SetSquadronGci( AirbaseName, 800, 1200 )
end end
end end

View File

@ -904,14 +904,14 @@ do -- AI_A2G_DISPATCHER
-- @type AI_A2G_DISPATCHER.DefenseCoordinates -- @type AI_A2G_DISPATCHER.DefenseCoordinates
-- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name.
--- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates -- @field #AI_A2G_DISPATCHER.DefenseCoordinates DefenseCoordinates
AI_A2G_DISPATCHER.DefenseCoordinates = {} AI_A2G_DISPATCHER.DefenseCoordinates = {}
--- Enumerator for spawns at airbases. --- Enumerator for spawns at airbases.
-- @type AI_A2G_DISPATCHER.Takeoff -- @type AI_A2G_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_A2G_DISPATCHER.Takeoff Takeoff -- @field #AI_A2G_DISPATCHER.Takeoff Takeoff
AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff AI_A2G_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defines Landing location. --- Defines Landing location.
@ -942,7 +942,7 @@ do -- AI_A2G_DISPATCHER
-- @type AI_A2G_DISPATCHER.DefenseQueue -- @type AI_A2G_DISPATCHER.DefenseQueue
-- @list<#AI_A2G_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... -- @list<#AI_A2G_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
--- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue -- @field #AI_A2G_DISPATCHER.DefenseQueue DefenseQueue
AI_A2G_DISPATCHER.DefenseQueue = {} AI_A2G_DISPATCHER.DefenseQueue = {}
--- Defense approach types. --- Defense approach types.
@ -1136,7 +1136,7 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:onafterStart( From, Event, To ) function AI_A2G_DISPATCHER:onafterStart( From, Event, To )
self:GetParent( self ).onafterStart( self, From, Event, To ) self:GetParent( self ).onafterStart( self, From, Event, To )
@ -1147,7 +1147,7 @@ do -- AI_A2G_DISPATCHER
for Resource = 1, DefenderSquadron.ResourceCount or 0 do for Resource = 1, DefenderSquadron.ResourceCount or 0 do
self:ResourcePark( DefenderSquadron ) self:ResourcePark( DefenderSquadron )
end end
self:I( "Parked resources for squadron " .. DefenderSquadron.Name ) self:T( "Parked resources for squadron " .. DefenderSquadron.Name )
end end
end end
@ -1201,7 +1201,7 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:ResourcePark( DefenderSquadron ) function AI_A2G_DISPATCHER:ResourcePark( DefenderSquadron )
local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) local TemplateID = math.random( 1, #DefenderSquadron.Spawn )
local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN
@ -1218,33 +1218,33 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_A2G_DISPATCHER:OnEventBaseCaptured( EventData ) function AI_A2G_DISPATCHER:OnEventBaseCaptured( EventData )
local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured.
self:I( "Captured " .. AirbaseName ) self:T( "Captured " .. AirbaseName )
-- Now search for all squadrons located at the airbase, and sanitize them. -- Now search for all squadrons located at the airbase, and sanitize them.
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
if Squadron.AirbaseName == AirbaseName then if Squadron.AirbaseName == AirbaseName then
Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
Squadron.Captured = true Squadron.Captured = true
self:I( "Squadron " .. SquadronName .. " captured." ) self:T( "Squadron " .. SquadronName .. " captured." )
end end
end end
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData ) function AI_A2G_DISPATCHER:OnEventCrashOrDead( EventData )
self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) self.Detection:ForgetDetectedUnit( EventData.IniUnitName )
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_A2G_DISPATCHER:OnEventLand( EventData ) function AI_A2G_DISPATCHER:OnEventLand( EventData )
self:F( "Landed" ) self:F( "Landed" )
@ -1261,7 +1261,7 @@ do -- AI_A2G_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender ) self:ResourcePark( Squadron )
return return
end end
if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then
@ -1273,7 +1273,7 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_A2G_DISPATCHER:OnEventEngineShutdown( EventData ) function AI_A2G_DISPATCHER:OnEventEngineShutdown( EventData )
local DefenderUnit = EventData.IniUnit local DefenderUnit = EventData.IniUnit
@ -1289,7 +1289,7 @@ do -- AI_A2G_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender ) self:ResourcePark( Squadron )
end end
end end
end end
@ -1297,7 +1297,7 @@ do -- AI_A2G_DISPATCHER
do -- Manage the defensive behaviour do -- Manage the defensive behaviour
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param #string DefenseCoordinateName The name of the coordinate to be defended by A2G defenses. -- @param #string DefenseCoordinateName The name of the coordinate to be defended by A2G defenses.
-- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by A2G defenses. -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by A2G defenses.
function AI_A2G_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) function AI_A2G_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate )
@ -1305,19 +1305,19 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:SetDefenseReactivityLow() function AI_A2G_DISPATCHER:SetDefenseReactivityLow()
self.DefenseReactivity = 0.05 self.DefenseReactivity = 0.05
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:SetDefenseReactivityMedium() function AI_A2G_DISPATCHER:SetDefenseReactivityMedium()
self.DefenseReactivity = 0.15 self.DefenseReactivity = 0.15
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:SetDefenseReactivityHigh() function AI_A2G_DISPATCHER:SetDefenseReactivityHigh()
self.DefenseReactivity = 0.5 self.DefenseReactivity = 0.5
end end
@ -1351,14 +1351,14 @@ do -- AI_A2G_DISPATCHER
-- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**.
-- 2. the **distance to any defense reference point**. -- 2. the **distance to any defense reference point**.
-- --
-- The **default** defense radius is defined as **400000** or **40km**. Override the default defense radius when the era of the warfare is early, or, -- The **default** defense radius is defined as **40000** or **40km**. Override the default defense radius when the era of the warfare is early, or,
-- when you don't want to let the AI_A2G_DISPATCHER react immediately when a certain border or area is not being crossed. -- when you don't want to let the AI_A2G_DISPATCHER react immediately when a certain border or area is not being crossed.
-- --
-- Use the method @{#AI_A2G_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons, -- Use the method @{#AI_A2G_DISPATCHER.SetDefendRadius}() to set a specific defend radius for all squadrons,
-- **the Defense Radius is defined for ALL squadrons which are operational.** -- **the Defense Radius is defined for ALL squadrons which are operational.**
-- --
-- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param #number DefenseRadius (Optional, Default = 200000) The defense radius to engage detected targets from the nearest capable and available squadron airbase. -- @param #number DefenseRadius (Optional, Default = 20000) The defense radius to engage detected targets from the nearest capable and available squadron airbase.
-- @return #AI_A2G_DISPATCHER -- @return #AI_A2G_DISPATCHER
-- @usage -- @usage
-- --
@ -1373,7 +1373,7 @@ do -- AI_A2G_DISPATCHER
-- --
function AI_A2G_DISPATCHER:SetDefenseRadius( DefenseRadius ) function AI_A2G_DISPATCHER:SetDefenseRadius( DefenseRadius )
self.DefenseRadius = DefenseRadius or 100000 self.DefenseRadius = DefenseRadius or 40000
self.Detection:SetAcceptRange( self.DefenseRadius ) self.Detection:SetAcceptRange( self.DefenseRadius )
@ -1868,7 +1868,7 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param #string SquadronName The squadron name. -- @param #string SquadronName The squadron name.
-- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps.
-- @usage -- @usage
@ -2144,7 +2144,7 @@ do -- AI_A2G_DISPATCHER
Sead.EngageAltType = EngageAltType Sead.EngageAltType = EngageAltType
Sead.Defend = true Sead.Defend = true
self:I( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
return self return self
end end
@ -2234,7 +2234,7 @@ do -- AI_A2G_DISPATCHER
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" )
self:I( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
end end
@ -2295,7 +2295,7 @@ do -- AI_A2G_DISPATCHER
Cas.EngageAltType = EngageAltType Cas.EngageAltType = EngageAltType
Cas.Defend = true Cas.Defend = true
self:I( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
return self return self
end end
@ -2385,7 +2385,7 @@ do -- AI_A2G_DISPATCHER
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" )
self:I( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
end end
@ -2446,7 +2446,7 @@ do -- AI_A2G_DISPATCHER
Bai.EngageAltType = EngageAltType Bai.EngageAltType = EngageAltType
Bai.Defend = true Bai.Defend = true
self:I( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
return self return self
end end
@ -2536,7 +2536,7 @@ do -- AI_A2G_DISPATCHER
self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" )
self:I( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:T( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } )
end end
@ -3369,7 +3369,7 @@ do -- AI_A2G_DISPATCHER
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) function AI_A2G_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size )
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName() local DefenderName = Defender:GetName()
@ -3380,7 +3380,7 @@ do -- AI_A2G_DISPATCHER
self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) function AI_A2G_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender )
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName() local DefenderName = Defender:GetName()
@ -3796,7 +3796,7 @@ do -- AI_A2G_DISPATCHER
Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To )
self:F({"LostControl", DefenderGroup:GetName()}) self:F({"LostControl", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
@ -3813,7 +3813,7 @@ do -- AI_A2G_DISPATCHER
end end
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action )
self:F({"Home", DefenderGroup:GetName()}) self:F({"Home", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
@ -3894,7 +3894,7 @@ do -- AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then if Squadron then
local FirstUnit = AttackSetUnit:GetFirst() local FirstUnit = AttackSetUnit:GetRandomSurely()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
if self.SetSendPlayerMessages then if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
@ -3933,7 +3933,7 @@ do -- AI_A2G_DISPATCHER
Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To )
self:F({"Defender LostControl", DefenderGroup:GetName()}) self:F({"Defender LostControl", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
@ -3950,7 +3950,7 @@ do -- AI_A2G_DISPATCHER
end end
end end
--- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action )
self:F({"Defender Home", DefenderGroup:GetName()}) self:F({"Defender Home", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )

View File

@ -9,7 +9,7 @@
-- @module AI.AI_Air -- @module AI.AI_Air
-- @image MOOSE.JPG -- @image MOOSE.JPG
--- @type AI_AIR -- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}. --- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
@ -264,7 +264,7 @@ function AI_AIR:New( AIGroup )
return self return self
end end
--- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm ) function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff() Fsm:Takeoff()
@ -446,13 +446,13 @@ function AI_AIR:onafterReturn( Controllable, From, Event, To )
end end
--- @param #AI_AIR self -- @param #AI_AIR self
function AI_AIR:onbeforeStatus() function AI_AIR:onbeforeStatus()
return self.CheckStatus return self.CheckStatus
end end
--- @param #AI_AIR self -- @param #AI_AIR self
function AI_AIR:onafterStatus() function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive() then if self.Controllable and self.Controllable:IsAlive() then
@ -465,7 +465,7 @@ function AI_AIR:onafterStatus()
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if DistanceFromHomeBase > self.DisengageRadius then if DistanceFromHomeBase > self.DisengageRadius then
self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" ) self:T( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 ) self:Hold( 300 )
RTB = false RTB = false
end end
@ -489,10 +489,10 @@ function AI_AIR:onafterStatus()
if Fuel < self.FuelThresholdPercentage then if Fuel < self.FuelThresholdPercentage then
if self.TankerName then if self.TankerName then
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel() self:Refuel()
else else
self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@ -518,7 +518,7 @@ function AI_AIR:onafterStatus()
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue. -- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage. -- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) self:T( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged() self:Damaged()
RTB = true RTB = true
self:SetStatusOff() self:SetStatusOff()
@ -536,7 +536,7 @@ function AI_AIR:onafterStatus()
if Damage ~= InitialLife then if Damage ~= InitialLife then
self:Damaged() self:Damaged()
else else
self:I( self.Controllable:GetName() .. " control lost! " ) self:T( self.Controllable:GetName() .. " control lost! " )
self:LostControl() self:LostControl()
end end
@ -560,7 +560,7 @@ function AI_AIR:onafterStatus()
end end
--- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBRoute( AIGroup, Fsm ) function AI_AIR.RTBRoute( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } ) AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
@ -571,7 +571,7 @@ function AI_AIR.RTBRoute( AIGroup, Fsm )
end end
--- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBHold( AIGroup, Fsm ) function AI_AIR.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } ) AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
@ -598,7 +598,7 @@ function AI_AIR:SetRTBSpeedFactors(MinFactor,MaxFactor)
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRTB( AIGroup, From, Event, To ) function AI_AIR:onafterRTB( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
@ -617,7 +617,10 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
--- Calculate the target route point. --- Calculate the target route point.
local FromCoord = AIGroup:GetCoordinate() local FromCoord = AIGroup:GetCoordinate()
if not FromCoord then return end
local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!) local ToTargetCoord = self.HomeAirbase:GetCoordinate() -- coordinate is on land height(!)
local ToTargetVec3 = ToTargetCoord:GetVec3() local ToTargetVec3 = ToTargetCoord:GetVec3()
ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground ToTargetVec3.y = ToTargetCoord:GetLandHeight()+3000 -- let's set this 1000m/3000 feet above ground
local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 ) local ToTargetCoord2 = COORDINATE:NewFromVec3( ToTargetVec3 )
@ -638,13 +641,13 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = ToTargetCoord2 local ToAirbaseCoord = ToTargetCoord2
if Distance < 5000 then if Distance < 5000 then
self:I( "RTB and near the airbase!" ) self:T( "RTB and near the airbase!" )
self:Home() self:Home()
return return
end end
if not AIGroup:InAir() == true then if not AIGroup:InAir() == true then
self:I( "Not anymore in the air, considered Home." ) self:T( "Not anymore in the air, considered Home." )
self:Home() self:Home()
return return
end end
@ -686,12 +689,12 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHome( AIGroup, From, Event, To ) function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) self:T( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
end end
@ -700,15 +703,17 @@ end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime ) function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) self:T( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local Coordinate = AIGroup:GetCoordinate()
if Coordinate == nil then return end
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed, Coordinate )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) ) local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self ) local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
@ -722,17 +727,17 @@ function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
end end
--- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.Resume( AIGroup, Fsm ) function AI_AIR.Resume( AIGroup, Fsm )
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } ) AIGroup:T( { "AI_AIR.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then if AIGroup:IsAlive() then
Fsm:__RTB( Fsm.TaskDelay ) Fsm:__RTB( Fsm.TaskDelay )
end end
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup -- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
@ -744,7 +749,7 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then if Tanker and Tanker:IsAlive() and Tanker:IsAirPlane() then
self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName ) self:T( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
local RefuelRoute = {} local RefuelRoute = {}
@ -798,13 +803,13 @@ end
--- @param #AI_AIR self -- @param #AI_AIR self
function AI_AIR:onafterDead() function AI_AIR:onafterDead()
self:SetStatusOff() self:SetStatusOff()
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnCrash( EventData ) function AI_AIR:OnCrash( EventData )
@ -815,7 +820,7 @@ function AI_AIR:OnCrash( EventData )
end end
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnEjection( EventData ) function AI_AIR:OnEjection( EventData )
@ -824,7 +829,7 @@ function AI_AIR:OnEjection( EventData )
end end
end end
--- @param #AI_AIR self -- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnPilotDead( EventData ) function AI_AIR:OnPilotDead( EventData )

View File

@ -900,14 +900,14 @@ do -- AI_AIR_DISPATCHER
-- @type AI_AIR_DISPATCHER.DefenseCoordinates -- @type AI_AIR_DISPATCHER.DefenseCoordinates
-- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name.
--- @field #AI_AIR_DISPATCHER.DefenseCoordinates DefenseCoordinates -- @field #AI_AIR_DISPATCHER.DefenseCoordinates DefenseCoordinates
AI_AIR_DISPATCHER.DefenseCoordinates = {} AI_AIR_DISPATCHER.DefenseCoordinates = {}
--- Enumerator for spawns at airbases --- Enumerator for spawns at airbases
-- @type AI_AIR_DISPATCHER.Takeoff -- @type AI_AIR_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_AIR_DISPATCHER.Takeoff Takeoff -- @field #AI_AIR_DISPATCHER.Takeoff Takeoff
AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defnes Landing location. --- Defnes Landing location.
@ -938,7 +938,7 @@ do -- AI_AIR_DISPATCHER
-- @type AI_AIR_DISPATCHER.DefenseQueue -- @type AI_AIR_DISPATCHER.DefenseQueue
-- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ... -- @list<#AI_AIR_DISPATCHER.DefenseQueueItem> DefenseQueueItem A list of all defenses being queued ...
--- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue -- @field #AI_AIR_DISPATCHER.DefenseQueue DefenseQueue
AI_AIR_DISPATCHER.DefenseQueue = {} AI_AIR_DISPATCHER.DefenseQueue = {}
--- Defense approach types --- Defense approach types
@ -1130,7 +1130,7 @@ do -- AI_AIR_DISPATCHER
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:onafterStart( From, Event, To ) function AI_AIR_DISPATCHER:onafterStart( From, Event, To )
self:GetParent( self ).onafterStart( self, From, Event, To ) self:GetParent( self ).onafterStart( self, From, Event, To )
@ -1141,7 +1141,7 @@ do -- AI_AIR_DISPATCHER
for Resource = 1, DefenderSquadron.ResourceCount or 0 do for Resource = 1, DefenderSquadron.ResourceCount or 0 do
self:ResourcePark( DefenderSquadron ) self:ResourcePark( DefenderSquadron )
end end
self:I( "Parked resources for squadron " .. DefenderSquadron.Name ) self:T( "Parked resources for squadron " .. DefenderSquadron.Name )
end end
end end
@ -1194,7 +1194,7 @@ do -- AI_AIR_DISPATCHER
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:ResourcePark( DefenderSquadron ) function AI_AIR_DISPATCHER:ResourcePark( DefenderSquadron )
local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) local TemplateID = math.random( 1, #DefenderSquadron.Spawn )
local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN
@ -1211,31 +1211,31 @@ do -- AI_AIR_DISPATCHER
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventBaseCaptured( EventData ) function AI_AIR_DISPATCHER:OnEventBaseCaptured( EventData )
local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured. local AirbaseName = EventData.PlaceName -- The name of the airbase that was captured.
self:I( "Captured " .. AirbaseName ) self:T( "Captured " .. AirbaseName )
-- Now search for all squadrons located at the airbase, and sanitize them. -- Now search for all squadrons located at the airbase, and sanitize them.
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
if Squadron.AirbaseName == AirbaseName then if Squadron.AirbaseName == AirbaseName then
Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning. Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
Squadron.Captured = true Squadron.Captured = true
self:I( "Squadron " .. SquadronName .. " captured." ) self:T( "Squadron " .. SquadronName .. " captured." )
end end
end end
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventCrashOrDead( EventData ) function AI_AIR_DISPATCHER:OnEventCrashOrDead( EventData )
self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) self.Detection:ForgetDetectedUnit( EventData.IniUnitName )
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventLand( EventData ) function AI_AIR_DISPATCHER:OnEventLand( EventData )
self:F( "Landed" ) self:F( "Landed" )
@ -1252,7 +1252,7 @@ do -- AI_AIR_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender ) self:ResourcePark( Squadron )
return return
end end
if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then
@ -1263,7 +1263,7 @@ do -- AI_AIR_DISPATCHER
end end
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR_DISPATCHER:OnEventEngineShutdown( EventData ) function AI_AIR_DISPATCHER:OnEventEngineShutdown( EventData )
local DefenderUnit = EventData.IniUnit local DefenderUnit = EventData.IniUnit
@ -1279,31 +1279,31 @@ do -- AI_AIR_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ResourcePark( Squadron, Defender ) self:ResourcePark( Squadron )
end end
end end
end end
do -- Manage the defensive behaviour do -- Manage the defensive behaviour
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #string DefenseCoordinateName The name of the coordinate to be defended by AIR defenses. -- @param #string DefenseCoordinateName The name of the coordinate to be defended by AIR defenses.
-- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by AIR defenses. -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by AIR defenses.
function AI_AIR_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) function AI_AIR_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate )
self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:SetDefenseReactivityLow() function AI_AIR_DISPATCHER:SetDefenseReactivityLow()
self.DefenseReactivity = 0.05 self.DefenseReactivity = 0.05
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:SetDefenseReactivityMedium() function AI_AIR_DISPATCHER:SetDefenseReactivityMedium()
self.DefenseReactivity = 0.15 self.DefenseReactivity = 0.15
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
function AI_AIR_DISPATCHER:SetDefenseReactivityHigh() function AI_AIR_DISPATCHER:SetDefenseReactivityHigh()
self.DefenseReactivity = 0.5 self.DefenseReactivity = 0.5
end end
@ -1867,7 +1867,7 @@ do -- AI_AIR_DISPATCHER
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name. -- @param #string SquadronName The squadron name.
-- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps. -- @param #number TakeoffInterval Only Takeoff new units each specified interval in seconds in 10 seconds steps.
-- @usage -- @usage
@ -2769,7 +2769,7 @@ do -- AI_AIR_DISPATCHER
-- TODO: Need to model the resources in a squadron. -- TODO: Need to model the resources in a squadron.
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron
function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size )
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
@ -2782,7 +2782,7 @@ do -- AI_AIR_DISPATCHER
self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron
function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender )
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
@ -2795,7 +2795,7 @@ do -- AI_AIR_DISPATCHER
self:F( { DefenderName = DefenderName, SquadronResourceCount = SquadronResourceCount } ) self:F( { DefenderName = DefenderName, SquadronResourceCount = SquadronResourceCount } )
end end
--- @param #AI_AIR_DISPATCHER self -- @param #AI_AIR_DISPATCHER self
-- @param Wrapper.Group#GROUP Defender -- @param Wrapper.Group#GROUP Defender
-- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron. -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron.
function AI_AIR_DISPATCHER:GetSquadronFromDefender( Defender ) function AI_AIR_DISPATCHER:GetSquadronFromDefender( Defender )

View File

@ -13,8 +13,8 @@
--- @type AI_AIR_ENGAGE -- @type AI_AIR_ENGAGE
-- @extends AI.AI_Air#AI_AIR -- @extends AI.AI_AIR#AI_AIR
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
@ -351,7 +351,7 @@ function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To )
end end
--- @param #AI_AIR_ENGAGE self -- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
@ -361,7 +361,7 @@ function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
--self:SetDetectionOff() --self:SetDetectionOff()
end end
--- @param #AI_AIR_ENGAGE self -- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
@ -374,7 +374,7 @@ function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
end end
end end
--- @param #AI_AIR_ENGAGE self -- @param #AI_AIR_ENGAGE self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_AIR_ENGAGE:OnEventDead( EventData ) function AI_AIR_ENGAGE:OnEventDead( EventData )
self:F( { "EventDead", EventData } ) self:F( { "EventDead", EventData } )
@ -387,9 +387,9 @@ function AI_AIR_ENGAGE:OnEventDead( EventData )
end end
--- @param Wrapper.Group#GROUP AIControllable -- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit ) function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName()))) Fsm:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit ) Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
@ -397,14 +397,14 @@ function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
end end
--- @param #AI_AIR_ENGAGE self -- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. -- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
-- @param #string To The To State string. -- @param #string To The To State string.
-- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked. -- @param Core.Set#SET_UNIT AttackSetUnit Unit set to be attacked.
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } ) self:T( { DefenderGroup, From, Event, To, AttackSetUnit } )
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
@ -426,7 +426,13 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
local DefenderCoord = DefenderGroup:GetPointVec3() local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3()
if TargetCoord == nil then
self:Return()
return
end
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
@ -435,12 +441,12 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes! -- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
if TargetDistance <= EngageDistance * 9 then if TargetDistance <= EngageDistance * 9 then
--self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000)) --self:T(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
self:__Engage( 0.1, AttackSetUnit ) self:__Engage( 0.1, AttackSetUnit )
else else
--self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000)) --self:T(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
local EngageRoute = {} local EngageRoute = {}
local AttackTasks = {} local AttackTasks = {}
@ -472,16 +478,16 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
end end
else else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:T( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
end end
end end
--- @param Wrapper.Group#GROUP AIControllable -- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit ) function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName()))) Fsm:T(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local delay=Fsm.TaskDelay or 0.1 local delay=Fsm.TaskDelay or 0.1
@ -490,7 +496,7 @@ function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
end end
--- @param #AI_AIR_ENGAGE self -- @param #AI_AIR_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. -- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
-- @param #string Event The Event string. -- @param #string Event The Event string.
@ -516,7 +522,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
local DefenderCoord = DefenderGroup:GetPointVec3() local DefenderCoord = DefenderGroup:GetPointVec3()
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() local TargetCoord = AttackSetUnit:GetRandomSurely():GetPointVec3()
if not TargetCoord then if not TargetCoord then
self:Return() self:Return()
return return
@ -547,12 +553,12 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
if #AttackUnitTasks == 0 then if #AttackUnitTasks == 0 then
self:I( DefenderGroupName .. ": No valid targets found -> Going RTB") self:T( DefenderGroupName .. ": No valid targets found -> Going RTB")
self:Return() self:Return()
return return
else else
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance)) local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
self:I(text) self:T(text)
DefenderGroup:OptionROEOpenFire() DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire() DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat() DefenderGroup:OptionKeepWeaponsOnThreat()
@ -569,13 +575,13 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
end end
else else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling! -- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:I( DefenderGroupName .. ": No targets found -> returning.") self:T( DefenderGroupName .. ": No targets found -> returning.")
self:Return() self:Return()
return return
end end
end end
--- @param Wrapper.Group#GROUP AIEngage -- @param Wrapper.Group#GROUP AIEngage
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm ) function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
AIEngage:F( { "Resume:", AIEngage:GetName() } ) AIEngage:F( { "Resume:", AIEngage:GetName() } )

View File

@ -13,7 +13,7 @@
--- @type AI_AIR_SQUADRON -- @type AI_AIR_SQUADRON
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@ -38,7 +38,7 @@ AI_AIR_SQUADRON = {
-- @return #AI_AIR_SQUADRON -- @return #AI_AIR_SQUADRON
function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
self:I( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) self:T( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON

View File

@ -9,7 +9,7 @@
-- @module AI.AI_Cargo -- @module AI.AI_Cargo
-- @image Cargo.JPG -- @image Cargo.JPG
--- @type AI_CARGO -- @type AI_CARGO
-- @extends Core.Fsm#FSM_CONTROLLABLE -- @extends Core.Fsm#FSM_CONTROLLABLE
@ -547,7 +547,7 @@ function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit,
for _, CarrierUnit in pairs( Carrier:GetUnits() ) do for _, CarrierUnit in pairs( Carrier:GetUnits() ) do
local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT
local IsEmpty = CarrierUnit:IsCargoEmpty() local IsEmpty = CarrierUnit:IsCargoEmpty()
self:I({ IsEmpty = IsEmpty }) self:T({ IsEmpty = IsEmpty })
if not IsEmpty then if not IsEmpty then
AllUnloaded = false AllUnloaded = false
break break

View File

@ -116,7 +116,7 @@
-- @image AI_Cargo_Dispatcher.JPG -- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_DISPATCHER -- @type AI_CARGO_DISPATCHER
-- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. -- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo.
-- @field Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects. -- @field Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, CARGO_SLINGLOAD objects.
-- @field Core.Zone#SET_ZONE PickupZoneSet The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere. -- @field Core.Zone#SET_ZONE PickupZoneSet The set of pickup zones, which are used to where the cargo can be picked up by the carriers. If nil, then cargo can be picked up everywhere.
@ -1161,7 +1161,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor()
else else
local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.", local text=string.format("WARNING: Cargo %s is too heavy to be loaded into transport. Cargo weight %.1f > %.1f load capacity of carrier %s.",
tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName())) tostring(Cargo:GetName()), Cargo:GetWeight(), LargestLoadCapacity, tostring(Carrier:GetName()))
self:I(text) self:T(text)
end end
end end
end end

View File

@ -556,7 +556,7 @@ function AI_ESCORT:SetFlightMenuFormation( Formation )
if MenuFormation then if MenuFormation then
local Arguments = MenuFormation.Arguments local Arguments = MenuFormation.Arguments
--self:I({Arguments=unpack(Arguments)}) --self:T({Arguments=unpack(Arguments)})
local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu ) local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu )
local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation,
function ( self, Formation, ... ) function ( self, Formation, ... )

View File

@ -15,7 +15,7 @@
-- @image MOOSE.JPG -- @image MOOSE.JPG
--- @type AI_ESCORT_DISPATCHER -- @type AI_ESCORT_DISPATCHER
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -33,7 +33,7 @@ AI_ESCORT_DISPATCHER = {
ClassName = "AI_ESCORT_DISPATCHER", ClassName = "AI_ESCORT_DISPATCHER",
} }
--- @field #list -- @field #list
AI_ESCORT_DISPATCHER.AI_Escorts = {} AI_ESCORT_DISPATCHER.AI_Escorts = {}
@ -102,7 +102,7 @@ function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To )
end end
--- @param #AI_ESCORT_DISPATCHER self -- @param #AI_ESCORT_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER:OnEventExit( EventData ) function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
@ -110,11 +110,11 @@ function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
local PlayerGroup = EventData.IniGroup local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit local PlayerUnit = EventData.IniUnit
self:I({EscortAirbase= self.EscortAirbase } ) self:T({EscortAirbase= self.EscortAirbase } )
self:I({PlayerGroupName = PlayerGroupName } ) self:T({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup}) self:T({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()}) self:T({FirstGroup = self.CarrierSet:GetFirst()})
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then if self.CarrierSet:FindGroup( PlayerGroupName ) then
if self.AI_Escorts[PlayerGroupName] then if self.AI_Escorts[PlayerGroupName] then
@ -125,7 +125,7 @@ function AI_ESCORT_DISPATCHER:OnEventExit( EventData )
end end
--- @param #AI_ESCORT_DISPATCHER self -- @param #AI_ESCORT_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
@ -133,17 +133,17 @@ function AI_ESCORT_DISPATCHER:OnEventBirth( EventData )
local PlayerGroup = EventData.IniGroup local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit local PlayerUnit = EventData.IniUnit
self:I({EscortAirbase= self.EscortAirbase } ) self:T({EscortAirbase= self.EscortAirbase } )
self:I({PlayerGroupName = PlayerGroupName } ) self:T({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup}) self:T({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()}) self:T({FirstGroup = self.CarrierSet:GetFirst()})
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then if self.CarrierSet:FindGroup( PlayerGroupName ) then
if not self.AI_Escorts[PlayerGroupName] then if not self.AI_Escorts[PlayerGroupName] then
local LeaderUnit = PlayerUnit local LeaderUnit = PlayerUnit
local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot )
self:I({EscortGroup = EscortGroup}) self:T({EscortGroup = EscortGroup})
self:ScheduleOnce( 1, self:ScheduleOnce( 1,
function( EscortGroup ) function( EscortGroup )

View File

@ -652,15 +652,15 @@ function AI_PATROL_ZONE:onafterStart( Controllable, From, Event, To )
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Wrapper.Controllable#CONTROLLABLE Controllable+
function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To ) function AI_PATROL_ZONE:onbeforeDetect( Controllable, From, Event, To )
return self.DetectOn and self.DetectActivated return self.DetectOn and self.DetectActivated
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
--- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Wrapper.Controllable#CONTROLLABLE Controllable
function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To ) function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
local Detected = false local Detected = false
@ -705,7 +705,7 @@ function AI_PATROL_ZONE:onafterDetect( Controllable, From, Event, To )
end end
--- @param Wrapper.Controllable#CONTROLLABLE AIControllable -- @param Wrapper.Controllable#CONTROLLABLE AIControllable
-- This static method is called from the route path within the last task at the last waypoint of the Controllable. -- This static method is called from the route path within the last task at the last waypoint of the Controllable.
-- Note that this method is required, as triggers the next route when patrolling for the Controllable. -- Note that this method is required, as triggers the next route when patrolling for the Controllable.
function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable ) function AI_PATROL_ZONE:_NewPatrolRoute( AIControllable )
@ -822,13 +822,13 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onbeforeStatus() function AI_PATROL_ZONE:onbeforeStatus()
return self.CheckStatus return self.CheckStatus
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterStatus() function AI_PATROL_ZONE:onafterStatus()
self:F2() self:F2()
@ -838,7 +838,7 @@ function AI_PATROL_ZONE:onafterStatus()
local Fuel = self.Controllable:GetFuelMin() local Fuel = self.Controllable:GetFuelMin()
if Fuel < self.PatrolFuelThresholdPercentage then if Fuel < self.PatrolFuelThresholdPercentage then
self:I( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) self:T( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@ -852,7 +852,7 @@ function AI_PATROL_ZONE:onafterStatus()
-- TODO: Check GROUP damage function. -- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife() local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageThreshold then if Damage <= self.PatrolDamageThreshold then
self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) self:T( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true RTB = true
end end
@ -864,7 +864,7 @@ function AI_PATROL_ZONE:onafterStatus()
end end
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterRTB() function AI_PATROL_ZONE:onafterRTB()
self:F2() self:F2()
@ -903,13 +903,13 @@ function AI_PATROL_ZONE:onafterRTB()
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
function AI_PATROL_ZONE:onafterDead() function AI_PATROL_ZONE:onafterDead()
self:SetDetectionOff() self:SetDetectionOff()
self:SetStatusOff() self:SetStatusOff()
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnCrash( EventData ) function AI_PATROL_ZONE:OnCrash( EventData )
@ -920,7 +920,7 @@ function AI_PATROL_ZONE:OnCrash( EventData )
end end
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnEjection( EventData ) function AI_PATROL_ZONE:OnEjection( EventData )
@ -929,7 +929,7 @@ function AI_PATROL_ZONE:OnEjection( EventData )
end end
end end
--- @param #AI_PATROL_ZONE self -- @param #AI_PATROL_ZONE self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_PATROL_ZONE:OnPilotDead( EventData ) function AI_PATROL_ZONE:OnPilotDead( EventData )

View File

@ -1157,6 +1157,15 @@ function BASE:_Serialize(Arguments)
return text return text
end end
----- (Internal) Serialize arguments
---- @param #BASE self
---- @param #table Arguments
---- @return #string Text
--function BASE:_Serialize(Arguments)
-- local text=UTILS.BasicSerialize(Arguments)
-- return text
--end
--- Trace a function call. This function is private. --- Trace a function call. This function is private.
-- @param #BASE self -- @param #BASE self
-- @param Arguments A #table or any field. -- @param Arguments A #table or any field.

View File

@ -20,7 +20,7 @@
-- --
-- @module Core.ClientMenu -- @module Core.ClientMenu
-- @image Core_Menu.JPG -- @image Core_Menu.JPG
-- last change: Oct 2023 -- last change: Apr 2024
-- TODO -- TODO
---------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------
@ -51,6 +51,7 @@
-- @field #boolean Generic -- @field #boolean Generic
-- @field #boolean debug -- @field #boolean debug
-- @field #CLIENTMENUMANAGER Controller -- @field #CLIENTMENUMANAGER Controller
-- @field #active boolean
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- ---
@ -58,7 +59,7 @@
CLIENTMENU = { CLIENTMENU = {
ClassName = "CLIENTMENUE", ClassName = "CLIENTMENUE",
lid = "", lid = "",
version = "0.1.1", version = "0.1.2",
name = nil, name = nil,
path = nil, path = nil,
group = nil, group = nil,
@ -70,6 +71,7 @@ CLIENTMENU = {
debug = false, debug = false,
Controller = nil, Controller = nil,
groupname = nil, groupname = nil,
active = false,
} }
--- ---
@ -114,7 +116,7 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
if self.Functionargs and self.debug then if self.Functionargs and self.debug then
self:T({"Functionargs",self.Functionargs}) self:T({"Functionargs",self.Functionargs})
end end
if not self.Generic then if not self.Generic and self.active == false then
if Function ~= nil then if Function ~= nil then
local ErrorHandler = function( errmsg ) local ErrorHandler = function( errmsg )
env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg ) env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg )
@ -133,8 +135,10 @@ function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
end end
end end
self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler) self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
self.active = true
else else
self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath) self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
self.active = true
end end
else else
if self.parentpath then if self.parentpath then
@ -200,6 +204,7 @@ function CLIENTMENU:RemoveF10()
if not status then if not status then
self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname)) self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname))
end end
self.active = false
end end
return self return self
end end
@ -412,7 +417,7 @@ end
CLIENTMENUMANAGER = { CLIENTMENUMANAGER = {
ClassName = "CLIENTMENUMANAGER", ClassName = "CLIENTMENUMANAGER",
lid = "", lid = "",
version = "0.1.4", version = "0.1.5a",
name = nil, name = nil,
clientset = nil, clientset = nil,
menutree = {}, menutree = {},
@ -676,6 +681,7 @@ end
function CLIENTMENUMANAGER:Propagate(Client) function CLIENTMENUMANAGER:Propagate(Client)
self:T(self.lid.."Propagate") self:T(self.lid.."Propagate")
--self:I(UTILS.PrintTableToLog(Client,1)) --self:I(UTILS.PrintTableToLog(Client,1))
local knownunits = {} -- track so we can ID multi seated
local Set = self.clientset.Set local Set = self.clientset.Set
if Client then if Client then
Set = {Client} Set = {Client}
@ -684,7 +690,15 @@ function CLIENTMENUMANAGER:Propagate(Client)
for _,_client in pairs(Set) do for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then if client and client:IsAlive() then
local playerunit = client:GetName()
local playergroup = client:GetGroup()
local playername = client:GetPlayerName() or "none" local playername = client:GetPlayerName() or "none"
if not knownunits[playerunit] then
knownunits[playerunit] = true
else
self:I("Player in multi seat unit: "..playername)
break -- multi seat already build
end
if not self.playertree[playername] then if not self.playertree[playername] then
self.playertree[playername] = {} self.playertree[playername] = {}
end end
@ -719,6 +733,7 @@ end
function CLIENTMENUMANAGER:AddEntry(Entry,Client) function CLIENTMENUMANAGER:AddEntry(Entry,Client)
self:T(self.lid.."AddEntry") self:T(self.lid.."AddEntry")
local Set = self.clientset.Set local Set = self.clientset.Set
local knownunits = {}
if Client then if Client then
Set = {Client} Set = {Client}
end end
@ -726,6 +741,13 @@ function CLIENTMENUMANAGER:AddEntry(Entry,Client)
local client = _client -- Wrapper.Client#CLIENT local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then if client and client:IsAlive() then
local playername = client:GetPlayerName() local playername = client:GetPlayerName()
local unitname = client:GetName()
if not knownunits[unitname] then
knownunits[unitname] = true
else
self:I("Player in multi seat unit: "..playername)
break
end
if Entry then if Entry then
self:T("Adding generic entry:" .. Entry.UUID) self:T("Adding generic entry:" .. Entry.UUID)
local parent = nil local parent = nil

View File

@ -1074,7 +1074,7 @@ end
-- @param #string unitname Name of the associated unit. -- @param #string unitname Name of the associated unit.
-- @return #number Octal -- @return #number Octal
function DATABASE:GetNextSTN(octal,unitname) function DATABASE:GetNextSTN(octal,unitname)
local first = UTILS.OctalToDecimal(octal) local first = UTILS.OctalToDecimal(octal) or 0
if self.STNS[first] == unitname then return octal end if self.STNS[first] == unitname then return octal end
local nextoctal = 77777 local nextoctal = 77777
local found = false local found = false
@ -1111,7 +1111,7 @@ end
-- @param #string unitname Name of the associated unit. -- @param #string unitname Name of the associated unit.
-- @return #number Octal -- @return #number Octal
function DATABASE:GetNextSADL(octal,unitname) function DATABASE:GetNextSADL(octal,unitname)
local first = UTILS.OctalToDecimal(octal) local first = UTILS.OctalToDecimal(octal) or 0
if self.SADL[first] == unitname then return octal end if self.SADL[first] == unitname then return octal end
local nextoctal = 7777 local nextoctal = 7777
local found = false local found = false
@ -1685,7 +1685,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
if Event.IniObjectCategory == 1 then if Event.IniObjectCategory == 1 then
-- Try to get the player name. This can be buggy for multicrew aircraft! -- Try to get the player name. This can be buggy for multicrew aircraft!
local PlayerName = Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName) local PlayerName = Event.IniPlayerName or Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName)
if PlayerName then if PlayerName then

View File

@ -395,7 +395,8 @@ end
--- Sends a MESSAGE to all players. --- Sends a MESSAGE to all players.
-- @param #MESSAGE self -- @param #MESSAGE self
-- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE -- @param #number Delay (Optional) Delay in seconds before the message is send. Default instantly (`nil`).
-- @return #MESSAGE self
-- @usage -- @usage
-- --
-- -- Send a message created to all players. -- -- Send a message created to all players.
@ -406,9 +407,13 @@ end
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 ) -- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25 )
-- MessageAll:ToAll() -- MessageAll:ToAll()
-- --
function MESSAGE:ToAll( Settings ) function MESSAGE:ToAll( Settings, Delay )
self:F() self:F()
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MESSAGE.ToAll, self, Settings, 0)
else
if self.MessageType then if self.MessageType then
local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType ) self.MessageDuration = Settings:GetMessageTime( self.MessageType )
@ -420,6 +425,8 @@ function MESSAGE:ToAll( Settings )
trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen ) trigger.action.outText( self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ), self.MessageDuration, self.ClearScreen )
end end
end
return self return self
end end

View File

@ -4388,8 +4388,8 @@ do -- SET_CLIENT
return self return self
end end
--- Builds a set of CLIENTs that contain the given string in their unit/pilot name. --- Builds a set of CLIENTs that contain the given string in their **unit/pilot** name and **NOT** the group name!
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. Pattern matching applies.
-- @param #SET_CLIENT self -- @param #SET_CLIENT self
-- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings. -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings.
-- @return #SET_CLIENT self -- @return #SET_CLIENT self

View File

@ -292,9 +292,10 @@ SPAWN = {
--- Enumerator for spawns at airbases --- Enumerator for spawns at airbases
-- @type SPAWN.Takeoff -- @type SPAWN.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @field #number Air Take off happens in air.
-- @field #number Runway Spawn on runway. Does not work in MP!
-- @field #SPAWN.Takeoff Takeoff -- @field #number Hot Spawn at parking with engines on.
-- @field #number Cold Spawn at parking with engines off.
SPAWN.Takeoff = { SPAWN.Takeoff = {
Air = 1, Air = 1,
Runway = 2, Runway = 2,
@ -1467,6 +1468,30 @@ do -- Delay methods
end -- Delay methods end -- Delay methods
--- Hide the group on the map view (visible to game master slots!).
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitHiddenOnMap()
self.SpawnHiddenOnMap = true
return self
end
--- Hide the group on MFDs (visible to game master slots!).
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitHiddenOnMFD()
self.SpawnHiddenOnMFD = true
return self
end
--- Hide the group on planner (visible to game master slots!).
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitHiddenOnPlanner()
self.SpawnHiddenOnPlanner = true
return self
end
--- Will spawn a group based on the internal index. --- Will spawn a group based on the internal index.
-- Note: This method uses the global _DATABASE object (an instance of @{Core.Database#DATABASE}), which contains ALL initial and new spawned objects in MOOSE. -- Note: This method uses the global _DATABASE object (an instance of @{Core.Database#DATABASE}), which contains ALL initial and new spawned objects in MOOSE.
-- @param #SPAWN self -- @param #SPAWN self
@ -1741,6 +1766,19 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
SpawnTemplate.modulation = self.SpawnInitModu SpawnTemplate.modulation = self.SpawnInitModu
end end
-- hiding options
if self.SpawnHiddenOnPlanner then
SpawnTemplate.hiddenOnPlanner=true
end
if self.SpawnHiddenOnMFD then
SpawnTemplate.hiddenOnMFD=true
end
if self.SpawnHiddenOnMap then
SpawnTemplate.hidden=true
end
-- Set country, coalition and category. -- Set country, coalition and category.
SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID
@ -3238,7 +3276,7 @@ end
--- Get the index from a given group. --- 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. -- The function will search the name of the group for a #, and will return the number behind the #-mark.
function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 ) local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
local Index = tonumber( IndexString ) local Index = tonumber( IndexString )
@ -3250,7 +3288,7 @@ end
--- Return the last maximum index that can be used. --- Return the last maximum index that can be used.
function SPAWN:_GetLastIndex() function SPAWN:_GetLastIndex()
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
return self.SpawnMaxGroups return self.SpawnMaxGroups
end end

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
-- --
-- @module Functional.Stratego -- @module Functional.Stratego
-- @image Functional.Stratego.png -- @image Functional.Stratego.png
-- Last Update April 2024
--- ---
@ -42,6 +43,7 @@
-- @field #number CaptureUnits -- @field #number CaptureUnits
-- @field #number CaptureThreatlevel -- @field #number CaptureThreatlevel
-- @field #boolean ExcludeShips -- @field #boolean ExcludeShips
-- @field Core.Zone#ZONE StrategoZone
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -154,6 +156,7 @@
-- @{#STRATEGO.FindRoute}(): Find a route between two nodes. -- @{#STRATEGO.FindRoute}(): Find a route between two nodes.
-- @{#STRATEGO.SetCaptureOptions}(): Set how many units of which minimum threat level are needed to capture one node (i.e. the underlying OpsZone). -- @{#STRATEGO.SetCaptureOptions}(): Set how many units of which minimum threat level are needed to capture one node (i.e. the underlying OpsZone).
-- @{#STRATEGO.SetDebug}(): Set debug and draw options. -- @{#STRATEGO.SetDebug}(): Set debug and draw options.
-- @{#STRATEGO.SetStrategoZone}(): Set a zone to restrict STRATEGO analytics to, can be any kind of ZONE Object.
-- --
-- --
-- ## Visualisation example code for the Syria map: -- ## Visualisation example code for the Syria map:
@ -177,7 +180,7 @@ STRATEGO = {
debug = false, debug = false,
drawzone = false, drawzone = false,
markzone = false, markzone = false,
version = "0.2.5", version = "0.2.7",
portweight = 3, portweight = 3,
POIweight = 1, POIweight = 1,
maxrunways = 3, maxrunways = 3,
@ -377,6 +380,15 @@ function STRATEGO:SetDebug(Debug,DrawZones,MarkZones)
return self return self
end end
--- [USER] Restrict Stratego to analyse this zone only.
-- @param #STRATEGO self
-- @param Core.Zone#ZONE Zone The Zone to restrict Stratego to, can be any kind of ZONE Object.
-- @return #STRATEGO self
function STRATEGO:SetStrategoZone(Zone)
self.StrategoZone = Zone
return self
end
--- [USER] Set weights for nodes and routes to determine their importance. --- [USER] Set weights for nodes and routes to determine their importance.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #number MaxRunways Set the maximum number of runways the big (equals strategic) airbases on the map have. Defaults to 3. The weight of an airbase node hence equals the number of runways. -- @param #number MaxRunways Set the maximum number of runways the big (equals strategic) airbases on the map have. Defaults to 3. The weight of an airbase node hence equals the number of runways.
@ -425,12 +437,19 @@ function STRATEGO:AnalyseBases()
local airbasetable = self.airbasetable local airbasetable = self.airbasetable
local nonconnectedab = self.nonconnectedab local nonconnectedab = self.nonconnectedab
local easynames = self.easynames local easynames = self.easynames
local zone = self.StrategoZone -- Core.Zone#ZONE_POLYGON
-- find bases with >= 1 runways -- find bases with >= 1 runways
self.bases:ForEach( self.bases:ForEach(
function(afb) function(afb)
local ab = afb -- Wrapper.Airbase#AIRBASE local ab = afb -- Wrapper.Airbase#AIRBASE
local abvec2 = ab:GetVec2()
if self.ExcludeShips and ab:IsShip() then return end if self.ExcludeShips and ab:IsShip() then return end
if zone ~= nil then
if not zone:IsVec2InZone(abvec2) then
return
end
end
local abname = ab:GetName() local abname = ab:GetName()
local runways = ab:GetRunways() local runways = ab:GetRunways()
local numrwys = #runways local numrwys = #runways
@ -759,9 +778,39 @@ function STRATEGO:GetNextHighestWeightNodes(Weight, Coalition)
return airbases[weight],weight return airbases[weight],weight
end end
--- [USER] Set the aggregated weight of a single node found by its name manually.
-- @param #STRATEGO self
-- @param #string Name The name to look for.
-- @param #number Weight The weight to be set.
-- @return #boolean success
function STRATEGO:SetNodeWeight(Name,Weight)
self:T(self.lid.."SetNodeWeight")
if Name and Weight and self.airbasetable[Name] then
self.airbasetable[Name].weight = Weight or 0
return true
else
return false
end
end
--- [USER] Set the base weight of a single node found by its name manually.
-- @param #STRATEGO self
-- @param #string Name The name to look for.
-- @param #number Weight The weight to be set.
-- @return #boolean success
function STRATEGO:SetNodeBaseWeight(Name,Weight)
self:T(self.lid.."SetNodeBaseWeight")
if Name and Weight and self.airbasetable[Name] then
self.airbasetable[Name].baseweight = Weight or 0
return true
else
return false
end
end
--- [USER] Get the aggregated weight of a node by its name. --- [USER] Get the aggregated weight of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string Name The name to look for.
-- @return #number Weight The weight or 0 if not found. -- @return #number Weight The weight or 0 if not found.
function STRATEGO:GetNodeWeight(Name) function STRATEGO:GetNodeWeight(Name)
self:T(self.lid.."GetNodeWeight") self:T(self.lid.."GetNodeWeight")
@ -774,7 +823,7 @@ end
--- [USER] Get the base weight of a node by its name. --- [USER] Get the base weight of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string Name The name to look for.
-- @return #number Weight The base weight or 0 if not found. -- @return #number Weight The base weight or 0 if not found.
function STRATEGO:GetNodeBaseWeight(Name) function STRATEGO:GetNodeBaseWeight(Name)
self:T(self.lid.."GetNodeBaseWeight") self:T(self.lid.."GetNodeBaseWeight")
@ -787,7 +836,7 @@ end
--- [USER] Get the COALITION of a node by its name. --- [USER] Get the COALITION of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #number Coalition The coalition. -- @return #number Coalition The coalition.
function STRATEGO:GetNodeCoalition(Name) function STRATEGO:GetNodeCoalition(Name)
self:T(self.lid.."GetNodeCoalition") self:T(self.lid.."GetNodeCoalition")
@ -800,7 +849,7 @@ end
--- [USER] Get the TYPE of a node by its name. --- [USER] Get the TYPE of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #string Type Type of the node, e.g. STRATEGO.Type.AIRBASE or nil if not found. -- @return #string Type Type of the node, e.g. STRATEGO.Type.AIRBASE or nil if not found.
function STRATEGO:GetNodeType(Name) function STRATEGO:GetNodeType(Name)
self:T(self.lid.."GetNodeType") self:T(self.lid.."GetNodeType")
@ -813,7 +862,7 @@ end
--- [USER] Get the ZONE of a node by its name. --- [USER] Get the ZONE of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return Core.Zone#ZONE Zone The Zone of the node or nil if not found. -- @return Core.Zone#ZONE Zone The Zone of the node or nil if not found.
function STRATEGO:GetNodeZone(Name) function STRATEGO:GetNodeZone(Name)
self:T(self.lid.."GetNodeZone") self:T(self.lid.."GetNodeZone")
@ -826,7 +875,7 @@ end
--- [USER] Get the OPSZONE of a node by its name. --- [USER] Get the OPSZONE of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return Ops.OpsZone#OPSZONE OpsZone The OpsZone of the node or nil if not found. -- @return Ops.OpsZone#OPSZONE OpsZone The OpsZone of the node or nil if not found.
function STRATEGO:GetNodeOpsZone(Name) function STRATEGO:GetNodeOpsZone(Name)
self:T(self.lid.."GetNodeOpsZone") self:T(self.lid.."GetNodeOpsZone")
@ -839,7 +888,7 @@ end
--- [USER] Get the COORDINATE of a node by its name. --- [USER] Get the COORDINATE of a node by its name.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return Core.Point#COORDINATE Coordinate The Coordinate of the node or nil if not found. -- @return Core.Point#COORDINATE Coordinate The Coordinate of the node or nil if not found.
function STRATEGO:GetNodeCoordinate(Name) function STRATEGO:GetNodeCoordinate(Name)
self:T(self.lid.."GetNodeCoordinate") self:T(self.lid.."GetNodeCoordinate")
@ -852,7 +901,7 @@ end
--- [USER] Check if the TYPE of a node is AIRBASE. --- [USER] Check if the TYPE of a node is AIRBASE.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsAirbase(Name) function STRATEGO:IsAirbase(Name)
self:T(self.lid.."IsAirbase") self:T(self.lid.."IsAirbase")
@ -865,7 +914,7 @@ end
--- [USER] Check if the TYPE of a node is PORT. --- [USER] Check if the TYPE of a node is PORT.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsPort(Name) function STRATEGO:IsPort(Name)
self:T(self.lid.."IsPort") self:T(self.lid.."IsPort")
@ -878,7 +927,7 @@ end
--- [USER] Check if the TYPE of a node is POI. --- [USER] Check if the TYPE of a node is POI.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsPOI(Name) function STRATEGO:IsPOI(Name)
self:T(self.lid.."IsPOI") self:T(self.lid.."IsPOI")
@ -891,7 +940,7 @@ end
--- [USER] Check if the TYPE of a node is FARP. --- [USER] Check if the TYPE of a node is FARP.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsFARP(Name) function STRATEGO:IsFARP(Name)
self:T(self.lid.."IsFARP") self:T(self.lid.."IsFARP")
@ -904,7 +953,7 @@ end
--- [USER] Check if the TYPE of a node is SHIP. --- [USER] Check if the TYPE of a node is SHIP.
-- @param #STRATEGO self -- @param #STRATEGO self
-- @param #string Name. -- @param #string The name to look for.
-- @return #boolean Outcome -- @return #boolean Outcome
function STRATEGO:IsShip(Name) function STRATEGO:IsShip(Name)
self:T(self.lid.."IsShip") self:T(self.lid.."IsShip")

View File

@ -154,6 +154,14 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Route.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Account.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Actions/Act_Assist.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/ShapeBase.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Circle.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Cube.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Line.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Oval.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Polygon.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Shapes/Triangle.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/UserSound.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/UserSound.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SoundOutput.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/SoundOutput.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/Radio.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Sound/Radio.lua' )

View File

@ -976,6 +976,8 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
-- Update route. -- Update route.
if Nwp>1 and self.isMobile then if Nwp>1 and self.isMobile then
self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!", Nwp)) self:T(self.lid..string.format("Got %d waypoints on spawn ==> Cruise in -1.0 sec!", Nwp))
local wp=self:GetWaypointNext()
self.option.Formation=wp.action
--self:__Cruise(-1, nil, self.option.Formation) --self:__Cruise(-1, nil, self.option.Formation)
self:__Cruise(-1) self:__Cruise(-1)
else else
@ -1288,6 +1290,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation)
-- Current set speed in m/s. -- Current set speed in m/s.
self.speedWp=wp.speed self.speedWp=wp.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s", self.speedWp))
-- Debug output. -- Debug output.
if self.verbose>=10 then --or self.attribute==GROUP.Attribute.GROUND_APC then if self.verbose>=10 then --or self.attribute==GROUP.Attribute.GROUND_APC then

View File

@ -30,8 +30,8 @@
-- @module Ops.CSAR -- @module Ops.CSAR
-- @image OPS_CSAR.jpg -- @image OPS_CSAR.jpg
-- Date: May 2023 ---
-- Last: Update Dec 2024 -- Last Update April 2024
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@ -294,7 +294,7 @@ CSAR.AircraftType["MH-60R"] = 10
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.20" CSAR.version="1.0.21"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@ -463,7 +463,7 @@ function CSAR:New(Coalition, Template, Alias)
self.SRSModulation = radio.modulation.AM -- modulation self.SRSModulation = radio.modulation.AM -- modulation
self.SRSport = 5002 -- port self.SRSport = 5002 -- port
self.SRSCulture = "en-GB" self.SRSCulture = "en-GB"
self.SRSVoice = nil self.SRSVoice = MSRS.Voices.Google.Standard.en_GB_Standard_B
self.SRSGPathToCredentials = nil self.SRSGPathToCredentials = nil
self.SRSVolume = 1.0 -- volume 0.0 to 1.0 self.SRSVolume = 1.0 -- volume 0.0 to 1.0
self.SRSGender = "male" -- male or female self.SRSGender = "male" -- male or female
@ -1190,7 +1190,7 @@ function CSAR:_EventHandler(EventData)
if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then
self:__Landed(2,_event.IniUnitName, _place) self:__Landed(2,_event.IniUnitName, _place)
self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true) self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,true,true)
else else
self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition()))
end end
@ -1529,7 +1529,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
local _reset = true local _reset = true
if (_distance < 500) then if (_distance < 500) then
self:T(self.lid .. "[Pickup Debug] Helo closer than 500m: ".._lookupKeyHeli)
if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.heliCloseMessage[_lookupKeyHeli] == nil then
if self.autosmoke == true then if self.autosmoke == true then
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true) self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true)
@ -1538,14 +1538,16 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
end end
self.heliCloseMessage[_lookupKeyHeli] = true self.heliCloseMessage[_lookupKeyHeli] = true
end end
self:T(self.lid .. "[Pickup Debug] Checking landed vs Hover for ".._lookupKeyHeli)
-- have we landed close enough? -- have we landed close enough?
if not _heliUnit:InAir() then if not _heliUnit:InAir() then
self:T(self.lid .. "[Pickup Debug] Helo landed: ".._lookupKeyHeli)
if self.pilotRuntoExtractPoint == true then if self.pilotRuntoExtractPoint == true then
if (_distance < self.extractDistance) then if (_distance < self.extractDistance) then
local _time = self.landedStatus[_lookupKeyHeli] local _time = self.landedStatus[_lookupKeyHeli]
self:T(self.lid .. "[Pickup Debug] Check pilot running or arrived ".._lookupKeyHeli)
if _time == nil then if _time == nil then
self:T(self.lid .. "[Pickup Debug] Pilot running not arrived yet ".._lookupKeyHeli)
self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 )
_time = self.landedStatus[_lookupKeyHeli] _time = self.landedStatus[_lookupKeyHeli]
_woundedGroup:OptionAlarmStateGreen() _woundedGroup:OptionAlarmStateGreen()
@ -1556,11 +1558,15 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
self.landedStatus[_lookupKeyHeli] = _time self.landedStatus[_lookupKeyHeli] = _time
end end
--if _time <= 0 or _distance < self.loadDistance then --if _time <= 0 or _distance < self.loadDistance then
self:T(self.lid .. "[Pickup Debug] Pilot close enough? ".._lookupKeyHeli)
if _distance < self.loadDistance + 5 or _distance <= 13 then if _distance < self.loadDistance + 5 or _distance <= 13 then
self:T(self.lid .. "[Pickup Debug] Pilot close enough - YES ".._lookupKeyHeli)
if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
return false return false
else else
self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli)
self.landedStatus[_lookupKeyHeli] = nil self.landedStatus[_lookupKeyHeli] = nil
self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
return true return true
@ -1568,28 +1574,32 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
end end
end end
else else
self:T(self.lid .. "[Pickup Debug] Helo landed, pilot NOT set to run to helo ".._lookupKeyHeli)
if (_distance < self.loadDistance) then if (_distance < self.loadDistance) then
self:T(self.lid .. "[Pickup Debug] Helo close enough, door check ".._lookupKeyHeli)
if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then
self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
return false return false
else else
self:T(self.lid .. "[Pickup Debug] Pick up Pilot ".._lookupKeyHeli)
self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
return true return true
end end
end end
end end
else else
self:T(self.lid .. "[Pickup Debug] Helo hovering".._lookupKeyHeli)
local _unitsInHelicopter = self:_PilotsOnboard(_heliName) local _unitsInHelicopter = self:_PilotsOnboard(_heliName)
local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()]
if _maxUnits == nil then if _maxUnits == nil then
_maxUnits = self.max_units _maxUnits = self.max_units
end end
self:T(self.lid .. "[Pickup Debug] Check capacity and close enough for winching ".._lookupKeyHeli)
if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then
-- DONE - make variable -- DONE - make variable
if _distance < self.rescuehoverdistance then if _distance < self.rescuehoverdistance then
self:T(self.lid .. "[Pickup Debug] Helo hovering close enough ".._lookupKeyHeli)
--check height! --check height!
local leaderheight = _woundedLeader:GetHeight() local leaderheight = _woundedLeader:GetHeight()
if leaderheight < 0 then leaderheight = 0 end if leaderheight < 0 then leaderheight = 0 end
@ -1597,7 +1607,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
-- DONE - make variable -- DONE - make variable
if _height <= self.rescuehoverheight then if _height <= self.rescuehoverheight then
self:T(self.lid .. "[Pickup Debug] Helo hovering low enough ".._lookupKeyHeli)
local _time = self.hoverStatus[_lookupKeyHeli] local _time = self.hoverStatus[_lookupKeyHeli]
if _time == nil then if _time == nil then
@ -1607,22 +1617,28 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
_time = self.hoverStatus[_lookupKeyHeli] - 10 _time = self.hoverStatus[_lookupKeyHeli] - 10
self.hoverStatus[_lookupKeyHeli] = _time self.hoverStatus[_lookupKeyHeli] = _time
end end
self:T(self.lid .. "[Pickup Debug] Check hover timer ".._lookupKeyHeli)
if _time > 0 then if _time > 0 then
self:T(self.lid .. "[Pickup Debug] Helo hovering not long enough ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true) self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you\'re too far away!", self.messageTime, true)
else else
self:T(self.lid .. "[Pickup Debug] Helo hovering long enough - door check ".._lookupKeyHeli)
if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then if self.pilotmustopendoors and (self:_IsLoadingDoorOpen(_heliName) == false) then
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true) self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true, true)
self:T(self.lid .. "[Pickup Debug] Door closed, try again next loop ".._lookupKeyHeli)
return false return false
else else
self.hoverStatus[_lookupKeyHeli] = nil self.hoverStatus[_lookupKeyHeli] = nil
self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName)
self:T(self.lid .. "[Pickup Debug] Pilot picked up ".._lookupKeyHeli)
return true return true
end end
end end
_reset = false _reset = false
else else
self:T(self.lid .. "[Pickup Debug] Helo hovering too high ".._lookupKeyHeli)
self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true)
self:T(self.lid .. "[Pickup Debug] Hovering too high, try again next loop ".._lookupKeyHeli)
return false return false
end end
end end
@ -1647,7 +1663,8 @@ end
-- @param #string heliname Heli name -- @param #string heliname Heli name
-- @param #string groupname Group name -- @param #string groupname Group name
-- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP -- @param #boolean isairport If true, EVENT.Landing took place at an airport or FARP
function CSAR:_ScheduledSARFlight(heliname,groupname, isairport) -- @param #boolean noreschedule If true, do not try to reschedule this is distances are not ok (coming from landing event)
function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule)
self:T(self.lid .. " _ScheduledSARFlight") self:T(self.lid .. " _ScheduledSARFlight")
self:T({heliname,groupname}) self:T({heliname,groupname})
local _heliUnit = self:_GetSARHeli(heliname) local _heliUnit = self:_GetSARHeli(heliname)
@ -1667,20 +1684,29 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport)
local _dist = self:_GetClosestMASH(_heliUnit) local _dist = self:_GetClosestMASH(_heliUnit)
if _dist == -1 then if _dist == -1 then
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance can not be determined!")
return return
end end
self:T(self.lid.."[Drop off debug] Check distance to MASH for "..heliname.." Distance km: "..math.floor(_dist/1000))
if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then if ( _dist < self.FARPRescueDistance or isairport ) and _heliUnit:InAir() == false then
self:T(self.lid.."[Drop off debug] Distance ok, door check")
if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then if self.pilotmustopendoors and self:_IsLoadingDoorOpen(heliname) == false then
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true) self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me out!", self.messageTime, true, true)
self:T(self.lid.."[Drop off debug] Door closed, try again next loop")
else else
self:T(self.lid.."[Drop off debug] Rescued!")
self:_RescuePilots(_heliUnit) self:_RescuePilots(_heliUnit)
return return
end end
end end
--queue up --queue up
self:__Returning(-5,heliname,_woundedGroupName, isairport) if not noreschedule then
self:__Returning(5,heliname,_woundedGroupName, isairport)
self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule)
end
return self return self
end end
@ -1752,7 +1778,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
_text = string.gsub(_text,"nm"," nautical miles") _text = string.gsub(_text,"nm"," nautical miles")
--self.msrs:SetVoice(self.SRSVoice) --self.msrs:SetVoice(self.SRSVoice)
--self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1) --self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,1)
self:I("Voice = "..self.SRSVoice) --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) self.SRSQueue:NewTransmission(_text,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,self.SRSVoice,volume,label,coord)
end end
return self return self
@ -1981,7 +2007,7 @@ end
--- (Internal) Determine distance to closest MASH. --- (Internal) Determine distance to closest MASH.
-- @param #CSAR self -- @param #CSAR self
-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT -- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT
-- @retunr -- @return #CSAR self
function CSAR:_GetClosestMASH(_heli) function CSAR:_GetClosestMASH(_heli)
self:T(self.lid .. " _GetClosestMASH") self:T(self.lid .. " _GetClosestMASH")
local _mashset = self.mash -- Core.Set#SET_GROUP local _mashset = self.mash -- Core.Set#SET_GROUP
@ -2219,7 +2245,7 @@ function CSAR:_RefreshRadioBeacons()
if self:_CountActiveDownedPilots() > 0 then if self:_CountActiveDownedPilots() > 0 then
local PilotTable = self.downedPilots local PilotTable = self.downedPilots
for _,_pilot in pairs (PilotTable) do for _,_pilot in pairs (PilotTable) do
self:T({_pilot}) self:T({_pilot.name})
local pilot = _pilot -- #CSAR.DownedPilot local pilot = _pilot -- #CSAR.DownedPilot
local group = pilot.group local group = pilot.group
local frequency = pilot.frequency or 0 -- thanks to @Thrud local frequency = pilot.frequency or 0 -- thanks to @Thrud
@ -2501,7 +2527,7 @@ end
-- @param #boolean IsAirport True if heli has landed on an AFB (from event land). -- @param #boolean IsAirport True if heli has landed on an AFB (from event land).
function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname, IsAirPort) function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname, IsAirPort)
self:T({From, Event, To, Heliname, Woundedgroupname}) self:T({From, Event, To, Heliname, Woundedgroupname})
self:_ScheduledSARFlight(Heliname,Woundedgroupname, IsAirPort) --self:_ScheduledSARFlight(Heliname,Woundedgroupname, IsAirPort)
return self return self
end end

View File

@ -24,7 +24,7 @@
-- @module Ops.CTLD -- @module Ops.CTLD
-- @image OPS_CTLD.jpg -- @image OPS_CTLD.jpg
-- Last Update March 2024 -- Last Update April 2024
do do
@ -1253,7 +1253,7 @@ CTLD.UnitTypeCapabilities = {
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.50" CTLD.version="1.0.51"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@ -2240,7 +2240,9 @@ end
local extractdistance = self.CrateDistance * self.ExtractFactor local extractdistance = self.CrateDistance * self.ExtractFactor
for k,v in pairs(self.DroppedTroops) do for k,v in pairs(self.DroppedTroops) do
local distance = self:_GetDistance(v:GetCoordinate(),unitcoord) local distance = self:_GetDistance(v:GetCoordinate(),unitcoord)
if distance <= extractdistance and distance ~= -1 then local TNow = timer.getTime()
local vtime = v.ExtractTime or TNow-310
if distance <= extractdistance and distance ~= -1 and (TNow - vtime > 300) then
nearestGroup = v nearestGroup = v
nearestGroupIndex = k nearestGroupIndex = k
nearestDistance = distance nearestDistance = distance
@ -2291,9 +2293,11 @@ end
end end
if troopsize + numberonboard > trooplimit then if troopsize + numberonboard > trooplimit then
self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group) self:_SendMessage("Sorry, we\'re crammed already!", 10, false, Group)
nearestGroup.ExtractTime = 0
--return self --return self
else else
self.CargoCounter = self.CargoCounter + 1 self.CargoCounter = self.CargoCounter + 1
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) 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}) self:T({cargotype=loadcargotype})
local running = math.floor(nearestDistance / 4)+10 -- time run to helo plus boarding local running = math.floor(nearestDistance / 4)+10 -- time run to helo plus boarding

View File

@ -505,6 +505,9 @@ end
function COHORT:SetCallsign(Callsign, Index) function COHORT:SetCallsign(Callsign, Index)
self.callsignName=Callsign self.callsignName=Callsign
self.callsignIndex=Index self.callsignIndex=Index
self.callsign={}
self.callsign.NumberSquad=Callsign
self.callsign.NumberGroup=Index
return self return self
end end

View File

@ -1133,9 +1133,10 @@ function FLIGHTCONTROL:onafterStatusUpdate()
-- Check if runway was repaired. -- Check if runway was repaired.
if self:IsRunwayOperational()==false then if self:IsRunwayOperational()==false then
local Trepair=self:GetRunwayRepairtime() local Trepair=self:GetRunwayRepairtime()
self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec", Trepair))
if Trepair==0 then if Trepair==0 then
self:RunwayRepaired() self:RunwayRepaired()
else
self:I(self.lid..string.format("Runway still destroyed! Will be repaired in %d sec", Trepair))
end end
end end
@ -1946,7 +1947,7 @@ function FLIGHTCONTROL:_GetNextFightParking()
local text="Parking flights:" local text="Parking flights:"
for i,_flight in pairs(Qparking) do for i,_flight in pairs(Qparking) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
text=text..string.format("\n[%d] %s [%s], state=%s [%s]: Tparking=%.1f sec", i, flight.groupname, flight.actype, flight:GetState(), self:GetFlightStatus(flight), flight:GetParkingTime()) text=text..string.format("\n[%d] %s [%s], state=%s [%s]: Tparking=%.1f sec", i, flight.groupname, tostring(flight.actype), flight:GetState(), self:GetFlightStatus(flight), flight:GetParkingTime())
end end
self:I(self.lid..text) self:I(self.lid..text)
end end
@ -2242,7 +2243,7 @@ function FLIGHTCONTROL:_InitParkingSpots()
local isalive=unit:IsAlive() local isalive=unit:IsAlive()
--env.info(string.format("FF parking spot %d is occupied by unit %s alive=%s", spot.TerminalID, unitname, tostring(isalive))) self:T2(self.lid..string.format("FF parking spot %d is occupied by unit %s alive=%s", spot.TerminalID, unitname, tostring(isalive)))
if isalive then if isalive then

View File

@ -62,6 +62,7 @@
-- @field #table flightplans Flight plans for this group. -- @field #table flightplans Flight plans for this group.
-- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Currently active flight plan. -- @field Navigation.FlightPlan#FLIGHTPLAN flightplan Currently active flight plan.
-- @field Core.Pathline#PATHLINE taxipath Assigned taxi pathline. -- @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 -- @extends Ops.OpsGroup#OPSGROUP
@ -277,6 +278,7 @@ function FLIGHTGROUP:New(group)
-- Holding flag. -- Holding flag.
self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname))
self.flaghold:Set(0) self.flaghold:Set(0)
self.holdtime=2*60
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
@ -790,6 +792,7 @@ function FLIGHTGROUP:SetReadyForTakeoff(ReadyTO, Delay)
if Delay and Delay>0 then if Delay and Delay>0 then
self:ScheduleOnce(Delay, FLIGHTGROUP.SetReadyForTakeoff, self, ReadyTO, 0) self:ScheduleOnce(Delay, FLIGHTGROUP.SetReadyForTakeoff, self, ReadyTO, 0)
else else
self:T(self.lid.."Set Ready for Takeoff switch for flightcontrol")
self.isReadyTO=ReadyTO self.isReadyTO=ReadyTO
end end
return self return self
@ -1273,6 +1276,9 @@ function FLIGHTGROUP:Status()
-- Check damage. -- Check damage.
self:_CheckDamage() self:_CheckDamage()
-- Check if stuck while taxiing.
self:_CheckStuck()
-- Get current mission (if any). -- Get current mission (if any).
local mission=self:GetMissionCurrent() local mission=self:GetMissionCurrent()
@ -1640,6 +1646,9 @@ function FLIGHTGROUP:Status()
if not mission then if not mission then
self.Twaiting=nil self.Twaiting=nil
self.dTwait=nil self.dTwait=nil
-- Check if group is done.
-- TODO: Not sure why I introduced this here.
self:_CheckGroupDone() self:_CheckGroupDone()
end end
@ -2119,7 +2128,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To)
-- Debug info. -- Debug info.
if self.verbose>=1 then if self.verbose>=1 then
local text=string.format("Initialized Flight Group %s:\n", self.groupname) local text=string.format("Initialized Flight Group %s:\n", self.groupname)
text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Unit type = %s\n", tostring(self.actype))
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000)
text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling))
@ -2159,6 +2168,10 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To)
if self.isAI then 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
-- Set ROE. -- Set ROE.
self:SwitchROE(self.option.ROE) self:SwitchROE(self.option.ROE)
@ -2764,6 +2777,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAA(From, Event, To)
if self.outofAAMrtb then if self.outofAAMrtb then
-- Back to destination or home. -- Back to destination or home.
local airbase=self.destbase or self.homebase local airbase=self.destbase or self.homebase
self:T(self.lid.."Calling RTB in onafterOutOfMissilesAA")
self:__RTB(-5, airbase) self:__RTB(-5, airbase)
end end
end end
@ -2778,6 +2792,7 @@ function FLIGHTGROUP:onafterOutOfMissilesAG(From, Event, To)
if self.outofAGMrtb then if self.outofAGMrtb then
-- Back to destination or home. -- Back to destination or home.
local airbase=self.destbase or self.homebase local airbase=self.destbase or self.homebase
self:T(self.lid.."Calling RTB in onafterOutOfMissilesAG")
self:__RTB(-5, airbase) self:__RTB(-5, airbase)
end end
end end
@ -2867,8 +2882,8 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
-- Number of remaining tasks/missions? -- Number of remaining tasks/missions?
if nTasks==0 and nMissions==0 and nTransports==0 then if nTasks==0 and nMissions==0 and nTransports==0 then
local destbase=self.destbase or self.homebase local destbase=self.destbase or self.homebase --Wrapper.Airbase#AIRBASE
local destzone=self.destzone or self.homezone local destzone=self.destzone or self.homezone --Wrapper.Airbase#AIRBASE
-- Send flight to destination. -- Send flight to destination.
if waittime then if waittime then
@ -2879,9 +2894,12 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!") self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports AND parking at destination airbase ==> Arrived!")
self:Arrived() self:Arrived()
else else
-- Only send RTB if current base is not yet the destination
if self.currbase==nil or self.currbase.AirbaseName~=destbase.AirbaseName then
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!") self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTB!")
self:__RTB(-0.1, destbase) self:__RTB(-0.1, destbase)
end end
end
elseif destzone then elseif destzone then
self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!") self:T(self.lid.."Passed Final WP and No current and/or future missions/tasks/transports ==> RTZ!")
self:__RTZ(-0.1, destzone) self:__RTZ(-0.1, destzone)
@ -3008,6 +3026,7 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold)
end end
if Tsuspend and not allowed then if Tsuspend and not allowed then
self:T(self.lid.."Calling RTB in onbeforeRTB")
self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold) self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold)
end end
@ -3225,7 +3244,7 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand)
self.flaghold:Set(0) self.flaghold:Set(0)
-- Set holding time. -- Set holding time.
local holdtime=2*60 local holdtime=self.holdtime
if fc or self.airboss then if fc or self.airboss then
holdtime=nil holdtime=nil
end end
@ -3388,7 +3407,7 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Duration, Altitude, Speed)
-- Set time stamp. -- Set time stamp.
self.Twaiting=timer.getAbsTime() self.Twaiting=timer.getAbsTime()
-- Max waiting -- Max waiting time in seconds.
self.dTwait=Duration self.dTwait=Duration
end end
@ -3688,6 +3707,7 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To)
-- Send back to airbase. -- Send back to airbase.
if airbase and self.fuellowrtb then if airbase and self.fuellowrtb then
self:T(self.lid.."Calling RTB in onafterFuelLow")
self:RTB(airbase) self:RTB(airbase)
--TODO: RTZ --TODO: RTZ
end end
@ -3712,6 +3732,7 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To)
local airbase=self.destbase or self.homebase local airbase=self.destbase or self.homebase
if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel() then if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel() then
self:T(self.lid.."Calling RTB in onafterFuelCritical")
self:RTB(airbase) self:RTB(airbase)
--TODO: RTZ --TODO: RTZ
end end
@ -4917,6 +4938,87 @@ function FLIGHTGROUP:_GetTerminal(_attribute, _category)
return _terminal return _terminal
end end
--- Check if group got stuck. This overwrites the OPSGROUP function.
-- Here we only check if stuck whilst taxiing.
-- @param #FLIGHTGROUP self
-- @param #boolean Despawn If `true`, despawn group if stuck.
-- @return #number Time in seconds the group got stuck or nil if not stuck.
function FLIGHTGROUP:_CheckStuck(Despawn)
-- Cases we are not stuck.
if not self:IsTaxiing() then
return nil
end
-- Current time.
local Tnow=timer.getTime()
-- Expected speed in m/s.
local ExpectedSpeed=5
-- Current speed in m/s.
local speed=self:GetVelocity()
-- Check speed.
if speed<0.1 then
if ExpectedSpeed>0 and not self.stuckTimestamp then
self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed))
self.stuckTimestamp=Tnow
self.stuckVec3=self:GetVec3()
end
else
-- Moving (again).
self.stuckTimestamp=nil
end
local holdtime=nil
-- Somehow we are not moving...
if self.stuckTimestamp then
-- Time we are holding.
holdtime=Tnow-self.stuckTimestamp
-- Trigger stuck event.
self:Stuck(holdtime)
if holdtime>=5*60 and holdtime<15*60 then
-- Debug warning.
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime))
elseif holdtime>=15*60 then
-- Debug warning.
self:T(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime))
-- Look for a current mission and cancel it as we do not seem to be able to perform it.
local mission=self:GetMissionCurrent()
if mission then
self:T(self.lid..string.format("WARNING: Cancelling mission %s [%s] due to being stuck", mission:GetName(), mission:GetType()))
self:MissionCancel(mission)
end
if self.stuckDespawn then
if self.legion then
self:T(self.lid..string.format("Asset is returned to its legion after being stuck!"))
self:ReturnToLegion()
else
self:T(self.lid..string.format("Despawning group after being stuck!"))
self:Despawn()
end
end
end
end
return holdtime
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- OPTION FUNCTIONS -- OPTION FUNCTIONS
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -118,6 +118,10 @@
-- --
-- @field #OPSGROUP.Spot spot Laser and IR spot. -- @field #OPSGROUP.Spot spot Laser and IR spot.
-- --
-- @field DCS#Vec3 stuckVec3 Position where the group got stuck.
-- @field #number stuckTimestamp Time stamp [sec], when the group got stuck.
-- @field #boolean stuckDespawn If `true`, group gets despawned after beeing stuck for a certain time.
--
-- @field #OPSGROUP.Ammo ammo Initial ammount of ammo. -- @field #OPSGROUP.Ammo ammo Initial ammount of ammo.
-- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType. -- @field #OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
-- --
@ -686,6 +690,7 @@ function OPSGROUP:New(group)
self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint. self:AddTransition("*", "GotoWaypoint", "*") -- Group switches to a specific waypoint.
self:AddTransition("*", "Wait", "*") -- Group will wait for further orders. self:AddTransition("*", "Wait", "*") -- Group will wait for further orders.
self:AddTransition("*", "Stuck", "*") -- Group got stuck.
self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle. self:AddTransition("*", "DetectedUnit", "*") -- Unit was detected (again) in this detection cycle.
self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set. self:AddTransition("*", "DetectedUnitNew", "*") -- Add a newly detected unit to the detected units set.
@ -1895,7 +1900,7 @@ end
--- Get current velocity of the group. --- Get current velocity of the group.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #string UnitName (Optional) Get heading of a specific unit of the group. Default is from the first existing unit in the group. -- @param #string UnitName (Optional) Get velocity of a specific unit of the group. Default is from the first existing unit in the group.
-- @return #number Velocity in m/s. -- @return #number Velocity in m/s.
function OPSGROUP:GetVelocity(UnitName) function OPSGROUP:GetVelocity(UnitName)
@ -2212,6 +2217,8 @@ function OPSGROUP:Destroy(Delay)
self:ScheduleOnce(Delay, OPSGROUP.Destroy, self, 0) self:ScheduleOnce(Delay, OPSGROUP.Destroy, self, 0)
else else
self:T(self.lid.."Destroying group!")
-- Get all units. -- Get all units.
local units=self:GetDCSUnits() local units=self:GetDCSUnits()
@ -6683,6 +6690,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint)
local wpnext=self:GetWaypointNext() local wpnext=self:GetWaypointNext()
if wpnext then if wpnext then
self.speedWp=wpnext.speed self.speedWp=wpnext.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s", self.speedWp))
end end
end end
@ -11398,6 +11406,7 @@ function OPSGROUP:_InitWaypoints(WpIndexMin, WpIndexMax)
-- Expected speed to the first waypoint. -- Expected speed to the first waypoint.
if i<=2 then if i<=2 then
self.speedWp=wp.speed self.speedWp=wp.speed
self:T(self.lid..string.format("Expected/waypoint speed=%.1f m/s", self.speedWp))
end end
-- Speed in knots. -- Speed in knots.
@ -12030,7 +12039,7 @@ function OPSGROUP:GetEPLRS()
return self.option.EPLRS or self.optionDefault.EPLRS return self.option.EPLRS or self.optionDefault.EPLRS
end end
--- Set the default EPLRS for the group. --- Set the default emission state for the group.
-- @param #OPSGROUP self -- @param #OPSGROUP self
-- @param #boolean OnOffSwitch If `true`, EPLRS is on by default. If `false` default EPLRS setting is off. If `nil`, default is on if group has EPLRS and off if it does not have a datalink. -- @param #boolean OnOffSwitch If `true`, EPLRS is on by default. If `false` default EPLRS setting is off. If `nil`, default is on if group has EPLRS and off if it does not have a datalink.
-- @return #OPSGROUP self -- @return #OPSGROUP self
@ -12039,7 +12048,7 @@ function OPSGROUP:SetDefaultEmission(OnOffSwitch)
if OnOffSwitch==nil then if OnOffSwitch==nil then
self.optionDefault.Emission=true self.optionDefault.Emission=true
else else
self.optionDefault.EPLRS=OnOffSwitch self.optionDefault.Emission=OnOffSwitch
end end
return self return self

View File

@ -0,0 +1,260 @@
--
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.CIRCLE
-- @image MOOSE.JPG
--- CIRCLE class.
-- @type CIRCLE
-- @field #string ClassName Name of the class.
-- @field #number Radius Radius of the circle
--- *It's NOT hip to be square* -- Someone, somewhere, probably
--
-- ===
--
-- # CIRCLE
-- CIRCLEs can be fetched from the drawings in the Mission Editor
---
-- This class has some of the standard CIRCLE functions you'd expect. One function of interest is CIRCLE:PointInSector() that you can use if a point is
-- within a certain sector (pizza slice) of a circle. This can be useful for many things, including rudimentary, "radar-like" searches from a unit.
--
-- CIRCLE class with properties and methods for handling circles.
-- @field #CIRCLE
CIRCLE = {
ClassName = "CIRCLE",
Radius = nil,
}
--- Finds a circle on the map by its name. The circle must have been added in the Mission Editor
-- @param #string shape_name Name of the circle to find
-- @return #CIRCLE The found circle, or nil if not found
function CIRCLE:FindOnMap(shape_name)
local self = BASE:Inherit(self, SHAPE_BASE:FindOnMap(shape_name))
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if string.find(object["name"], shape_name, 1, true) then
if object["polygonMode"] == "circle" then
self.Radius = object["radius"]
end
end
end
end
return self
end
--- Finds a circle by its name in the database.
-- @param #string shape_name Name of the circle to find
-- @return #CIRCLE The found circle, or nil if not found
function CIRCLE:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
--- Creates a new circle from a center point and a radius.
-- @param #table vec2 The center point of the circle
-- @param #number radius The radius of the circle
-- @return #CIRCLE The new circle
function CIRCLE:New(vec2, radius)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.CenterVec2 = vec2
self.Radius = radius
return self
end
--- Gets the radius of the circle.
-- @return #number The radius of the circle
function CIRCLE:GetRadius()
return self.Radius
end
--- Checks if a point is contained within the circle.
-- @param #table point The point to check
-- @return #bool True if the point is contained, false otherwise
function CIRCLE:ContainsPoint(point)
if ((point.x - self.CenterVec2.x) ^ 2 + (point.y - self.CenterVec2.y) ^ 2) ^ 0.5 <= self.Radius then
return true
end
return false
end
--- Checks if a point is contained within a sector of the circle. The start and end sector need to be clockwise
-- @param #table point The point to check
-- @param #table sector_start The start point of the sector
-- @param #table sector_end The end point of the sector
-- @param #table center The center point of the sector
-- @param #number radius The radius of the sector
-- @return #bool True if the point is contained, false otherwise
function CIRCLE:PointInSector(point, sector_start, sector_end, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
local function are_clockwise(v1, v2)
return -v1.x * v2.y + v1.y * v2.x > 0
end
local function is_in_radius(rp)
return rp.x * rp.x + rp.y * rp.y <= radius ^ 2
end
local rel_pt = {
x = point.x - center.x,
y = point.y - center.y
}
local rel_sector_start = {
x = sector_start.x - center.x,
y = sector_start.y - center.y,
}
local rel_sector_end = {
x = sector_end.x - center.x,
y = sector_end.y - center.y,
}
return not are_clockwise(rel_sector_start, rel_pt) and
are_clockwise(rel_sector_end, rel_pt) and
is_in_radius(rel_pt, radius)
end
--- Checks if a unit is contained within a sector of the circle. The start and end sector need to be clockwise
-- @param #string unit_name The name of the unit to check
-- @param #table sector_start The start point of the sector
-- @param #table sector_end The end point of the sector
-- @param #table center The center point of the sector
-- @param #number radius The radius of the sector
-- @return #bool True if the unit is contained, false otherwise
function CIRCLE:UnitInSector(unit_name, sector_start, sector_end, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
if self:PointInSector(UNIT:FindByName(unit_name):GetVec2(), sector_start, sector_end, center, radius) then
return true
end
return false
end
--- Checks if any unit of a group is contained within a sector of the circle. The start and end sector need to be clockwise
-- @param #string group_name The name of the group to check
-- @param #table sector_start The start point of the sector
-- @param #table sector_end The end point of the sector
-- @param #table center The center point of the sector
-- @param #number radius The radius of the sector
-- @return #bool True if any unit of the group is contained, false otherwise
function CIRCLE:AnyOfGroupInSector(group_name, sector_start, sector_end, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
for _, unit in pairs(GROUP:FindByName(group_name):GetUnits()) do
if self:PointInSector(unit:GetVec2(), sector_start, sector_end, center, radius) then
return true
end
end
return false
end
--- Checks if all units of a group are contained within a sector of the circle. The start and end sector need to be clockwise
-- @param #string group_name The name of the group to check
-- @param #table sector_start The start point of the sector
-- @param #table sector_end The end point of the sector
-- @param #table center The center point of the sector
-- @param #number radius The radius of the sector
-- @return #bool True if all units of the group are contained, false otherwise
function CIRCLE:AllOfGroupInSector(group_name, sector_start, sector_end, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
for _, unit in pairs(GROUP:FindByName(group_name):GetUnits()) do
if not self:PointInSector(unit:GetVec2(), sector_start, sector_end, center, radius) then
return false
end
end
return true
end
--- Checks if a unit is contained within a radius of the circle.
-- @param #string unit_name The name of the unit to check
-- @param #table center The center point of the radius
-- @param #number radius The radius to check
-- @return #bool True if the unit is contained, false otherwise
function CIRCLE:UnitInRadius(unit_name, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
if UTILS.IsInRadius(center, UNIT:FindByName(unit_name):GetVec2(), radius) then
return true
end
return false
end
--- Checks if any unit of a group is contained within a radius of the circle.
-- @param #string group_name The name of the group to check
-- @param #table center The center point of the radius
-- @param #number radius The radius to check
-- @return #bool True if any unit of the group is contained, false otherwise
function CIRCLE:AnyOfGroupInRadius(group_name, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
for _, unit in pairs(GROUP:FindByName(group_name):GetUnits()) do
if UTILS.IsInRadius(center, unit:GetVec2(), radius) then
return true
end
end
return false
end
--- Checks if all units of a group are contained within a radius of the circle.
-- @param #string group_name The name of the group to check
-- @param #table center The center point of the radius
-- @param #number radius The radius to check
-- @return #bool True if all units of the group are contained, false otherwise
function CIRCLE:AllOfGroupInRadius(group_name, center, radius)
center = center or self.CenterVec2
radius = radius or self.Radius
for _, unit in pairs(GROUP:FindByName(group_name):GetUnits()) do
if not UTILS.IsInRadius(center, unit:GetVec2(), radius) then
return false
end
end
return true
end
--- Returns a random Vec2 within the circle.
-- @return #table The random Vec2
function CIRCLE:GetRandomVec2()
local angle = math.random() * 2 * math.pi
local rx = math.random(0, self.Radius) * math.cos(angle) + self.CenterVec2.x
local ry = math.random(0, self.Radius) * math.sin(angle) + self.CenterVec2.y
return {x=rx, y=ry}
end
--- Returns a random Vec2 on the border of the circle.
-- @return #table The random Vec2
function CIRCLE:GetRandomVec2OnBorder()
local angle = math.random() * 2 * math.pi
local rx = self.Radius * math.cos(angle) + self.CenterVec2.x
local ry = self.Radius * math.sin(angle) + self.CenterVec2.y
return {x=rx, y=ry}
end
--- Calculates the bounding box of the circle. The bounding box is the smallest rectangle that contains the circle.
-- @return #table The bounding box of the circle
function CIRCLE:GetBoundingBox()
local min_x = self.CenterVec2.x - self.Radius
local min_y = self.CenterVec2.y - self.Radius
local max_x = self.CenterVec2.x + self.Radius
local max_y = self.CenterVec2.y + self.Radius
return {
{x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y}
}
end

View File

@ -0,0 +1,85 @@
---
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.CUBE
-- @image MOOSE.JPG
--- LINE class.
-- @type CUBE
-- @field #string ClassName Name of the class.
-- @field #number Points points of the line
-- @field #number Coords coordinates of the line
--
-- ===
---
-- @field #CUBE
CUBE = {
ClassName = "CUBE",
Points = {},
Coords = {}
}
--- Points need to be added in the following order:
--- p1 -> p4 make up the front face of the cube
--- p5 -> p8 make up the back face of the cube
--- p1 connects to p5
--- p2 connects to p6
--- p3 connects to p7
--- p4 connects to p8
---
--- 8-----------7
--- /| /|
--- / | / |
--- 4--+--------3 |
--- | | | |
--- | | | |
--- | | | |
--- | 5--------+--6
--- | / | /
--- |/ |/
--- 1-----------2
---
function CUBE:New(p1, p2, p3, p4, p5, p6, p7, p8)
local self = BASE:Inherit(self, SHAPE_BASE)
self.Points = {p1, p2, p3, p4, p5, p6, p7, p8}
for _, point in spairs(self.Points) do
table.insert(self.Coords, COORDINATE:NewFromVec3(point))
end
return self
end
function CUBE:GetCenter()
local center = { x=0, y=0, z=0 }
for _, point in pairs(self.Points) do
center.x = center.x + point.x
center.y = center.y + point.y
center.z = center.z + point.z
end
center.x = center.x / 8
center.y = center.y / 8
center.z = center.z / 8
return center
end
function CUBE:ContainsPoint(point, cube_points)
cube_points = cube_points or self.Points
local min_x, min_y, min_z = math.huge, math.huge, math.huge
local max_x, max_y, max_z = -math.huge, -math.huge, -math.huge
-- Find the minimum and maximum x, y, and z values of the cube points
for _, p in ipairs(cube_points) do
if p.x < min_x then min_x = p.x end
if p.y < min_y then min_y = p.y end
if p.z < min_z then min_z = p.z end
if p.x > max_x then max_x = p.x end
if p.y > max_y then max_y = p.y end
if p.z > max_z then max_z = p.z end
end
return point.x >= min_x and point.x <= max_x and point.y >= min_y and point.y <= max_y and point.z >= min_z and point.z <= max_z
end

View File

@ -0,0 +1,333 @@
---
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.LINE
-- @image MOOSE.JPG
--- LINE class.
-- @type LINE
-- @field #string ClassName Name of the class.
-- @field #number Points points of the line
-- @field #number Coords coordinates of the line
--
-- ===
---
-- @field #LINE
LINE = {
ClassName = "LINE",
Points = {},
Coords = {},
}
--- Finds a line on the map by its name. The line must be drawn in the Mission Editor
-- @param #string line_name Name of the line to find
-- @return #LINE The found line, or nil if not found
function LINE:FindOnMap(line_name)
local self = BASE:Inherit(self, SHAPE_BASE:FindOnMap(line_name))
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if object["name"] == line_name then
if object["primitiveType"] == "Line" then
for _, point in UTILS.spairs(object["points"]) do
local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] }
local coord = COORDINATE:NewFromVec2(p)
table.insert(self.Points, p)
table.insert(self.Coords, coord)
end
end
end
end
end
self:I(#self.Points)
if #self.Points == 0 then
return nil
end
self.MarkIDs = {}
return self
end
--- Finds a line by its name in the database.
-- @param #string shape_name Name of the line to find
-- @return #LINE The found line, or nil if not found
function LINE:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
--- Creates a new line from two points.
-- @param #table vec2 The first point of the line
-- @param #number radius The second point of the line
-- @return #LINE The new line
function LINE:New(...)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.Points = {...}
self:I(self.Points)
for _, point in UTILS.spairs(self.Points) do
table.insert(self.Coords, COORDINATE:NewFromVec2(point))
end
return self
end
--- Creates a new line from a circle.
-- @param #table center_point center point of the circle
-- @param #number radius radius of the circle, half length of the line
-- @param #number angle_degrees degrees the line will form from center point
-- @return #LINE The new line
function LINE:NewFromCircle(center_point, radius, angle_degrees)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.CenterVec2 = center_point
local angleRadians = math.rad(angle_degrees)
local point1 = {
x = center_point.x + radius * math.cos(angleRadians),
y = center_point.y + radius * math.sin(angleRadians)
}
local point2 = {
x = center_point.x + radius * math.cos(angleRadians + math.pi),
y = center_point.y + radius * math.sin(angleRadians + math.pi)
}
for _, point in pairs{point1, point2} do
table.insert(self.Points, point)
table.insert(self.Coords, COORDINATE:NewFromVec2(point))
end
return self
end
--- Gets the coordinates of the line.
-- @return #table The coordinates of the line
function LINE:Coordinates()
return self.Coords
end
--- Gets the start coordinate of the line. The start coordinate is the first point of the line.
-- @return #COORDINATE The start coordinate of the line
function LINE:GetStartCoordinate()
return self.Coords[1]
end
--- Gets the end coordinate of the line. The end coordinate is the last point of the line.
-- @return #COORDINATE The end coordinate of the line
function LINE:GetEndCoordinate()
return self.Coords[#self.Coords]
end
--- Gets the start point of the line. The start point is the first point of the line.
-- @return #table The start point of the line
function LINE:GetStartPoint()
return self.Points[1]
end
--- Gets the end point of the line. The end point is the last point of the line.
-- @return #table The end point of the line
function LINE:GetEndPoint()
return self.Points[#self.Points]
end
--- Gets the length of the line.
-- @return #number The length of the line
function LINE:GetLength()
local total_length = 0
for i=1, #self.Points - 1 do
local x1, y1 = self.Points[i]["x"], self.Points[i]["y"]
local x2, y2 = self.Points[i+1]["x"], self.Points[i+1]["y"]
local segment_length = math.sqrt((x2 - x1)^2 + (y2 - y1)^2)
total_length = total_length + segment_length
end
return total_length
end
--- Returns a random point on the line.
-- @param #table points (optional) The points of the line or 2 other points if you're just using the LINE class without an object of it
-- @return #table The random point
function LINE:GetRandomPoint(points)
points = points or self.Points
local rand = math.random() -- 0->1
local random_x = points[1].x + rand * (points[2].x - points[1].x)
local random_y = points[1].y + rand * (points[2].y - points[1].y)
return { x= random_x, y= random_y }
end
--- Gets the heading of the line.
-- @param #table points (optional) The points of the line or 2 other points if you're just using the LINE class without an object of it
-- @return #number The heading of the line
function LINE:GetHeading(points)
points = points or self.Points
local angle = math.atan2(points[2].y - points[1].y, points[2].x - points[1].x)
angle = math.deg(angle)
if angle < 0 then
angle = angle + 360
end
return angle
end
--- Return each part of the line as a new line
-- @return #table The points
function LINE:GetIndividualParts()
local parts = {}
if #self.Points == 2 then
parts = {self}
end
for i=1, #self.Points -1 do
local p1 = self.Points[i]
local p2 = self.Points[i % #self.Points + 1]
table.add(parts, LINE:New(p1, p2))
end
return parts
end
--- Gets a number of points in between the start and end points of the line.
-- @param #number amount The number of points to get
-- @param #table start_point (Optional) The start point of the line, defaults to the object's start point
-- @param #table end_point (Optional) The end point of the line, defaults to the object's end point
-- @return #table The points
function LINE:GetPointsInbetween(amount, start_point, end_point)
start_point = start_point or self:GetStartPoint()
end_point = end_point or self:GetEndPoint()
if amount == 0 then return {start_point, end_point} end
amount = amount + 1
local points = {}
local difference = { x = end_point.x - start_point.x, y = end_point.y - start_point.y }
local divided = { x = difference.x / amount, y = difference.y / amount }
for j=0, amount do
local part_pos = {x = divided.x * j, y = divided.y * j}
-- add part_pos vector to the start point so the new point is placed along in the line
local point = {x = start_point.x + part_pos.x, y = start_point.y + part_pos.y}
table.insert(points, point)
end
return points
end
--- Gets a number of points in between the start and end points of the line.
-- @param #number amount The number of points to get
-- @param #table start_point (Optional) The start point of the line, defaults to the object's start point
-- @param #table end_point (Optional) The end point of the line, defaults to the object's end point
-- @return #table The points
function LINE:GetCoordinatesInBetween(amount, start_point, end_point)
local coords = {}
for _, pt in pairs(self:GetPointsInbetween(amount, start_point, end_point)) do
table.add(coords, COORDINATE:NewFromVec2(pt))
end
return coords
end
function LINE:GetRandomPoint(start_point, end_point)
start_point = start_point or self:GetStartPoint()
end_point = end_point or self:GetEndPoint()
local fraction = math.random()
local difference = { x = end_point.x - start_point.x, y = end_point.y - start_point.y }
local part_pos = {x = difference.x * fraction, y = difference.y * fraction}
local random_point = { x = start_point.x + part_pos.x, y = start_point.y + part_pos.y}
return random_point
end
function LINE:GetRandomCoordinate(start_point, end_point)
start_point = start_point or self:GetStartPoint()
end_point = end_point or self:GetEndPoint()
return COORDINATE:NewFromVec2(self:GetRandomPoint(start_point, end_point))
end
--- Gets a number of points on a sine wave between the start and end points of the line.
-- @param #number amount The number of points to get
-- @param #table start_point (Optional) The start point of the line, defaults to the object's start point
-- @param #table end_point (Optional) The end point of the line, defaults to the object's end point
-- @param #number frequency (Optional) The frequency of the sine wave, default 1
-- @param #number phase (Optional) The phase of the sine wave, default 0
-- @param #number amplitude (Optional) The amplitude of the sine wave, default 100
-- @return #table The points
function LINE:GetPointsBetweenAsSineWave(amount, start_point, end_point, frequency, phase, amplitude)
amount = amount or 20
start_point = start_point or self:GetStartPoint()
end_point = end_point or self:GetEndPoint()
frequency = frequency or 1 -- number of cycles per unit of x
phase = phase or 0 -- offset in radians
amplitude = amplitude or 100 -- maximum height of the wave
local points = {}
-- Returns the y-coordinate of the sine wave at x
local function sine_wave(x)
return amplitude * math.sin(2 * math.pi * frequency * (x - start_point.x) + phase)
end
-- Plot x-amount of points on the sine wave between point_01 and point_02
local x = start_point.x
local step = (end_point.x - start_point.x) / 20
for _=1, amount do
local y = sine_wave(x)
x = x + step
table.add(points, {x=x, y=y})
end
return points
end
--- Calculates the bounding box of the line. The bounding box is the smallest rectangle that contains the line.
-- @return #table The bounding box of the line
function LINE:GetBoundingBox()
local min_x, min_y, max_x, max_y = self.Points[1].x, self.Points[1].y, self.Points[2].x, self.Points[2].y
for i = 2, #self.Points do
local x, y = self.Points[i].x, self.Points[i].y
if x < min_x then
min_x = x
end
if y < min_y then
min_y = y
end
if x > max_x then
max_x = x
end
if y > max_y then
max_y = y
end
end
return {
{x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y}
}
end
--- Draws the line on the map.
-- @param #table points The points of the line
function LINE:Draw()
for i=1, #self.Coords -1 do
local c1 = self.Coords[i]
local c2 = self.Coords[i % #self.Coords + 1]
table.add(self.MarkIDs, c1:LineToAll(c2))
end
end
--- Removes the drawing of the line from the map.
function LINE:RemoveDraw()
for _, mark_id in pairs(self.MarkIDs) do
UTILS.RemoveMark(mark_id)
end
end

View File

@ -0,0 +1,213 @@
---
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.OVAL
-- @image MOOSE.JPG
--- OVAL class.
-- @type OVAL
-- @field #string ClassName Name of the class.
-- @field #number MajorAxis The major axis (radius) of the oval
-- @field #number MinorAxis The minor axis (radius) of the oval
-- @field #number Angle The angle the oval is rotated on
--- *The little man removed his hat, what an egg shaped head he had* -- Agatha Christie
--
-- ===
--
-- # OVAL
-- OVALs can be fetched from the drawings in the Mission Editor
--
-- The major and minor axes define how elongated the shape of an oval is. This class has some basic functions that the other SHAPE classes have as well.
-- Since it's not possible to draw the shape of an oval while the mission is running, right now the draw function draws 2 cicles. One with the major axis and one with
-- the minor axis. It then draws a diamond shape on an angle where the corners touch the major and minor axes to give an indication of what the oval actually
-- looks like.
--
-- Using ovals can be handy to find an area on the ground that is actually an intersection of a cone and a plane. So imagine you're faking the view cone of
-- a targeting pod and
--- OVAL class with properties and methods for handling ovals.
-- @field #OVAL
OVAL = {
ClassName = "OVAL",
MajorAxis = nil,
MinorAxis = nil,
Angle = 0,
DrawPoly=nil
}
--- Finds an oval on the map by its name. The oval must be drawn on the map.
-- @param #string shape_name Name of the oval to find
-- @return #OVAL The found oval, or nil if not found
function OVAL:FindOnMap(shape_name)
local self = BASE:Inherit(self, SHAPE_BASE:FindOnMap(shape_name))
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if string.find(object["name"], shape_name, 1, true) then
if object["polygonMode"] == "oval" then
self.CenterVec2 = { x = object["mapX"], y = object["mapY"] }
self.MajorAxis = object["r1"]
self.MinorAxis = object["r2"]
self.Angle = object["angle"]
end
end
end
end
return self
end
--- Finds an oval by its name in the database.
-- @param #string shape_name Name of the oval to find
-- @return #OVAL The found oval, or nil if not found
function OVAL:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
--- Creates a new oval from a center point, major axis, minor axis, and angle.
-- @param #table vec2 The center point of the oval
-- @param #number major_axis The major axis of the oval
-- @param #number minor_axis The minor axis of the oval
-- @param #number angle The angle of the oval
-- @return #OVAL The new oval
function OVAL:New(vec2, major_axis, minor_axis, angle)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.CenterVec2 = vec2
self.MajorAxis = major_axis
self.MinorAxis = minor_axis
self.Angle = angle or 0
return self
end
--- Gets the major axis of the oval.
-- @return #number The major axis of the oval
function OVAL:GetMajorAxis()
return self.MajorAxis
end
--- Gets the minor axis of the oval.
-- @return #number The minor axis of the oval
function OVAL:GetMinorAxis()
return self.MinorAxis
end
--- Gets the angle of the oval.
-- @return #number The angle of the oval
function OVAL:GetAngle()
return self.Angle
end
--- Sets the major axis of the oval.
-- @param #number value The new major axis
function OVAL:SetMajorAxis(value)
self.MajorAxis = value
end
--- Sets the minor axis of the oval.
-- @param #number value The new minor axis
function OVAL:SetMinorAxis(value)
self.MinorAxis = value
end
--- Sets the angle of the oval.
-- @param #number value The new angle
function OVAL:SetAngle(value)
self.Angle = value
end
--- Checks if a point is contained within the oval.
-- @param #table point The point to check
-- @return #bool True if the point is contained, false otherwise
function OVAL:ContainsPoint(point)
local cos, sin = math.cos, math.sin
local dx = point.x - self.CenterVec2.x
local dy = point.y - self.CenterVec2.y
local rx = dx * cos(self.Angle) + dy * sin(self.Angle)
local ry = -dx * sin(self.Angle) + dy * cos(self.Angle)
return rx * rx / (self.MajorAxis * self.MajorAxis) + ry * ry / (self.MinorAxis * self.MinorAxis) <= 1
end
--- Returns a random Vec2 within the oval.
-- @return #table The random Vec2
function OVAL:GetRandomVec2()
local theta = math.rad(self.Angle)
local random_point = math.sqrt(math.random()) --> uniformly
--local random_point = math.random() --> more clumped around center
local phi = math.random() * 2 * math.pi
local x_c = random_point * math.cos(phi)
local y_c = random_point * math.sin(phi)
local x_e = x_c * self.MajorAxis
local y_e = y_c * self.MinorAxis
local rx = (x_e * math.cos(theta) - y_e * math.sin(theta)) + self.CenterVec2.x
local ry = (x_e * math.sin(theta) + y_e * math.cos(theta)) + self.CenterVec2.y
return {x=rx, y=ry}
end
--- Calculates the bounding box of the oval. The bounding box is the smallest rectangle that contains the oval.
-- @return #table The bounding box of the oval
function OVAL:GetBoundingBox()
local min_x = self.CenterVec2.x - self.MajorAxis
local min_y = self.CenterVec2.y - self.MinorAxis
local max_x = self.CenterVec2.x + self.MajorAxis
local max_y = self.CenterVec2.y + self.MinorAxis
return {
{x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y}
}
end
--- Draws the oval on the map, for debugging
-- @param #number angle (Optional) The angle of the oval. If nil will use self.Angle
function OVAL:Draw()
--for pt in pairs(self:PointsOnEdge(20)) do
-- COORDINATE:NewFromVec2(pt)
--end
self.DrawPoly = POLYGON:NewFromPoints(self:PointsOnEdge(20))
self.DrawPoly:Draw(true)
---- TODO: draw a better shape using line segments
--angle = angle or self.Angle
--local coor = self:GetCenterCoordinate()
--
--table.add(self.MarkIDs, coor:CircleToAll(self.MajorAxis))
--table.add(self.MarkIDs, coor:CircleToAll(self.MinorAxis))
--table.add(self.MarkIDs, coor:LineToAll(coor:Translate(self.MajorAxis, self.Angle)))
--
--local pt_1 = coor:Translate(self.MajorAxis, self.Angle)
--local pt_2 = coor:Translate(self.MinorAxis, self.Angle - 90)
--local pt_3 = coor:Translate(self.MajorAxis, self.Angle - 180)
--local pt_4 = coor:Translate(self.MinorAxis, self.Angle - 270)
--table.add(self.MarkIDs, pt_1:QuadToAll(pt_2, pt_3, pt_4), -1, {0, 1, 0}, 1, {0, 1, 0})
end
--- Removes the drawing of the oval from the map
function OVAL:RemoveDraw()
self.DrawPoly:RemoveDraw()
end
function OVAL:PointsOnEdge(num_points)
num_points = num_points or 20
local points = {}
local dtheta = 2 * math.pi / num_points
for i = 0, num_points - 1 do
local theta = i * dtheta
local x = self.CenterVec2.x + self.MajorAxis * math.cos(theta) * math.cos(self.Angle) - self.MinorAxis * math.sin(theta) * math.sin(self.Angle)
local y = self.CenterVec2.y + self.MajorAxis * math.cos(theta) * math.sin(self.Angle) + self.MinorAxis * math.sin(theta) * math.cos(self.Angle)
table.insert(points, {x = x, y = y})
end
return points
end

View File

@ -0,0 +1,458 @@
---
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.POLYGON
-- @image MOOSE.JPG
--- POLYGON class.
-- @type POLYGON
-- @field #string ClassName Name of the class.
-- @field #table Points List of 3D points defining the shape, this will be assigned automatically if you're passing in a drawing from the Mission Editor
-- @field #table Coords List of COORDINATE defining the path, this will be assigned automatically if you're passing in a drawing from the Mission Editor
-- @field #table MarkIDs List any MARKIDs this class use, this will be assigned automatically if you're passing in a drawing from the Mission Editor
-- @field #table Triangles List of TRIANGLEs that make up the shape of the POLYGON after being triangulated
-- @extends Core.Base#BASE
--- *Polygons are fashionable at the moment* -- Trip Hawkins
--
-- ===
--
-- # POLYGON
-- POLYGONs can be fetched from the drawings in the Mission Editor if the drawing is:
-- * A closed shape made with line segments
-- * A closed shape made with a freehand line
-- * A freehand drawn polygon
-- * A rect
-- Use the POLYGON:FindOnMap() of POLYGON:Find() functions for this. You can also create a non existing polygon in memory using the POLYGON:New() function. Pass in a
-- any number of Vec2s into this function to define the shape of the polygon you want.
--
-- You can draw very intricate and complex polygons in the Mission Editor to avoid (or include) map objects. You can then generate random points within this complex
-- shape for spawning groups or checking positions.
--
-- When a POLYGON is made, it's automatically triangulated. The resulting triangles are stored in POLYGON.Triangles. This also immeadiately saves the surface area
-- of the POLYGON. Because the POLYGON is triangulated, it's possible to generate random points within this POLYGON without having to use a trial and error method to see if
-- the point is contained within the shape.
-- Using POLYGON:GetRandomVec2() will result in a truly, non-biased, random Vec2 within the shape. You'll want to use this function most. There's also POLYGON:GetRandomNonWeightedVec2
-- which ignores the size of the triangles in the polygon to pick a random points. This will result in more points clumping together in parts of the polygon where the triangles are
-- the smallest.
---
-- @field #POLYGON
POLYGON = {
ClassName = "POLYGON",
Points = {},
Coords = {},
Triangles = {},
SurfaceArea = 0,
TriangleMarkIDs = {},
OutlineMarkIDs = {},
Angle = nil, -- for arrows
Heading = nil -- for arrows
}
--- Finds a polygon on the map by its name. The polygon must be added in the mission editor.
-- @param #string shape_name Name of the polygon to find
-- @return #POLYGON The found polygon, or nil if not found
function POLYGON:FindOnMap(shape_name)
local self = BASE:Inherit(self, SHAPE_BASE:FindOnMap(shape_name))
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if object["name"] == shape_name then
if (object["primitiveType"] == "Line" and object["closed"] == true) or (object["polygonMode"] == "free") then
for _, point in UTILS.spairs(object["points"]) do
local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] }
local coord = COORDINATE:NewFromVec2(p)
self.Points[#self.Points + 1] = p
self.Coords[#self.Coords + 1] = coord
end
elseif object["polygonMode"] == "rect" then
local angle = object["angle"]
local half_width = object["width"] / 2
local half_height = object["height"] / 2
local p1 = UTILS.RotatePointAroundPivot({ x = self.CenterVec2.x - half_height, y = self.CenterVec2.y + half_width }, self.CenterVec2, angle)
local p2 = UTILS.RotatePointAroundPivot({ x = self.CenterVec2.x + half_height, y = self.CenterVec2.y + half_width }, self.CenterVec2, angle)
local p3 = UTILS.RotatePointAroundPivot({ x = self.CenterVec2.x + half_height, y = self.CenterVec2.y - half_width }, self.CenterVec2, angle)
local p4 = UTILS.RotatePointAroundPivot({ x = self.CenterVec2.x - half_height, y = self.CenterVec2.y - half_width }, self.CenterVec2, angle)
self.Points = {p1, p2, p3, p4}
for _, point in pairs(self.Points) do
self.Coords[#self.Coords + 1] = COORDINATE:NewFromVec2(point)
end
elseif object["polygonMode"] == "arrow" then
for _, point in UTILS.spairs(object["points"]) do
local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] }
local coord = COORDINATE:NewFromVec2(p)
self.Points[#self.Points + 1] = p
self.Coords[#self.Coords + 1] = coord
end
self.Angle = object["angle"]
self.Heading = UTILS.ClampAngle(self.Angle + 90)
end
end
end
end
if #self.Points == 0 then
return nil
end
self.CenterVec2 = self:GetCentroid()
self.Triangles = self:Triangulate()
self.SurfaceArea = self:__CalculateSurfaceArea()
self.TriangleMarkIDs = {}
self.OutlineMarkIDs = {}
return self
end
--- Creates a polygon from a zone. The zone must be defined in the mission.
-- @param #string zone_name Name of the zone
-- @return #POLYGON The polygon created from the zone, or nil if the zone is not found
function POLYGON:FromZone(zone_name)
for _, zone in pairs(env.mission.triggers.zones) do
if zone["name"] == zone_name then
return POLYGON:New(unpack(zone["verticies"] or {}))
end
end
end
--- Finds a polygon by its name in the database.
-- @param #string shape_name Name of the polygon to find
-- @return #POLYGON The found polygon, or nil if not found
function POLYGON:Find(shape_name)
return _DATABASE:FindShape(shape_name)
end
--- Creates a new polygon from a list of points. Each point is a table with 'x' and 'y' fields.
-- @param #table ... Points of the polygon
-- @return #POLYGON The new polygon
function POLYGON:New(...)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.Points = {...}
self.Coords = {}
for _, point in UTILS.spairs(self.Points) do
table.insert(self.Coords, COORDINATE:NewFromVec2(point))
end
self.Triangles = self:Triangulate()
self.SurfaceArea = self:__CalculateSurfaceArea()
return self
end
--- Calculates the centroid of the polygon. The centroid is the average of the 'x' and 'y' coordinates of the points.
-- @return #table The centroid of the polygon
function POLYGON:GetCentroid()
local function sum(t)
local total = 0
for _, value in pairs(t) do
total = total + value
end
return total
end
local x_values = {}
local y_values = {}
local length = table.length(self.Points)
for _, point in pairs(self.Points) do
table.insert(x_values, point.x)
table.insert(y_values, point.y)
end
local x = sum(x_values) / length
local y = sum(y_values) / length
return {
["x"] = x,
["y"] = y
}
end
--- Returns the coordinates of the polygon. Each coordinate is a COORDINATE object.
-- @return #table The coordinates of the polygon
function POLYGON:GetCoordinates()
return self.Coords
end
--- Returns the start coordinate of the polygon. The start coordinate is the first point of the polygon.
-- @return #COORDINATE The start coordinate of the polygon
function POLYGON:GetStartCoordinate()
return self.Coords[1]
end
--- Returns the end coordinate of the polygon. The end coordinate is the last point of the polygon.
-- @return #COORDINATE The end coordinate of the polygon
function POLYGON:GetEndCoordinate()
return self.Coords[#self.Coords]
end
--- Returns the start point of the polygon. The start point is the first point of the polygon.
-- @return #table The start point of the polygon
function POLYGON:GetStartPoint()
return self.Points[1]
end
--- Returns the end point of the polygon. The end point is the last point of the polygon.
-- @return #table The end point of the polygon
function POLYGON:GetEndPoint()
return self.Points[#self.Points]
end
--- Returns the points of the polygon. Each point is a table with 'x' and 'y' fields.
-- @return #table The points of the polygon
function POLYGON:GetPoints()
return self.Points
end
--- Calculates the surface area of the polygon. The surface area is the sum of the areas of the triangles that make up the polygon.
-- @return #number The surface area of the polygon
function POLYGON:GetSurfaceArea()
return self.SurfaceArea
end
--- Calculates the bounding box of the polygon. The bounding box is the smallest rectangle that contains the polygon.
-- @return #table The bounding box of the polygon
function POLYGON:GetBoundingBox()
local min_x, min_y, max_x, max_y = self.Points[1].x, self.Points[1].y, self.Points[1].x, self.Points[1].y
for i = 2, #self.Points do
local x, y = self.Points[i].x, self.Points[i].y
if x < min_x then
min_x = x
end
if y < min_y then
min_y = y
end
if x > max_x then
max_x = x
end
if y > max_y then
max_y = y
end
end
return {
{x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y}
}
end
--- Triangulates the polygon. The polygon is divided into triangles.
-- @param #table points (optional) Points of the polygon or other points if you're just using the POLYGON class without an object of it
-- @return #table The triangles of the polygon
function POLYGON:Triangulate(points)
points = points or self.Points
local triangles = {}
local function get_orientation(shape_points)
local sum = 0
for i = 1, #shape_points do
local j = i % #shape_points + 1
sum = sum + (shape_points[j].x - shape_points[i].x) * (shape_points[j].y + shape_points[i].y)
end
return sum >= 0 and "clockwise" or "counter-clockwise" -- sum >= 0, return "clockwise", else return "counter-clockwise"
end
local function ensure_clockwise(shape_points)
local orientation = get_orientation(shape_points)
if orientation == "counter-clockwise" then
-- Reverse the order of shape_points so they're clockwise
local reversed = {}
for i = #shape_points, 1, -1 do
table.insert(reversed, shape_points[i])
end
return reversed
end
return shape_points
end
local function is_clockwise(p1, p2, p3)
local cross_product = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
return cross_product < 0
end
local function divide_recursively(shape_points)
if #shape_points == 3 then
table.insert(triangles, TRIANGLE:New(shape_points[1], shape_points[2], shape_points[3]))
elseif #shape_points > 3 then -- find an ear -> a triangle with no other points inside it
for i, p1 in ipairs(shape_points) do
local p2 = shape_points[(i % #shape_points) + 1]
local p3 = shape_points[(i + 1) % #shape_points + 1]
local triangle = TRIANGLE:New(p1, p2, p3)
local is_ear = true
if not is_clockwise(p1, p2, p3) then
is_ear = false
else
for _, point in ipairs(shape_points) do
if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then
is_ear = false
break
end
end
end
if is_ear then
-- Check if any point in the original polygon is inside the ear triangle
local is_valid_triangle = true
for _, point in ipairs(points) do
if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then
is_valid_triangle = false
break
end
end
if is_valid_triangle then
table.insert(triangles, triangle)
local remaining_points = {}
for j, point in ipairs(shape_points) do
if point ~= p2 then
table.insert(remaining_points, point)
end
end
divide_recursively(remaining_points)
break
end
end
end
end
end
points = ensure_clockwise(points)
divide_recursively(points)
return triangles
end
function POLYGON:CovarianceMatrix()
local cx, cy = self:GetCentroid()
local covXX, covYY, covXY = 0, 0, 0
for _, p in ipairs(self.points) do
covXX = covXX + (p.x - cx)^2
covYY = covYY + (p.y - cy)^2
covXY = covXY + (p.x - cx) * (p.y - cy)
end
covXX = covXX / (#self.points - 1)
covYY = covYY / (#self.points - 1)
covXY = covXY / (#self.points - 1)
return covXX, covYY, covXY
end
function POLYGON:Direction()
local covXX, covYY, covXY = self:CovarianceMatrix()
-- Simplified calculation for the largest eigenvector's direction
local theta = 0.5 * math.atan2(2 * covXY, covXX - covYY)
return math.cos(theta), math.sin(theta)
end
--- Returns a random Vec2 within the polygon. The Vec2 is weighted by the areas of the triangles that make up the polygon.
-- @return #table The random Vec2
function POLYGON:GetRandomVec2()
local weights = {}
for _, triangle in pairs(self.Triangles) do
weights[triangle] = triangle.SurfaceArea / self.SurfaceArea
end
local random_weight = math.random()
local accumulated_weight = 0
for triangle, weight in pairs(weights) do
accumulated_weight = accumulated_weight + weight
if accumulated_weight >= random_weight then
return triangle:GetRandomVec2()
end
end
end
--- Returns a random non-weighted Vec2 within the polygon. The Vec2 is chosen from one of the triangles that make up the polygon.
-- @return #table The random non-weighted Vec2
function POLYGON:GetRandomNonWeightedVec2()
return self.Triangles[math.random(1, #self.Triangles)]:GetRandomVec2()
end
--- Checks if a point is contained within the polygon. The point is a table with 'x' and 'y' fields.
-- @param #table point The point to check
-- @param #table points (optional) Points of the polygon or other points if you're just using the POLYGON class without an object of it
-- @return #bool True if the point is contained, false otherwise
function POLYGON:ContainsPoint(point, polygon_points)
local x = point.x
local y = point.y
polygon_points = polygon_points or self.Points
local counter = 0
local num_points = #polygon_points
for current_index = 1, num_points do
local next_index = (current_index % num_points) + 1
local current_x, current_y = polygon_points[current_index].x, polygon_points[current_index].y
local next_x, next_y = polygon_points[next_index].x, polygon_points[next_index].y
if ((current_y > y) ~= (next_y > y)) and (x < (next_x - current_x) * (y - current_y) / (next_y - current_y) + current_x) then
counter = counter + 1
end
end
return counter % 2 == 1
end
--- Draws the polygon on the map. The polygon can be drawn with or without inner triangles. This is just for debugging
-- @param #bool include_inner_triangles Whether to include inner triangles in the drawing
function POLYGON:Draw(include_inner_triangles)
include_inner_triangles = include_inner_triangles or false
for i=1, #self.Coords do
local c1 = self.Coords[i]
local c2 = self.Coords[i % #self.Coords + 1]
table.add(self.OutlineMarkIDs, c1:LineToAll(c2))
end
if include_inner_triangles then
for _, triangle in ipairs(self.Triangles) do
triangle:Draw()
end
end
end
--- Removes the drawing of the polygon from the map.
function POLYGON:RemoveDraw()
for _, triangle in pairs(self.Triangles) do
triangle:RemoveDraw()
end
for _, mark_id in pairs(self.OutlineMarkIDs) do
UTILS.RemoveMark(mark_id)
end
end
--- Calculates the surface area of the polygon. The surface area is the sum of the areas of the triangles that make up the polygon.
-- @return #number The surface area of the polygon
function POLYGON:__CalculateSurfaceArea()
local area = 0
for _, triangle in pairs(self.Triangles) do
area = area + triangle.SurfaceArea
end
return area
end

View File

@ -0,0 +1,214 @@
--- **Shapes** - Class that serves as the base shapes drawn in the Mission Editor
--
--
-- ### Author: **nielsvaes/coconutcockpit**
--
-- ===
-- @module Shapes.SHAPE_BASE
-- @image CORE_Pathline.png
--- SHAPE_BASE class.
-- @type SHAPE_BASE
-- @field #string ClassName Name of the class.
-- @field #string Name Name of the shape
-- @field #table CenterVec2 Vec2 of the center of the shape, this will be assigned automatically
-- @field #table Points List of 3D points defining the shape, this will be assigned automatically
-- @field #table Coords List of COORDINATE defining the path, this will be assigned automatically
-- @field #table MarkIDs List any MARKIDs this class use, this will be assigned automatically
-- @extends Core.Base#BASE
--- *I'm in love with the shape of you -- Ed Sheeran
--
-- ===
--
-- # SHAPE_BASE
-- The class serves as the base class to deal with these shapes using MOOSE. You should never use this class on its own,
-- rather use:
-- CIRCLE
-- LINE
-- OVAL
-- POLYGON
-- TRIANGLE (although this one's a bit special as well)
--
-- ===
-- The idea is that anything you draw on the map in the Mission Editor can be turned in a shape to work with in MOOSE.
-- This is the base class that all other shape classes are built on. There are some shared functions, most of which are overridden in the derived classes
--
-- @field #SHAPE_BASE
SHAPE_BASE = {
ClassName = "SHAPE_BASE",
Name = "",
CenterVec2 = nil,
Points = {},
Coords = {},
MarkIDs = {},
ColorString = "",
ColorRGBA = {}
}
--- Creates a new instance of SHAPE_BASE.
-- @return #SHAPE_BASE The new instance
function SHAPE_BASE:New()
local self = BASE:Inherit(self, BASE:New())
return self
end
--- Finds a shape on the map by its name.
-- @param #string shape_name Name of the shape to find
-- @return #SHAPE_BASE The found shape
function SHAPE_BASE:FindOnMap(shape_name)
local self = BASE:Inherit(self, BASE:New())
local found = false
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if object["name"] == shape_name then
self.Name = object["name"]
self.CenterVec2 = { x = object["mapX"], y = object["mapY"] }
self.ColorString = object["colorString"]
self.ColorRGBA = UTILS.HexToRGBA(self.ColorString)
found = true
end
end
end
if not found then
self:E("Can't find a shape with name " .. shape_name)
end
return self
end
function SHAPE_BASE:GetAllShapes(filter)
filter = filter or ""
local return_shapes = {}
for _, layer in pairs(env.mission.drawings.layers) do
for _, object in pairs(layer["objects"]) do
if string.contains(object["name"], filter) then
table.add(return_shapes, object)
end
end
end
return return_shapes
end
--- Offsets the shape to a new position.
-- @param #table new_vec2 The new position
function SHAPE_BASE:Offset(new_vec2)
local offset_vec2 = UTILS.Vec2Subtract(new_vec2, self.CenterVec2)
self.CenterVec2 = new_vec2
if self.ClassName == "POLYGON" then
for _, point in pairs(self.Points) do
point.x = point.x + offset_vec2.x
point.y = point.y + offset_vec2.y
end
end
end
--- Gets the name of the shape.
-- @return #string The name of the shape
function SHAPE_BASE:GetName()
return self.Name
end
function SHAPE_BASE:GetColorString()
return self.ColorString
end
function SHAPE_BASE:GetColorRGBA()
return self.ColorRGBA
end
function SHAPE_BASE:GetColorRed()
return self.ColorRGBA.R
end
function SHAPE_BASE:GetColorGreen()
return self.ColorRGBA.G
end
function SHAPE_BASE:GetColorBlue()
return self.ColorRGBA.B
end
function SHAPE_BASE:GetColorAlpha()
return self.ColorRGBA.A
end
--- Gets the center position of the shape.
-- @return #table The center position
function SHAPE_BASE:GetCenterVec2()
return self.CenterVec2
end
--- Gets the center coordinate of the shape.
-- @return #COORDINATE The center coordinate
function SHAPE_BASE:GetCenterCoordinate()
return COORDINATE:NewFromVec2(self.CenterVec2)
end
--- Gets the coordinate of the shape.
-- @return #COORDINATE The coordinate
function SHAPE_BASE:GetCoordinate()
return self:GetCenterCoordinate()
end
--- Checks if a point is contained within the shape.
-- @param #table _ The point to check
-- @return #bool True if the point is contained, false otherwise
function SHAPE_BASE:ContainsPoint(_)
self:E("This needs to be set in the derived class")
end
--- Checks if a unit is contained within the shape.
-- @param #string unit_name The name of the unit to check
-- @return #bool True if the unit is contained, false otherwise
function SHAPE_BASE:ContainsUnit(unit_name)
local unit = UNIT:FindByName(unit_name)
if unit == nil or not unit:IsAlive() then
return false
end
if self:ContainsPoint(unit:GetVec2()) then
return true
end
return false
end
--- Checks if any unit of a group is contained within the shape.
-- @param #string group_name The name of the group to check
-- @return #bool True if any unit of the group is contained, false otherwise
function SHAPE_BASE:ContainsAnyOfGroup(group_name)
local group = GROUP:FindByName(group_name)
if group == nil or not group:IsAlive() then
return false
end
for _, unit in pairs(group:GetUnits()) do
if self:ContainsPoint(unit:GetVec2()) then
return true
end
end
return false
end
--- Checks if all units of a group are contained within the shape.
-- @param #string group_name The name of the group to check
-- @return #bool True if all units of the group are contained, false otherwise
function SHAPE_BASE:ContainsAllOfGroup(group_name)
local group = GROUP:FindByName(group_name)
if group == nil or not group:IsAlive() then
return false
end
for _, unit in pairs(group:GetUnits()) do
if not self:ContainsPoint(unit:GetVec2()) then
return false
end
end
return true
end

View File

@ -0,0 +1,101 @@
--- TRIANGLE class with properties and methods for handling triangles. This class is mostly used by the POLYGON class, but you can use it on its own as well
--
-- ### Author: **nielsvaes/coconutcockpit**
--
--
-- ===
-- @module Shapes.TRIANGLE
-- @image MOOSE.JPG
--- LINE class.
-- @type CUBE
-- @field #string ClassName Name of the class.
-- @field #number Points points of the line
-- @field #number Coords coordinates of the line
--
-- ===
---
-- @field #TRIANGLE
TRIANGLE = {
ClassName = "TRIANGLE",
Points = {},
Coords = {},
SurfaceArea = 0
}
--- Creates a new triangle from three points. The points need to be given as Vec2s
-- @param #table p1 The first point of the triangle
-- @param #table p2 The second point of the triangle
-- @param #table p3 The third point of the triangle
-- @return #TRIANGLE The new triangle
function TRIANGLE:New(p1, p2, p3)
local self = BASE:Inherit(self, SHAPE_BASE:New())
self.Points = {p1, p2, p3}
local center_x = (p1.x + p2.x + p3.x) / 3
local center_y = (p1.y + p2.y + p3.y) / 3
self.CenterVec2 = {x=center_x, y=center_y}
for _, pt in pairs({p1, p2, p3}) do
table.add(self.Coords, COORDINATE:NewFromVec2(pt))
end
self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5
self.MarkIDs = {}
return self
end
--- Checks if a point is contained within the triangle.
-- @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
function TRIANGLE:ContainsPoint(pt, points)
points = points or self.Points
local function sign(p1, p2, p3)
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y)
end
local d1 = sign(pt, self.Points[1], self.Points[2])
local d2 = sign(pt, self.Points[2], self.Points[3])
local d3 = sign(pt, self.Points[3], self.Points[1])
local has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
local has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
return not (has_neg and has_pos)
end
--- Returns a random Vec2 within the triangle.
-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it
-- @return #table The random Vec2
function TRIANGLE:GetRandomVec2(points)
points = points or self.Points
local pt = {math.random(), math.random()}
table.sort(pt)
local s = pt[1]
local t = pt[2] - pt[1]
local u = 1 - pt[2]
return {x = s * points[1].x + t * points[2].x + u * points[3].x,
y = s * points[1].y + t * points[2].y + u * points[3].y}
end
--- Draws the triangle on the map, just for debugging
function TRIANGLE:Draw()
for i=1, #self.Coords do
local c1 = self.Coords[i]
local c2 = self.Coords[i % #self.Coords + 1]
table.add(self.MarkIDs, c1:LineToAll(c2))
end
end
--- Removes the drawing of the triangle from the map.
function TRIANGLE:RemoveDraw()
for _, mark_id in pairs(self.MarkIDs) do
UTILS.RemoveMark(mark_id)
end
end

View File

@ -3644,6 +3644,25 @@ function string.contains(str, value)
return string.match(str, value) return string.match(str, value)
end end
--- Moves an object from one table to another
-- @param #obj object to move
-- @param #from_table table to move from
-- @param #to_table table to move to
function table.move_object(obj, from_table, to_table)
local index
for i, v in pairs(from_table) do
if v == obj then
index = i
end
end
if index then
local moved = table.remove(from_table, index)
table.insert_unique(to_table, moved)
end
end
--- Given tbl is a indexed table ({"hello", "dcs", "world"}), checks if element exists in the table. --- Given tbl is a indexed table ({"hello", "dcs", "world"}), checks if element exists in the table.
--- The table can be made up out of complex tables or values as well --- The table can be made up out of complex tables or values as well
-- @param #table tbl -- @param #table tbl
@ -3862,6 +3881,25 @@ function UTILS.OctalToDecimal(Number)
return tonumber(Number,8) return tonumber(Number,8)
end end
--- HexToRGBA
-- @param hex_string table
-- @return #table R, G, B, A
function UTILS.HexToRGBA(hex_string)
local hexNumber = tonumber(string.sub(hex_string, 3), 16) -- convert the string to a number
-- extract RGBA components
local alpha = hexNumber % 256
hexNumber = (hexNumber - alpha) / 256
local blue = hexNumber % 256
hexNumber = (hexNumber - blue) / 256
local green = hexNumber % 256
hexNumber = (hexNumber - green) / 256
local red = hexNumber % 256
return {R = red, G = green, B = blue, A = alpha}
end
--- Function to save the position of a set of #OPSGROUP (ARMYGROUP) objects. --- Function to save the position of a set of #OPSGROUP (ARMYGROUP) objects.
-- @param Core.Set#SET_OPSGROUP Set of ops objects to save -- @param Core.Set#SET_OPSGROUP Set of ops objects to save
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.

View File

@ -762,11 +762,13 @@ AIRBASE.Sinai = {
-- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. -- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots.
-- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig. -- @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 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
AIRBASE.TerminalType = { AIRBASE.TerminalType = {
Runway=16, Runway=16,
HelicopterOnly=40, HelicopterOnly=40,
Shelter=68, Shelter=68,
OpenMed=72, OpenMed=72,
SmallSizeFighter=100,
OpenBig=104, OpenBig=104,
OpenMedOrBig=176, OpenMedOrBig=176,
HelicopterUsable=216, HelicopterUsable=216,
@ -1678,7 +1680,7 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
-- Put coordinates of free spots into table. -- Put coordinates of free spots into table.
local freespots={} local freespots={}
for _,_spot in pairs(parkingfree) do for _,_spot in pairs(parkingfree) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then -- and _spot.Term_Index>0 then --Not sure why I had this in. But caused problems now for a Gas platform where a valid spot was not included!
if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index) local spot=self:_GetParkingSpotByID(_spot.Term_Index)
@ -2025,7 +2027,7 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
match=true match=true
end end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then elseif termtype==AIRBASE.TerminalType.FighterAircraft then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter 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 match=true
end end
end end

View File

@ -359,14 +359,15 @@ end
-- @param #GROUP self -- @param #GROUP self
-- @return DCS#Group The DCS Group. -- @return DCS#Group The DCS Group.
function GROUP:GetDCSObject() function GROUP:GetDCSObject()
-- Get DCS group.
local DCSGroup = Group.getByName( self.GroupName ) local DCSGroup = Group.getByName( self.GroupName )
if DCSGroup then if DCSGroup then
return DCSGroup return DCSGroup
else
env.error("ERROR: Could not get DCS group object!")
end 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 return nil
end end
@ -1207,13 +1208,12 @@ end
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP. -- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetCoordinate() function GROUP:GetCoordinate()
local Units = self:GetUnits() or {} local Units = self:GetUnits() or {}
for _,_unit in pairs(Units) do for _,_unit in pairs(Units) do
local FirstUnit = _unit -- Wrapper.Unit#UNIT local FirstUnit = _unit -- Wrapper.Unit#UNIT
if FirstUnit then if FirstUnit and FirstUnit:IsAlive() then
local FirstUnitCoordinate = FirstUnit:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate()
@ -1225,6 +1225,22 @@ function GROUP:GetCoordinate()
end end
end end
-- no luck, try the API way
local DCSGroup = Group.getByName(self.GroupName)
local DCSUnits = DCSGroup:getUnits() or {}
for _,_unit in pairs(DCSUnits) do
if Object.isExist(_unit) then
local position = _unit:getPosition()
local point = position.p ~= nil and position.p or _unit:GetPoint()
if point then
--self:I(point)
local coord = COORDINATE:NewFromVec3(point)
return coord
end
end
end
BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } )
end end

View File

@ -723,7 +723,7 @@ end
--- Converts a JSON string to a lua value. --- Converts a JSON string to a lua value.
-- @param #string Json Anything JSON -- @param #string Json Anything JSON
-- @return #table Lua -- @return #table Lua
function NET.Lua2Json(Json) function NET.Json2Lua(Json)
return net.json2lua(Json) return net.json2lua(Json)
end end