Compare commits

..

3 Commits

Author SHA1 Message Date
kaltokri
4c66fd7cab Renamed basic.md to concepts.md and added more text 2024-01-22 17:36:32 +01:00
kaltokri
80f76b26c2 Small fixes in advanced guide 2024-01-19 16:57:33 +01:00
kaltokri
4e956c3203 New guides added 2024-01-19 11:41:09 +01:00
104 changed files with 3988 additions and 88368 deletions

View File

@@ -57,7 +57,6 @@ 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

@@ -47,7 +47,6 @@ 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

@@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
@@ -43,7 +43,7 @@ jobs:
working-directory: docs/ working-directory: docs/
- name: Setup Pages - name: Setup Pages
id: pages id: pages
uses: actions/configure-pages@v4 uses: actions/configure-pages@v3
- name: Build with Jekyll - name: Build with Jekyll
# Outputs to the './_site' directory by default # Outputs to the './_site' directory by default
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
@@ -52,7 +52,7 @@ jobs:
working-directory: docs/ working-directory: docs/
- name: Upload artifact - name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default # Automatically uploads an artifact from the './_site' directory by default
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v1
with: with:
path: docs/_site/ path: docs/_site/
@@ -66,13 +66,13 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v4 uses: actions/deploy-pages@v1
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: deploy needs: deploy
steps: steps:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v3
- run: npm install linkinator - run: npm install linkinator
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter - run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter

7
.gitignore vendored
View File

@@ -28,13 +28,6 @@ local.properties
.buildpath .buildpath
#####################
## Visual Studio Code
#####################
*.code-workspace
.vscode/
################# #################
## Visual Studio ## Visual Studio
################# #################

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:T( "Captured " .. AirbaseName ) self:I( "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:T( "Squadron " .. SquadronName .. " captured." ) self:I( "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:T( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } ) self:I( { 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:T( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:") --self:I("***** 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:T("Class Name: " .. classname) --self:I("Class Name: " .. classname)
--self:T("Unit Name: " .. unitname) --self:I("Unit Name: " .. unitname)
--self:T({AIFriendly}) --self:I({AIFriendly})
end end
local Friendly = nil local Friendly = nil
if AIFriendly and AIFriendly:IsAlive() then if AIFriendly and AIFriendly:IsAlive() then
--self:T("AIFriendly alive, getting GROUP") --self:I("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:T( { Airbases = AirbaseNames } ) self:I( { Airbases = AirbaseNames } )
self:T( "Defining Templates for Airbases ..." ) self:I( "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:T( { Airbase = AirbaseName } ) self:I( { 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:T( { Template = Template:GetName() } ) self:I( { 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:T( "Setting up CAP ..." ) self:I( "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:T( { CAPZoneGroup = CAPID } ) self:I( { 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:T( { AirbaseDistance = Distance } ) self:I( { 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:T( { CAPAirbase = AirbaseClosest:GetName() } ) self:I( { 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:T( "Setting up GCI ..." ) self:I( "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:T( { GCIAirbase = AirbaseName } ) self:I( { 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:T( "Parked resources for squadron " .. DefenderSquadron.Name ) self:I( "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:T( "Captured " .. AirbaseName ) self:I( "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:T( "Squadron " .. SquadronName .. " captured." ) self:I( "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 ) self:ResourcePark( Squadron, Defender )
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 ) self:ResourcePark( Squadron, Defender )
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 **40000** or **40km**. Override the default defense radius when the era of the warfare is early, or, -- The **default** defense radius is defined as **400000** 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 = 20000) The defense radius to engage detected targets from the nearest capable and available squadron airbase. -- @param #number DefenseRadius (Optional, Default = 200000) 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 40000 self.DefenseRadius = DefenseRadius or 100000
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:T( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:T( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) self:I( { 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:GetRandomSurely() local FirstUnit = AttackSetUnit:GetFirst()
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:T( self.Controllable:GetName() .. " is too far from home base, RTB!" ) self:I( 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:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel() self:Refuel()
else else
self:T( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) self:I( 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:T( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) self:I( 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:T( self.Controllable:GetName() .. " control lost! " ) self:I( 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,10 +617,7 @@ 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 )
@@ -641,13 +638,13 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = ToTargetCoord2 local ToAirbaseCoord = ToTargetCoord2
if Distance < 5000 then if Distance < 5000 then
self:T( "RTB and near the airbase!" ) self:I( "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:T( "Not anymore in the air, considered Home." ) self:I( "Not anymore in the air, considered Home." )
self:Home() self:Home()
return return
end end
@@ -689,12 +686,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:T( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
end end
@@ -703,17 +700,15 @@ 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:T( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local Coordinate = AIGroup:GetCoordinate() local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
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 )
@@ -727,17 +722,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:T( { "AI_AIR.Resume:", AIGroup:GetName() } ) AIGroup:I( { "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 } )
@@ -749,7 +744,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:T( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName ) self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! State=" .. self:GetState() .. ", Refuelling tanker " .. self.TankerName )
local RefuelRoute = {} local RefuelRoute = {}
@@ -803,13 +798,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 )
@@ -820,7 +815,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 )
@@ -829,7 +824,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:T( "Parked resources for squadron " .. DefenderSquadron.Name ) self:I( "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:T( "Captured " .. AirbaseName ) self:I( "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:T( "Squadron " .. SquadronName .. " captured." ) self:I( "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 ) self:ResourcePark( Squadron, Defender )
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 ) self:ResourcePark( Squadron, Defender )
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:T(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName()))) Fsm:I(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:T( { DefenderGroup, From, Event, To, AttackSetUnit } ) self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
@@ -426,13 +426,7 @@ 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:GetRandomSurely():GetPointVec3() local TargetCoord = AttackSetUnit:GetFirst():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 )
@@ -441,12 +435,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:T(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000)) --self:I(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:T(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000)) --self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
local EngageRoute = {} local EngageRoute = {}
local AttackTasks = {} local AttackTasks = {}
@@ -478,16 +472,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:T( DefenderGroupName .. ": No targets found -> Going RTB") self:I( 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:T(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName()))) Fsm:I(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
@@ -496,7 +490,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.
@@ -522,7 +516,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:GetRandomSurely():GetPointVec3() local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
if not TargetCoord then if not TargetCoord then
self:Return() self:Return()
return return
@@ -553,12 +547,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:T( DefenderGroupName .. ": No valid targets found -> Going RTB") self:I( 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:T(text) self:I(text)
DefenderGroup:OptionROEOpenFire() DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire() DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat() DefenderGroup:OptionKeepWeaponsOnThreat()
@@ -575,13 +569,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:T( DefenderGroupName .. ": No targets found -> returning.") self:I( 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:T( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) self:I( { 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:T({ IsEmpty = IsEmpty }) self:I({ 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:T(text) self:I(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:T({Arguments=unpack(Arguments)}) --self:I({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:T({EscortAirbase= self.EscortAirbase } ) self:I({EscortAirbase= self.EscortAirbase } )
self:T({PlayerGroupName = PlayerGroupName } ) self:I({PlayerGroupName = PlayerGroupName } )
self:T({PlayerGroup = PlayerGroup}) self:I({PlayerGroup = PlayerGroup})
self:T({FirstGroup = self.CarrierSet:GetFirst()}) self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) self:I({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:T({EscortAirbase= self.EscortAirbase } ) self:I({EscortAirbase= self.EscortAirbase } )
self:T({PlayerGroupName = PlayerGroupName } ) self:I({PlayerGroupName = PlayerGroupName } )
self:T({PlayerGroup = PlayerGroup}) self:I({PlayerGroup = PlayerGroup})
self:T({FirstGroup = self.CarrierSet:GetFirst()}) self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:T({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) self:I({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:T({EscortGroup = EscortGroup}) self:I({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:T( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) self:I( 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:T( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) self:I( 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

@@ -1144,28 +1144,6 @@ function BASE:TraceClassMethod( Class, Method )
self:I( "Tracing method " .. Method .. " of class " .. Class ) self:I( "Tracing method " .. Method .. " of class " .. Class )
end end
--- (Internal) Serialize arguments
-- @param #BASE self
-- @param #table Arguments
-- @return #string Text
function BASE:_Serialize(Arguments)
local text = UTILS.PrintTableToLog({Arguments}, 0, true)
text = string.gsub(text,"(\n+)","")
text = string.gsub(text,"%(%(","%(")
text = string.gsub(text,"%)%)","%)")
text = string.gsub(text,"(%s+)"," ")
return text
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.
@@ -1190,7 +1168,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, BASE:_Serialize(Arguments) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
end end
end end
end end
@@ -1264,7 +1242,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
end end
end end
end end
@@ -1336,7 +1314,7 @@ function BASE:E( Arguments )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
else else
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
end end
end end
@@ -1363,8 +1341,39 @@ function BASE:I( Arguments )
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
else else
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) ) env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
end end
end end
--- old stuff
-- function BASE:_Destructor()
-- --self:E("_Destructor")
--
-- --self:EventRemoveAll()
-- end
-- THIS IS WHY WE NEED LUA 5.2 ...
-- function BASE:_SetDestructor()
--
-- -- TODO: Okay, this is really technical...
-- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
-- -- Therefore, I am parking this logic until I've properly discussed all this with the community.
--
-- local proxy = newproxy(true)
-- local proxyMeta = getmetatable(proxy)
--
-- proxyMeta.__gc = function ()
-- env.info("In __gc for " .. self:GetClassNameAndID() )
-- if self._Destructor then
-- self:_Destructor()
-- end
-- end
--
-- -- keep the userdata from newproxy reachable until the object
-- -- table is about to be garbage-collected - then the __gc hook
-- -- will be invoked and the destructor called
-- rawset( self, '__proxy', proxy )
--
-- end

View File

@@ -8,10 +8,6 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon)
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Core.Beacon -- @module Core.Beacon
@@ -38,13 +34,11 @@
-- @type BEACON -- @type BEACON
-- @field #string ClassName Name of the class "BEACON". -- @field #string ClassName Name of the class "BEACON".
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{Wrapper.Controllable#CONTROLLABLE} that will receive radio capabilities. -- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{Wrapper.Controllable#CONTROLLABLE} that will receive radio capabilities.
-- @field #number UniqueName Counter to make the unique naming work.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
BEACON = { BEACON = {
ClassName = "BEACON", ClassName = "BEACON",
Positionable = nil, Positionable = nil,
name = nil, name = nil,
UniqueName = 0,
} }
--- Beacon types supported by DCS. --- Beacon types supported by DCS.
@@ -292,7 +286,6 @@ end
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon -- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration}) self:F({TACANChannel, Message, Bearing, BeaconDuration})
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
local IsValid = true local IsValid = true
@@ -387,8 +380,6 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
self:F({FileName, Frequency, Modulation, Power, BeaconDuration}) self:F({FileName, Frequency, Modulation, Power, BeaconDuration})
local IsValid = false local IsValid = false
Modulation = Modulation or radio.modulation.AM
-- Check the filename -- Check the filename
if type(FileName) == "string" then if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then if FileName:find(".ogg") or FileName:find(".wav") then
@@ -425,9 +416,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
if IsValid then if IsValid then
self:T2({"Activating Beacon on ", Frequency, Modulation}) self:T2({"Activating Beacon on ", Frequency, Modulation})
-- Note that this is looped. I have to give this transmission a unique name, I use the class ID -- Note that this is looped. I have to give this transmission a unique name, I use the class ID
BEACON.UniqueName = BEACON.UniqueName + 1 trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, tostring(self.ID))
self.BeaconName = "MooseBeacon"..tostring(BEACON.UniqueName)
trigger.action.radioTransmission(FileName, self.Positionable:GetPositionVec3(), Modulation, true, Frequency, Power, self.BeaconName)
if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD if BeaconDuration then -- Schedule the stop of the BEACON if asked by the MD
SCHEDULER:New( nil, SCHEDULER:New( nil,
@@ -436,7 +425,6 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
end, {}, BeaconDuration) end, {}, BeaconDuration)
end end
end end
return self
end end
--- Stops the Radio Beacon --- Stops the Radio Beacon
@@ -445,7 +433,7 @@ end
function BEACON:StopRadioBeacon() function BEACON:StopRadioBeacon()
self:F() self:F()
-- The unique name of the transmission is the class ID -- The unique name of the transmission is the class ID
trigger.action.stopRadioTransmission(self.BeaconName) trigger.action.stopRadioTransmission(tostring(self.ID))
return self return self
end end

View File

@@ -1,920 +0,0 @@
--- **Core** - Client Menu Management.
--
-- **Main Features:**
--
-- * For complex, non-static menu structures
-- * Lightweigt implementation as alternative to MENU
-- * Separation of menu tree creation from menu on the clients's side
-- * Works with a SET_CLIENT set of clients
-- * Allow manipulation of the shadow tree in various ways
-- * Push to all or only one client
-- * Change entries' menu text
-- * Option to make an entry usable once only across all clients
-- * Auto appends GROUP and CLIENT objects to menu calls
--
-- ===
--
-- ### Author: **applevangelist**
--
-- ===
--
-- @module Core.ClientMenu
-- @image Core_Menu.JPG
-- last change: May 2024
-- TODO
----------------------------------------------------------------------------------------------------------------
--
-- CLIENTMENU
--
----------------------------------------------------------------------------------------------------------------
---
-- @type CLIENTMENU
-- @field #string ClassName Class Name
-- @field #string lid Lid for log entries
-- @field #string version Version string
-- @field #string name Name
-- @field #string groupname Group name
-- @field #table path
-- @field #table parentpath
-- @field #CLIENTMENU Parent
-- @field Wrapper.Client#CLIENT client
-- @field #number GroupID Group ID
-- @field #number ID Entry ID
-- @field Wrapper.Group#GROUP group
-- @field #string UUID Unique ID based on path+name
-- @field #string Function
-- @field #table Functionargs
-- @field #table Children
-- @field #boolean Once
-- @field #boolean Generic
-- @field #boolean debug
-- @field #CLIENTMENUMANAGER Controller
-- @field #active boolean
-- @extends Core.Base#BASE
---
-- @field #CLIENTMENU
CLIENTMENU = {
ClassName = "CLIENTMENUE",
lid = "",
version = "0.1.2",
name = nil,
path = nil,
group = nil,
client = nil,
GroupID = nil,
Children = {},
Once = false,
Generic = false,
debug = false,
Controller = nil,
groupname = nil,
active = false,
}
---
-- @field #CLIENTMENU_ID
CLIENTMENU_ID = 0
--- Create an new CLIENTMENU object.
-- @param #CLIENTMENU self
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is. Leave as nil for a generic entry.
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
-- @param ... (optional) Arguments for the Function, comma separated
-- @return #CLIENTMENU self
function CLIENTMENU:NewEntry(Client,Text,Parent,Function,...)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENU
CLIENTMENU_ID = CLIENTMENU_ID + 1
self.ID = CLIENTMENU_ID
if Client then
self.group = Client:GetGroup()
self.client = Client
self.GroupID = self.group:GetID()
self.groupname = self.group:GetName() or "Unknown Groupname"
else
self.Generic = true
end
self.name = Text or "unknown entry"
if Parent then
if Parent:IsInstanceOf("MENU_BASE") then
self.parentpath = Parent.MenuPath
else
self.parentpath = Parent:GetPath()
Parent:AddChild(self)
end
end
self.Parent = Parent
self.Function = Function
self.Functionargs = arg or {}
table.insert(self.Functionargs,self.group)
table.insert(self.Functionargs,self.client)
if self.Functionargs and self.debug then
self:T({"Functionargs",self.Functionargs})
end
if not self.Generic and self.active == false then
if Function ~= nil then
local ErrorHandler = function( errmsg )
env.info( "MOOSE Error in CLIENTMENU COMMAND function: " .. errmsg )
if BASE.Debug ~= nil then
env.info( BASE.Debug.traceback() )
end
return errmsg
end
self.CallHandler = function()
local function MenuFunction()
return self.Function( unpack( self.Functionargs ) )
end
local Status, Result = xpcall( MenuFunction, ErrorHandler)
if self.Once == true then
self:Clear()
end
end
self.path = missionCommands.addCommandForGroup(self.GroupID,Text,self.parentpath, self.CallHandler)
self.active = true
else
self.path = missionCommands.addSubMenuForGroup(self.GroupID,Text,self.parentpath)
self.active = true
end
else
if self.parentpath then
self.path = UTILS.DeepCopy(self.parentpath)
else
self.path = {}
end
self.path[#self.path+1] = Text
end
self.UUID = table.concat(self.path,";")
self:T({self.UUID})
self.Once = false
-- Log id.
self.lid=string.format("CLIENTMENU %s | %s | ", self.ID, self.name)
self:T(self.lid.."Created")
return self
end
--- Create a UUID
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Parent The parent object if any
-- @param #string Text The menu entry text
-- @return #string UUID
function CLIENTMENU:CreateUUID(Parent,Text)
local path = {}
if Parent and Parent.path then
path = Parent.path
end
path[#path+1] = Text
local UUID = table.concat(path,";")
return UUID
end
--- Set the CLIENTMENUMANAGER for this entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENUMANAGER Controller The controlling object.
-- @return #CLIENTMENU self
function CLIENTMENU:SetController(Controller)
self.Controller = Controller
return self
end
--- The entry will be deleted after being used used - for menu entries with functions only.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:SetOnce()
self:T(self.lid.."SetOnce")
self.Once = true
return self
end
--- Remove the entry from the F10 menu.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveF10()
self:T(self.lid.."RemoveF10")
if self.GroupID then
--self:I(self.lid.."Removing "..table.concat(self.path,";"))
local function RemoveFunction()
return missionCommands.removeItemForGroup(self.GroupID , self.path )
end
local status, err = pcall(RemoveFunction)
if not status then
self:I(string.format("**** Error Removing Menu Entry %s for %s!",tostring(self.name),self.groupname))
end
self.active = false
end
return self
end
--- Get the menu path table.
-- @param #CLIENTMENU self
-- @return #table Path
function CLIENTMENU:GetPath()
self:T(self.lid.."GetPath")
return self.path
end
--- Get the UUID.
-- @param #CLIENTMENU self
-- @return #string UUID
function CLIENTMENU:GetUUID()
self:T(self.lid.."GetUUID")
return self.UUID
end
--- Link a child entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Child The entry to link as a child.
-- @return #CLIENTMENU self
function CLIENTMENU:AddChild(Child)
self:T(self.lid.."AddChild "..Child.ID)
table.insert(self.Children,Child.ID,Child)
return self
end
--- Remove a child entry.
-- @param #CLIENTMENU self
-- @param #CLIENTMENU Child The entry to remove from the children.
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveChild(Child)
self:T(self.lid.."RemoveChild "..Child.ID)
table.remove(self.Children,Child.ID)
return self
end
--- Remove all subentries (children) from this entry.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:RemoveSubEntries()
self:T(self.lid.."RemoveSubEntries")
self:T({self.Children})
for _id,_entry in pairs(self.Children) do
self:T("Removing ".._id)
if _entry then
_entry:RemoveSubEntries()
_entry:RemoveF10()
if _entry.Parent then
_entry.Parent:RemoveChild(self)
end
--if self.Controller then
--self.Controller:_RemoveByID(_entry.ID)
--end
--_entry = nil
end
end
return self
end
--- Remove this entry and all subentries (children) from this entry.
-- @param #CLIENTMENU self
-- @return #CLIENTMENU self
function CLIENTMENU:Clear()
self:T(self.lid.."Clear")
for _id,_entry in pairs(self.Children) do
if _entry then
_entry:RemoveSubEntries()
_entry = nil
end
end
self:RemoveF10()
if self.Parent then
self.Parent:RemoveChild(self)
end
--if self.Controller then
--self.Controller:_RemoveByID(self.ID)
--end
return self
end
-- TODO
----------------------------------------------------------------------------------------------------------------
--
-- CLIENTMENUMANAGER
--
----------------------------------------------------------------------------------------------------------------
--- Class CLIENTMENUMANAGER
-- @type CLIENTMENUMANAGER
-- @field #string ClassName Class Name
-- @field #string lid Lid for log entries
-- @field #string version Version string
-- @field #string name Name
-- @field Core.Set#SET_CLIENT clientset The set of clients this menu manager is for
-- @field #table flattree
-- @field #table rootentries
-- @field #table menutree
-- @field #number entrycount
-- @field #boolean debug
-- @field #table PlayerMenu
-- @field #number Coalition
-- @extends Core.Base#BASE
--- *As a child my family's menu consisted of two choices: take it, or leave it.*
--
-- ===
--
-- ## CLIENTMENU and CLIENTMENUMANAGER
--
-- Manage menu structures for a SET_CLIENT of clients.
--
-- ## Concept
--
-- Separate creation of a menu tree structure from pushing it to each client. Create a shadow "reference" menu structure tree for your client pilot's in a mission.
-- This can then be propagated to all clients. Manipulate the entries in the structure with removing, clearing or changing single entries, create replacement sub-structures
-- for entries etc, push to one or all clients.
--
-- Many functions can either change the tree for one client or for all clients.
--
-- ## Conceptual remarks
--
-- There's a couple of things to fully understand:
--
-- 1) **CLIENTMENUMANAGER** manages a set of entries from **CLIENTMENU**, it's main purpose is to administer the *shadow menu tree*, ie. a menu structure which is not
-- (yet) visible to any client
-- 2) The entries are **CLIENTMENU** objects, which are linked in a tree form. There's two ways to create them:
-- A) in the manager with ":NewEntry()" which initially
-- adds it to the shadow menu **only**
-- B) stand-alone directly as `CLIENTMENU:NewEntry()` - here it depends on whether or not you gave a CLIENT object if the entry is created as generic entry or pushed
-- a **specific** client. **Be aware** though that the entries are not managed by the CLIENTMANAGER before the next step!
-- A generic entry can be added to the manager (and the shadow tree) with `:AddEntry()` - this will also push it to all clients(!) if no client is given, or a specific client only.
-- 3) Pushing only works for alive clients.
-- 4) Live and shadow tree entries are managed via the CLIENTMENUMANAGER object.
-- 5) `Propagate()`refreshes the menu tree for all, or a single client.
--
-- ## Create a base reference tree and send to all clients
--
-- local clientset = SET_CLIENT:New():FilterStart()
--
-- local menumgr = CLIENTMENUMANAGER:New(clientset,"Dayshift")
-- local mymenu = menumgr:NewEntry("Top")
-- local mymenu_lv1a = menumgr:NewEntry("Level 1 a",mymenu)
-- local mymenu_lv1b = menumgr:NewEntry("Level 1 b",mymenu)
-- -- next one is a command menu entry, which can only be used once
-- local mymenu_lv1c = menumgr:NewEntry("Action Level 1 c",mymenu, testfunction, "testtext"):SetOnce()
--
-- local mymenu_lv2a = menumgr:NewEntry("Go here",mymenu_lv1a)
-- local mymenu_lv2b = menumgr:NewEntry("Level 2 ab",mymenu_lv1a)
-- local mymenu_lv2c = menumgr:NewEntry("Level 2 ac",mymenu_lv1a)
--
-- local mymenu_lv2ba = menumgr:NewEntry("Level 2 ba",mymenu_lv1b)
-- local mymenu_lv2bb = menumgr:NewEntry("Level 2 bb",mymenu_lv1b)
-- local mymenu_lv2bc = menumgr:NewEntry("Level 2 bc",mymenu_lv1b)
--
-- local mymenu_lv3a = menumgr:NewEntry("Level 3 aaa",mymenu_lv2a)
-- local mymenu_lv3b = menumgr:NewEntry("Level 3 aab",mymenu_lv2a)
-- local mymenu_lv3c = menumgr:NewEntry("Level 3 aac",mymenu_lv2a)
--
-- menumgr:Propagate() -- propagate **once** to all clients in the SET_CLIENT
--
-- ## Remove a single entry's subtree
--
-- menumgr:RemoveSubEntries(mymenu_lv3a)
--
-- ## Remove a single entry and also it's subtree
--
-- menumgr:DeleteEntry(mymenu_lv3a)
--
-- ## Add a single entry
--
-- local baimenu = menumgr:NewEntry("BAI",mymenu_lv1b)
--
-- menumgr:AddEntry(baimenu)
--
-- ## Add an entry with a function
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, Argument1, Argument1)
--
-- Now, the class will **automatically append the call with GROUP and CLIENT objects**, as this is can only be done when pushing the entry to the clients. So, the actual function implementation needs to look like this:
--
-- function TestFunction( Argument1, Argument2, Group, Client)
--
-- **Caveat is**, that you need to ensure your arguments are not **nil** or **false**, as LUA will optimize those away. You would end up having Group and Client in wrong places in the function call. Hence,
-- if you need/ want to send **nil** or **false**, send a place holder instead and ensure your function can handle this, e.g.
--
-- local baimenu = menumgr:NewEntry("Task Action", mymenu_lv1b, TestFunction, "nil", Argument1)
--
-- ## Change the text of a leaf entry in the menu tree
--
-- menumgr:ChangeEntryTextForAll(mymenu_lv1b,"Attack")
--
-- ## Reset a single clients menu tree
--
-- menumgr:ResetMenu(client)
--
-- ## Reset all and clear the reference tree
--
-- menumgr:ResetMenuComplete()
--
-- ## Set to auto-propagate for CLIENTs joining the SET_CLIENT **after** the script is loaded - handy if you have a single menu tree.
--
-- menumgr:InitAutoPropagation()
--
-- @field #CLIENTMENUMANAGER
CLIENTMENUMANAGER = {
ClassName = "CLIENTMENUMANAGER",
lid = "",
version = "0.1.5a",
name = nil,
clientset = nil,
menutree = {},
flattree = {},
playertree = {},
entrycount = 0,
rootentries = {},
debug = true,
PlayerMenu = {},
Coalition = nil,
}
--- Create a new ClientManager instance.
-- @param #CLIENTMENUMANAGER self
-- @param Core.Set#SET_CLIENT ClientSet The set of clients to manage.
-- @param #string Alias The name of this manager.
-- @param #number Coalition (Optional) Coalition of this Manager, defaults to coalition.side.BLUE
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:New(ClientSet, Alias, Coalition)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, BASE:New()) -- #CLIENTMENUMANAGER
self.clientset = ClientSet
self.PlayerMenu = {}
self.name = Alias or "Nightshift"
self.Coalition = Coalition or coalition.side.BLUE
-- Log id.
self.lid=string.format("CLIENTMENUMANAGER %s | %s | ", self.version, self.name)
if self.debug then
self:I(self.lid.."Created")
end
return self
end
--- [Internal] Event handling
-- @param #CLIENTMENUMANAGER self
-- @param Core.Event#EVENTDATA EventData
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:_EventHandler(EventData)
self:T(self.lid.."_EventHandler: "..EventData.id)
--self:I(self.lid.."_EventHandler: "..tostring(EventData.IniPlayerName))
if EventData.id == EVENTS.PlayerLeaveUnit or EventData.id == EVENTS.Ejection or EventData.id == EVENTS.Crash or EventData.id == EVENTS.PilotDead then
self:T(self.lid.."Leave event for player: "..tostring(EventData.IniPlayerName))
local Client = _DATABASE:FindClient( EventData.IniUnitName )
if Client then
self:ResetMenu(Client)
end
elseif (EventData.id == EVENTS.PlayerEnterAircraft) and EventData.IniCoalition == self.Coalition then
if EventData.IniPlayerName and EventData.IniGroup then
if (not self.clientset:IsIncludeObject(_DATABASE:FindClient( EventData.IniUnitName ))) then
self:T(self.lid.."Client not in SET: "..EventData.IniPlayerName)
return self
end
--self:I(self.lid.."Join event for player: "..EventData.IniPlayerName)
local player = _DATABASE:FindClient( EventData.IniUnitName )
self:Propagate(player)
end
elseif EventData.id == EVENTS.PlayerEnterUnit then
-- special for CA slots
local grp = GROUP:FindByName(EventData.IniGroupName)
if grp:IsGround() then
self:T(string.format("Player %s entered GROUND unit %s!",EventData.IniPlayerName,EventData.IniUnitName))
local IsPlayer = EventData.IniDCSUnit:getPlayerName()
if IsPlayer then
local client=_DATABASE.CLIENTS[EventData.IniDCSUnitName] --Wrapper.Client#CLIENT
-- Add client in case it does not exist already.
if not client then
-- Debug info.
self:I(string.format("Player '%s' joined ground unit '%s' of group '%s'", tostring(EventData.IniPlayerName), tostring(EventData.IniDCSUnitName), tostring(EventData.IniDCSGroupName)))
client=_DATABASE:AddClient(EventData.IniDCSUnitName)
-- Add player.
client:AddPlayer(EventData.IniPlayerName)
-- Add player.
if not _DATABASE.PLAYERS[EventData.IniPlayerName] then
_DATABASE:AddPlayer( EventData.IniUnitName, EventData.IniPlayerName )
end
-- Player settings.
local Settings = SETTINGS:Set( EventData.IniPlayerName )
Settings:SetPlayerMenu(EventData.IniUnit)
end
--local player = _DATABASE:FindClient( EventData.IniPlayerName )
self:Propagate(client)
end
end
end
return self
end
--- Set this Client Manager to auto-propagate menus **once** to newly joined players. Useful if you have **one** menu structure only. Does not automatically push follow-up changes to the client(s).
-- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:InitAutoPropagation()
-- Player Events
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler)
self:HandleEvent(EVENTS.Ejection, self._EventHandler)
self:HandleEvent(EVENTS.Crash, self._EventHandler)
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
self:SetEventPriority(5)
return self
end
--- Create a new entry in the **generic** structure.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
-- @param ... (optional) Arguments for the Function, comma separated.
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:NewEntry(Text,Parent,Function,...)
self:T(self.lid.."NewEntry "..Text or "None")
self.entrycount = self.entrycount + 1
local entry = CLIENTMENU:NewEntry(nil,Text,Parent,Function,unpack(arg))
if not Parent then
self.rootentries[self.entrycount] = entry
end
local depth = #entry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],entry.UUID)
self.flattree[entry.UUID] = entry
return entry
end
--- Check matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string UUID UUID of the menu entry.
-- @return #boolean Exists
function CLIENTMENUMANAGER:EntryUUIDExists(UUID)
local exists = self.flattree[UUID] and true or false
return exists
end
--- Find matching entry in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string UUID UUID of the menu entry.
-- @return #CLIENTMENU Entry The #CLIENTMENU object found or nil.
function CLIENTMENUMANAGER:FindEntryByUUID(UUID)
self:T(self.lid.."FindEntryByUUID "..UUID or "None")
local entry = nil
for _gid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Entry and Entry.UUID == UUID then
entry = Entry
end
end
return entry
end
--- Find matching entries by text in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text or partial text of the menu entry to find.
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
-- @return #table Table of matching #CLIENTMENU objects
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindUUIDsByText(Text,Parent)
self:T(self.lid.."FindUUIDsByText "..Text or "None")
local matches = {}
local entries = {}
local n = 0
for _uuid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Parent then
if Entry and string.find(Entry.name,Text,1,true) and string.find(Entry.UUID,Parent.UUID,1,true) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
else
if Entry and string.find(Entry.name,Text,1,true) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
end
end
return matches, entries, n
end
--- Find matching entries in the generic structure by the menu text.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text or partial text of the F10 menu entry.
-- @param #CLIENTMENU Parent (Optional) Only find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByText(Text,Parent)
self:T(self.lid.."FindEntriesByText "..Text or "None")
local matches, objects, number = self:FindUUIDsByText(Text, Parent)
return objects, number
end
--- Find matching entries under a parent in the generic structure by UUID.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching UUIDs of #CLIENTMENU objects
-- @return #table Table of matching #CLIENTMENU objects
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindUUIDsByParent(Parent)
self:T(self.lid.."FindUUIDsByParent")
local matches = {}
local entries = {}
local n = 0
for _uuid,_entry in pairs(self.flattree) do
local Entry = _entry -- #CLIENTMENU
if Parent then
if Entry and string.find(Entry.UUID,Parent.UUID,1,true) then
table.insert(matches,_uuid)
table.insert(entries,Entry )
n=n+1
end
end
end
return matches, entries, n
end
--- Find matching entries in the generic structure under a parent.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Parent Find entries under this parent entry.
-- @return #table Table of matching #CLIENTMENU objects.
-- @return #number Number of matches
function CLIENTMENUMANAGER:FindEntriesByParent(Parent)
self:T(self.lid.."FindEntriesByParent")
local matches, objects, number = self:FindUUIDsByParent(Parent)
return objects, number
end
--- Alter the text of a leaf entry in the generic structure and push to one specific client's F10 menu.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The menu entry.
-- @param #string Text New Text of the F10 menu entry.
-- @param Wrapper.Client#CLIENT Client (optional) The client for whom to alter the entry, if nil done for all clients.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ChangeEntryText(Entry, Text, Client)
self:T(self.lid.."ChangeEntryText "..Text or "None")
local newentry = CLIENTMENU:NewEntry(nil,Text,Entry.Parent,Entry.Function,unpack(Entry.Functionargs))
self:DeleteF10Entry(Entry,Client)
self:DeleteGenericEntry(Entry)
if not Entry.Parent then
self.rootentries[self.entrycount] = newentry
end
local depth = #newentry.path
if not self.menutree[depth] then self.menutree[depth] = {} end
table.insert(self.menutree[depth],newentry.UUID)
self.flattree[newentry.UUID] = newentry
self:AddEntry(newentry,Client)
return self
end
--- Push the complete menu structure to each of the clients in the set - refresh the menu tree of the clients.
-- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client (optional) If given, propagate only for this client.
-- @return #CLIENTMENU Entry
function CLIENTMENUMANAGER:Propagate(Client)
self:T(self.lid.."Propagate")
--self:I(UTILS.PrintTableToLog(Client,1))
local knownunits = {} -- track so we can ID multi seated
local Set = self.clientset.Set
if Client then
Set = {Client}
end
self:ResetMenu(Client)
for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
local playerunit = client:GetName()
--local playergroup = client:GetGroup()
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
self.playertree[playername] = {}
end
for level,branch in pairs (self.menutree) do
self:T("Building branch:" .. level)
for _,leaf in pairs(branch) do
self:T("Building leaf:" .. leaf)
local entry = self:FindEntryByUUID(leaf)
if entry then
self:T("Found generic entry:" .. entry.UUID)
local parent = nil
if entry.Parent and entry.Parent.UUID then
parent = self.playertree[playername][entry.Parent.UUID] or self:FindEntryByUUID(entry.Parent.UUID)
end
self.playertree[playername][entry.UUID] = CLIENTMENU:NewEntry(client,entry.name,parent,entry.Function,unpack(entry.Functionargs))
self.playertree[playername][entry.UUID].Once = entry.Once
else
self:T("NO generic entry for:" .. leaf)
end
end
end
end
end
return self
end
--- Push a single previously created entry into the F10 menu structure of all clients.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to add.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:AddEntry(Entry,Client)
self:T(self.lid.."AddEntry")
local Set = self.clientset.Set
local knownunits = {}
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
local client = _client -- Wrapper.Client#CLIENT
if client and client:IsAlive() then
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
self:T("Adding generic entry:" .. Entry.UUID)
local parent = nil
if not self.playertree[playername] then
self.playertree[playername] = {}
end
if Entry.Parent and Entry.Parent.UUID then
parent = self.playertree[playername][Entry.Parent.UUID] or self:FindEntryByUUID(Entry.Parent.UUID)
end
self.playertree[playername][Entry.UUID] = CLIENTMENU:NewEntry(client,Entry.name,parent,Entry.Function,unpack(Entry.Functionargs))
self.playertree[playername][Entry.UUID].Once = Entry.Once
else
self:T("NO generic entry given")
end
end
end
return self
end
--- Blank out the menu - remove **all root entries** and all entries below from the client's F10 menus, leaving the generic structure untouched.
-- @param #CLIENTMENUMANAGER self
-- @param Wrapper.Client#CLIENT Client (optional) If given, remove only for this client.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenu(Client)
self:T(self.lid.."ResetMenu")
for _,_entry in pairs(self.rootentries) do
--local RootEntry = self.structure.generic[_entry]
if _entry then
self:DeleteF10Entry(_entry,Client)
end
end
return self
end
--- Blank out the menu - remove **all root entries** and all entries below from all clients' F10 menus, and **delete** the generic structure.
-- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:ResetMenuComplete()
self:T(self.lid.."ResetMenuComplete")
for _,_entry in pairs(self.rootentries) do
--local RootEntry = self.structure.generic[_entry]
if _entry then
self:DeleteF10Entry(_entry)
end
end
self.playertree = nil
self.playertree = {}
self.rootentries = nil
self.rootentries = {}
self.menutree = nil
self.menutree = {}
return self
end
--- Remove the entry and all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:DeleteF10Entry(Entry,Client)
self:T(self.lid.."DeleteF10Entry")
local Set = self.clientset.Set
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
if _client and _client:IsAlive() then
local playername = _client:GetPlayerName()
if self.playertree[playername] then
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
if centry then
--self:I("Match for "..Entry.UUID)
centry:Clear()
end
end
end
end
return self
end
--- Remove the entry and all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to remove
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:DeleteGenericEntry(Entry)
self:T(self.lid.."DeleteGenericEntry")
if Entry.Children and #Entry.Children > 0 then
self:RemoveGenericSubEntries(Entry)
end
local depth = #Entry.path
local uuid = Entry.UUID
local tbl = UTILS.DeepCopy(self.menutree)
if tbl[depth] then
for i=depth,#tbl do
--self:I("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid,1,true) or _uuid == uuid then
--self:I("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end
end
return self
end
--- Remove all entries below the given entry from the generic tree.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveGenericSubEntries(Entry)
self:T(self.lid.."RemoveGenericSubEntries")
local depth = #Entry.path + 1
local uuid = Entry.UUID
local tbl = UTILS.DeepCopy(self.menutree)
if tbl[depth] then
for i=depth,#tbl do
self:T("Level = "..i)
for _id,_uuid in pairs(tbl[i]) do
self:T(_uuid)
if string.find(_uuid,uuid,1,true) then
self:T("Match for ".._uuid)
self.menutree[i][_id] = nil
self.flattree[_uuid] = nil
end
end
end
end
return self
end
--- Remove all entries below the given entry from the client's F10 menus.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry where to start. This entry stays.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client. In this case the generic structure will not be touched.
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:RemoveF10SubEntries(Entry,Client)
self:T(self.lid.."RemoveSubEntries")
local Set = self.clientset.Set
if Client then
Set = {Client}
end
for _,_client in pairs(Set) do
if _client and _client:IsAlive() then
local playername = _client:GetPlayerName()
if self.playertree[playername] then
local centry = self.playertree[playername][Entry.UUID] -- #CLIENTMENU
centry:RemoveSubEntries()
end
end
end
return self
end
----------------------------------------------------------------------------------------------------------------
--
-- End ClientMenu
--
----------------------------------------------------------------------------------------------------------------

View File

@@ -37,8 +37,6 @@
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
-- @field #table CLIENTS Clients. -- @field #table CLIENTS Clients.
-- @field #table STORAGES DCS warehouse storages. -- @field #table STORAGES DCS warehouse storages.
-- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes.
-- @field #table SADL Used Link16 octal numbers for A10/C-II planes.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
@@ -95,8 +93,6 @@ DATABASE = {
OPSZONES = {}, OPSZONES = {},
PATHLINES = {}, PATHLINES = {},
STORAGES = {}, STORAGES = {},
STNS={},
SADL={},
} }
local _DATABASECoalition = local _DATABASECoalition =
@@ -932,7 +928,7 @@ function DATABASE:Spawn( SpawnTemplate )
SpawnTemplate.CountryID = nil SpawnTemplate.CountryID = nil
SpawnTemplate.CategoryID = nil SpawnTemplate.CategoryID = nil
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name ) self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID )
self:T3( SpawnTemplate ) self:T3( SpawnTemplate )
coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
@@ -1034,27 +1030,6 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end end
if UnitTemplate.AddPropAircraft then
if UnitTemplate.AddPropAircraft.STN_L16 then
local stn = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16)
if stn == nil or stn < 1 then
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
else
self.STNS[stn] = UnitTemplate.name
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
end
end
if UnitTemplate.AddPropAircraft.SADL_TN then
local sadl = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN)
if sadl == nil or sadl < 1 then
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
else
self.SADL[sadl] = UnitTemplate.name
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
end
end
end
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
end end
@@ -1068,92 +1043,15 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
) )
end end
--- Get next (consecutive) free STN as octal number.
-- @param #DATABASE self
-- @param #number octal Starting octal.
-- @param #string unitname Name of the associated unit.
-- @return #number Octal
function DATABASE:GetNextSTN(octal,unitname)
local first = UTILS.OctalToDecimal(octal) or 0
if self.STNS[first] == unitname then return octal end
local nextoctal = 77777
local found = false
if 32767-first < 10 then
first = 0
end
for i=first+1,32767 do
if self.STNS[i] == nil then
found = true
nextoctal = UTILS.DecimalToOctal(i)
self.STNS[i] = unitname
self:T("Register STN "..tostring(nextoctal).." for ".. unitname)
break
end
end
if not found then
self:E(string.format("WARNING: No next free STN past %05d found!",octal))
-- cleanup
local NewSTNS = {}
for _id,_name in pairs(self.STNS) do
if self.UNITS[_name] ~= nil then
NewSTNS[_id] = _name
end
end
self.STNS = nil
self.STNS = NewSTNS
end
return nextoctal
end
--- Get next (consecutive) free SADL as octal number.
-- @param #DATABASE self
-- @param #number octal Starting octal.
-- @param #string unitname Name of the associated unit.
-- @return #number Octal
function DATABASE:GetNextSADL(octal,unitname)
local first = UTILS.OctalToDecimal(octal) or 0
if self.SADL[first] == unitname then return octal end
local nextoctal = 7777
local found = false
if 4095-first < 10 then
first = 0
end
for i=first+1,4095 do
if self.STNS[i] == nil then
found = true
nextoctal = UTILS.DecimalToOctal(i)
self.SADL[i] = unitname
self:T("Register SADL "..tostring(nextoctal).." for ".. unitname)
break
end
end
if not found then
self:E(string.format("WARNING: No next free SADL past %04d found!",octal))
-- cleanup
local NewSTNS = {}
for _id,_name in pairs(self.SADL) do
if self.UNITS[_name] ~= nil then
NewSTNS[_id] = _name
end
end
self.SADL = nil
self.SADL = NewSTNS
end
return nextoctal
end
--- Get group template. --- Get group template.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string GroupName Group name. -- @param #string GroupName Group name.
-- @return #table Group template table. -- @return #table Group template table.
function DATABASE:GetGroupTemplate( GroupName ) function DATABASE:GetGroupTemplate( GroupName )
local GroupTemplate=nil local GroupTemplate = self.Templates.Groups[GroupName].Template
if self.Templates.Groups[GroupName] then
GroupTemplate = self.Templates.Groups[GroupName].Template
GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID
GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID
GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID
end
return GroupTemplate return GroupTemplate
end end
@@ -1688,7 +1586,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.IniPlayerName or Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName) local PlayerName = Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName)
if PlayerName then if PlayerName then

View File

@@ -1301,7 +1301,7 @@ function EVENT:onEvent( Event )
-- STATIC -- STATIC
--- ---
Event.TgtDCSUnit = Event.target Event.TgtDCSUnit = Event.target
if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K) if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
-- Workaround for borked target info on cruise missiles -- Workaround for borked target info on cruise missiles
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then
@@ -1378,7 +1378,6 @@ function EVENT:onEvent( Event )
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition Event.MarkCoalition=Event.coalition
Event.IniCoalition=Event.coalition
Event.MarkGroupID = Event.groupID Event.MarkGroupID = Event.groupID
end end

View File

@@ -17,7 +17,7 @@
-- ### Author: **Applevangelist** -- ### Author: **Applevangelist**
-- --
-- Date: 5 May 2021 -- Date: 5 May 2021
-- Last Update: Mar 2023 -- Last Update: Feb 2023
-- --
-- === -- ===
--- ---
@@ -50,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS", ClassName = "MARKEROPS",
Tag = "mytag", Tag = "mytag",
Keywords = {}, Keywords = {},
version = "0.1.3", version = "0.1.1",
debug = false, debug = false,
Casesensitive = true, Casesensitive = true,
} }
@@ -114,8 +114,6 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- @param #string Text The text on the marker -- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number MarkerID Id of this marker
-- @param #number CoalitionNumber Coalition of the marker creator
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map. --- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged -- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
@@ -126,8 +124,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- @param #string Text The text on the marker -- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number MarkerID Id of this marker -- @param #number idx DCS Marker ID
-- @param #number CoalitionNumber Coalition of the marker creator
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
@@ -158,30 +155,29 @@ function MARKEROPS_BASE:OnEventMark(Event)
local text = tostring(Event.text) local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end end
local coalition = Event.MarkCoalition
-- decision -- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED", carrier=Event.IniGroupName, vec3=Event.pos}) self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
-- Handle event -- Handle event
local Eventtext = tostring(Event.text) local Eventtext = tostring(Event.text)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition) self:MarkAdded(Eventtext,matchtable,coord)
end end
end end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE", carrier=Event.IniGroupName, vec3=Event.pos}) self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
-- Handle event. -- Handle event.
local Eventtext = tostring(Event.text) local Eventtext = tostring(Event.text)
if Eventtext~=nil then if Eventtext~=nil then
if self:_MatchTag(Eventtext) then if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext) local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition) self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
end end
end end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED", carrier=Event.IniGroupName, vec3=Event.pos}) self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
-- Hande event. -- Hande event.
local Eventtext = tostring(Event.text) local Eventtext = tostring(Event.text)
if Eventtext~=nil then if Eventtext~=nil then
@@ -234,10 +230,8 @@ end
-- @param #string To The To state -- @param #string To The To state
-- @param #string Text The text on the marker -- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text
-- @param #number MarkerID Id of this marker
-- @param #number CoalitionNumber Coalition of the marker creator
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end end
@@ -248,10 +242,8 @@ end
-- @param #string To The To state -- @param #string To The To state
-- @param #string Text The text on the marker -- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text -- @param #table Keywords Table of matching keywords found in the Event text
-- @param #number MarkerID Id of this marker
-- @param #number CoalitionNumber Coalition of the marker creator
-- @param Core.Point#COORDINATE Coord Coordinate of the marker. -- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end end

View File

@@ -2,7 +2,7 @@
-- --
-- === -- ===
-- --
-- ## Features: -- ### Features:
-- --
-- * Setup mission sub menus. -- * Setup mission sub menus.
-- * Setup mission command menus. -- * Setup mission command menus.
@@ -44,11 +44,7 @@
-- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs. -- * @{Core.Menu#MENU_GROUP_COMMAND}: Manages command menus for GROUPs.
-- --
-- === -- ===
-- ---
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Menu)
--
-- ===
--
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: -- ### Contributions:
-- --
@@ -170,8 +166,7 @@ function MENU_INDEX:Refresh( Group )
end end
do -- MENU_BASE do -- MENU_BASE
--- --- @type MENU_BASE
-- @type MENU_BASE
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Defines the main MENU class where other MENU classes are derived from. --- Defines the main MENU class where other MENU classes are derived from.
-- This is an abstract class, so don't use it. -- This is an abstract class, so don't use it.
@@ -211,7 +206,6 @@ do -- MENU_BASE
return self return self
end end
function MENU_BASE:SetParentMenu( MenuText, Menu ) function MENU_BASE:SetParentMenu( MenuText, Menu )
if self.ParentMenu then if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {} self.ParentMenu.Menus = self.ParentMenu.Menus or {}
@@ -283,10 +277,8 @@ do -- MENU_BASE
end end
end end
do do -- MENU_COMMAND_BASE
--- --- @type MENU_COMMAND_BASE
-- MENU_COMMAND_BASE
-- @type MENU_COMMAND_BASE
-- @field #function MenuCallHandler -- @field #function MenuCallHandler
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -351,10 +343,8 @@ do
end end
end end
do do -- MENU_MISSION
--- --- @type MENU_MISSION
-- MENU_MISSION
-- @type MENU_MISSION
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
--- Manages the main menus for a complete mission. --- Manages the main menus for a complete mission.
-- --
@@ -519,9 +509,8 @@ do -- MENU_MISSION_COMMAND
return self return self
end end
end end
do do -- MENU_COALITION
--- MENU_COALITION --- @type MENU_COALITION
-- @type MENU_COALITION
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
--- Manages the main menus for DCS.coalition. --- Manages the main menus for DCS.coalition.
@@ -646,10 +635,9 @@ do
return self return self
end end
end end
do do -- MENU_COALITION_COMMAND
--- MENU_COALITION_COMMAND --- @type MENU_COALITION_COMMAND
-- @type MENU_COALITION_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE -- @extends Core.Menu#MENU_COMMAND_BASE
--- Manages the command menus for coalitions, which allow players to execute functions during mission execution. --- Manages the command menus for coalitions, which allow players to execute functions during mission execution.
@@ -737,11 +725,8 @@ do
-- So every menu for a client created must be tracked so that program logic accidentally does not create. -- So every menu for a client created must be tracked so that program logic accidentally does not create.
-- the same menus twice during initialization logic. -- the same menus twice during initialization logic.
-- These menu classes are handling this logic with this variable. -- These menu classes are handling this logic with this variable.
local _MENUGROUPS = {} local _MENUGROUPS = {}
--- @type MENU_GROUP
---
-- @type MENU_GROUP
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -772,7 +757,7 @@ do
-- MenuStatus[MenuGroupName]:Remove() -- MenuStatus[MenuGroupName]:Remove()
-- end -- end
-- --
-- -- @param Wrapper.Group#GROUP MenuGroup -- --- @param Wrapper.Group#GROUP MenuGroup
-- local function AddStatusMenu( MenuGroup ) -- local function AddStatusMenu( MenuGroup )
-- local MenuGroupName = MenuGroup:GetName() -- local MenuGroupName = MenuGroup:GetName()
-- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object. -- -- This would create a menu for the red coalition under the MenuCoalitionRed menu object.
@@ -914,8 +899,8 @@ do
return self return self
end end
---
-- @type MENU_GROUP_COMMAND --- @type MENU_GROUP_COMMAND
-- @extends Core.Menu#MENU_COMMAND_BASE -- @extends Core.Menu#MENU_COMMAND_BASE
--- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution. --- The @{Core.Menu#MENU_GROUP_COMMAND} class manages the command menus for coalitions, which allow players to execute functions during mission execution.
@@ -998,8 +983,7 @@ do
end end
--- MENU_GROUP_DELAYED --- MENU_GROUP_DELAYED
do do
--- --- @type MENU_GROUP_DELAYED
-- @type MENU_GROUP_DELAYED
-- @extends Core.Menu#MENU_BASE -- @extends Core.Menu#MENU_BASE
@@ -1050,7 +1034,6 @@ do
-- @param #MENU_GROUP_DELAYED self -- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED -- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Set() function MENU_GROUP_DELAYED:Set()
if not self.GroupID then return end
do do
if not self.MenuSet then if not self.MenuSet then
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath ) missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -1123,8 +1106,8 @@ do
return self return self
end end
---
-- @type MENU_GROUP_COMMAND_DELAYED --- @type MENU_GROUP_COMMAND_DELAYED
-- @extends Core.Menu#MENU_COMMAND_BASE -- @extends Core.Menu#MENU_COMMAND_BASE
--- Manages the command menus for coalitions, which allow players to execute functions during mission execution. --- Manages the command menus for coalitions, which allow players to execute functions during mission execution.

View File

@@ -395,8 +395,7 @@ 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.
-- @param #number Delay (Optional) Delay in seconds before the message is send. Default instantly (`nil`). -- @return #MESSAGE
-- @return #MESSAGE self
-- @usage -- @usage
-- --
-- -- Send a message created to all players. -- -- Send a message created to all players.
@@ -407,13 +406,9 @@ 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, Delay ) function MESSAGE:ToAll( Settings )
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 )
@@ -425,8 +420,6 @@ function MESSAGE:ToAll( Settings, Delay )
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
@@ -516,10 +509,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
end end
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE" _MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label) _MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE")
_MESSAGESRS.port = Port or MSRS.port or 5002 _MESSAGESRS.port = Port or MSRS.port or 5002
_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port) _MESSAGESRS.MSRS:SetPort(Port or 5002)
_MESSAGESRS.volume = Volume or MSRS.volume or 1 _MESSAGESRS.volume = Volume or MSRS.volume or 1
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) _MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)

View File

@@ -73,7 +73,7 @@ PATHLINE = {
--- PATHLINE class version. --- PATHLINE class version.
-- @field #string version -- @field #string version
PATHLINE.version="0.1.1" PATHLINE.version="0.1.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@@ -237,14 +237,13 @@ end
--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often. --- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often.
-- @param #PATHLINE self -- @param #PATHLINE self
-- @return <Core.Point#COORDINATE> List of COORDINATES points. -- @return <Core.Point#COORDINATE> List of COORDINATES points.
function PATHLINE:GetCoordinates() function PATHLINE:GetCoordinats()
local vecs={} local vecs={}
for _,_point in pairs(self.points) do for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point local point=_point --#PATHLINE.Point
local coord=COORDINATE:NewFromVec3(point.vec3) local coord=COORDINATE:NewFromVec3(point.vec3)
table.insert(vecs,coord)
end end
return vecs return vecs
@@ -263,7 +262,7 @@ function PATHLINE:GetPointFromIndex(n)
local point=nil --#PATHLINE.Point local point=nil --#PATHLINE.Point
if n>=1 and n<=N then if n>=1 and n<=N then
point=self.points[n] point=self.point[n]
else else
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n))) self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n)))
end end

View File

@@ -703,7 +703,6 @@ do -- COORDINATE
-- @return DCS#Distance The distance from the reference @{#COORDINATE} in meters. -- @return DCS#Distance The distance from the reference @{#COORDINATE} in meters.
function COORDINATE:DistanceFromPointVec2( PointVec2Reference ) function COORDINATE:DistanceFromPointVec2( PointVec2Reference )
self:F2( PointVec2Reference ) self:F2( PointVec2Reference )
if not PointVec2Reference then return math.huge end
local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5 local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5
@@ -3148,18 +3147,17 @@ do -- COORDINATE
-- @param #string Northing Meters northing - string in order to allow for leading zeros, e.g. "12340". Should be 5 digits. -- @param #string Northing Meters northing - string in order to allow for leading zeros, e.g. "12340". Should be 5 digits.
-- @return #COORDINATE self -- @return #COORDINATE self
function COORDINATE:NewFromMGRS( UTMZone, MGRSDigraph, Easting, Northing ) function COORDINATE:NewFromMGRS( UTMZone, MGRSDigraph, Easting, Northing )
if string.len(Easting) < 5 then Easting = tostring(Easting..string.rep("0",5-string.len(Easting) )) end if string.len(Easting) < 5 then Easting = Easting..string.rep("0",5-string.len(Easting) )end
if string.len(Northing) < 5 then Northing = tostring(Northing..string.rep("0",5-string.len(Northing) )) end if string.len(Northing) < 5 then Northing = Northing..string.rep("0",5-string.len(Northing) )end
local MGRS = { local MGRS = {
UTMZone = UTMZone, UTMZone = UTMZone,
MGRSDigraph = MGRSDigraph, MGRSDigraph = MGRSDigraph,
Easting = tostring(Easting), Easting = Easting,
Northing = tostring(Northing), Northing = Northing,
} }
local lat, lon = coord.MGRStoLL(MGRS) local lat, lon = coord.MGRStoLL(MGRS)
local point = coord.LLtoLO(lat, lon, 0) local point = coord.LLtoLO(lat, lon, 0)
local coord = COORDINATE:NewFromVec2({x=point.x,y=point.z}) local coord = COORDINATE:NewFromVec2({x=point.x,y=point.z})
return coord
end end
--- Provides a coordinate string of the point, based on a coordinate format system: --- Provides a coordinate string of the point, based on a coordinate format system:

View File

@@ -1097,7 +1097,6 @@ do
GroupPrefixes = nil, GroupPrefixes = nil,
Zones = nil, Zones = nil,
Functions = nil, Functions = nil,
Alive = nil,
}, },
FilterMeta = { FilterMeta = {
Coalitions = { Coalitions = {
@@ -1205,7 +1204,7 @@ do
if not DontSetCargoBayLimit then if not DontSetCargoBayLimit then
-- I set the default cargo bay weight limit each time a new group is added to the set. -- I set the default cargo bay weight limit each time a new group is added to the set.
-- TODO Why is this here in the first place? -- TODO Why is this here in the first place?
for UnitID, UnitData in pairs( group:GetUnits() or {} ) do for UnitID, UnitData in pairs( group:GetUnits() ) do
if UnitData and UnitData:IsAlive() then if UnitData and UnitData:IsAlive() then
UnitData:SetCargoBayWeightLimit() UnitData:SetCargoBayWeightLimit()
end end
@@ -1471,7 +1470,7 @@ do
end end
--- Builds a set of groups that are active, ie in the mission but not yet activated (false) or actived (true). --- Builds a set of groups that are only active.
-- Only the groups that are active will be included within the set. -- Only the groups that are active will be included within the set.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param #boolean Active (Optional) Include only active groups to the set. -- @param #boolean Active (Optional) Include only active groups to the set.
@@ -1497,14 +1496,6 @@ do
return self return self
end end
--- Build a set of groups that are alive.
-- @param #SET_GROUP self
-- @return #SET_GROUP self
function SET_GROUP:FilterAlive()
self.Filter.Alive = true
return self
end
--- Starts the filtering. --- Starts the filtering.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @return #SET_GROUP self -- @return #SET_GROUP self
@@ -1516,7 +1507,6 @@ do
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash )
if self.Filter.Zones then if self.Filter.Zones then
self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self)
local timing = self.ZoneTimerInterval or 30 local timing = self.ZoneTimerInterval or 30
@@ -2004,15 +1994,6 @@ do
self:F2( MGroup ) self:F2( MGroup )
local MGroupInclude = true local MGroupInclude = true
if self.Filter.Alive == true then
local MGroupAlive = false
self:F( { Active = self.Filter.Active } )
if MGroup and MGroup:IsAlive() then
MGroupAlive = true
end
MGroupInclude = MGroupInclude and MGroupAlive
end
if self.Filter.Active ~= nil then if self.Filter.Active ~= nil then
local MGroupActive = false local MGroupActive = false
self:F( { Active = self.Filter.Active } ) self:F( { Active = self.Filter.Active } )
@@ -3016,7 +2997,7 @@ do -- SET_UNIT
local velocity = self:GetVelocity() or 0 local velocity = self:GetVelocity() or 0
Coordinate:SetHeading( heading ) Coordinate:SetHeading( heading )
Coordinate:SetVelocity( velocity ) Coordinate:SetVelocity( velocity )
self:T(UTILS.PrintTableToLog(Coordinate)) self:I(UTILS.PrintTableToLog(Coordinate))
end end
return Coordinate return Coordinate
@@ -3478,7 +3459,7 @@ do -- SET_STATIC
--- Add STATIC(s) to SET_STATIC. --- Add STATIC(s) to SET_STATIC.
-- @param #SET_STATIC self -- @param #SET_STATIC self
-- @param Wrapper.Static#STATIC AddStatic A single STATIC. -- @param #string AddStatic A single STATIC.
-- @return #SET_STATIC self -- @return #SET_STATIC self
function SET_STATIC:AddStatic( AddStatic ) function SET_STATIC:AddStatic( AddStatic )
self:F2( AddStatic:GetName() ) self:F2( AddStatic:GetName() )
@@ -4389,8 +4370,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 and **NOT** the group name! --- Builds a set of CLIENTs that contain the given string in their unit/pilot name.
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. Pattern matching applies. -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string.
-- @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
@@ -4540,7 +4521,7 @@ do -- SET_CLIENT
if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then
-- CA Slot entered -- CA Slot entered
local ObjectName, Object = self:AddInDatabase( Event ) local ObjectName, Object = self:AddInDatabase( Event )
self:T( ObjectName, UTILS.PrintTableToLog(Object) ) self:I( ObjectName, UTILS.PrintTableToLog(Object) )
if Object and self:IsIncludeObject( Object ) then if Object and self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object ) self:Add( ObjectName, Object )
end end
@@ -8418,7 +8399,7 @@ do -- SET_SCENERY
--- Calculate current relative lifepoints of the SET objects, i.e. Life divided by Life0 as percentage value, eg 75 meaning 75% alive. --- Calculate current relative lifepoints of the SET objects, i.e. Life divided by Life0 as percentage value, eg 75 meaning 75% alive.
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the Life0 value to 120% -- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the Life0 value to 120%
-- of the last life value if life exceeds life0 ata any point. -- of the last life value if life exceeds life0 ata any point.
-- Thus we will get a smooth percentage decrease, if you use this e.g. as success criteria for a bombing task. -- Thus will will get a smooth percentage decrease, if you use this e.g. as success criteria for a bombing task.
-- @param #SET_SCENERY self -- @param #SET_SCENERY self
-- @return #number LifePoints -- @return #number LifePoints
function SET_SCENERY:GetRelativeLife() function SET_SCENERY:GetRelativeLife()

View File

@@ -200,22 +200,6 @@
-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed. -- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
-- --
-- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only)
--
-- * @{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
-- * @{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
--
-- ### Callsigns
--
-- * @{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
-- * @{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
--
-- ### Speed
--
-- * @{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
-- * @{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
-- * @{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
--
-- ## SPAWN **Spawn** methods -- ## SPAWN **Spawn** methods
-- --
-- Groups can be spawned at different times and methods: -- Groups can be spawned at different times and methods:
@@ -292,10 +276,9 @@ SPAWN = {
--- Enumerator for spawns at airbases --- Enumerator for spawns at airbases
-- @type SPAWN.Takeoff -- @type SPAWN.Takeoff
-- @field #number Air Take off happens in air. -- @extends Wrapper.Group#GROUP.Takeoff
-- @field #number Runway Spawn on runway. Does not work in MP!
-- @field #number Hot Spawn at parking with engines on. -- @field #SPAWN.Takeoff Takeoff
-- @field #number Cold Spawn at parking with engines off.
SPAWN.Takeoff = { SPAWN.Takeoff = {
Air = 1, Air = 1,
Runway = 2, Runway = 2,
@@ -537,7 +520,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
end end
if SpawnTemplate then if SpawnTemplate then
self.SpawnTemplate = UTILS.DeepCopy(SpawnTemplate) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!! self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
self.SpawnTemplatePrefix = SpawnTemplatePrefix self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix
self.SpawnTemplate.name = SpawnTemplatePrefix self.SpawnTemplate.name = SpawnTemplatePrefix
@@ -620,14 +603,12 @@ end
-- and any spaces before and after the resulting name are removed. -- and any spaces before and after the resulting name are removed.
-- IMPORTANT! This method MUST be the first used after :New !!! -- IMPORTANT! This method MUST be the first used after :New !!!
-- @param #SPAWN self -- @param #SPAWN self
-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names. -- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names.
-- @return #SPAWN self -- @return #SPAWN self
function SPAWN:InitKeepUnitNames( KeepUnitNames ) function SPAWN:InitKeepUnitNames( KeepUnitNames )
self:F() self:F()
self.SpawnInitKeepUnitNames = false self.SpawnInitKeepUnitNames = KeepUnitNames or true
if KeepUnitNames == true then self.SpawnInitKeepUnitNames = true end
return self return self
end end
@@ -743,7 +724,7 @@ end
-- @param #number Country Country id as number or enumerator: -- @param #number Country Country id as number or enumerator:
-- --
-- * @{DCS#country.id.RUSSIA} -- * @{DCS#country.id.RUSSIA}
-- * @{DCS#country.id.USA} -- * @{DCS#county.id.USA}
-- --
-- @return #SPAWN self -- @return #SPAWN self
function SPAWN:InitCountry( Country ) function SPAWN:InitCountry( Country )
@@ -799,82 +780,6 @@ function SPAWN:InitSkill( Skill )
return self return self
end end
--- [Airplane - F15/16/18/AWACS/B1B/Tanker only] Set the STN Link16 starting number of the Group; each unit of the spawned group will have a consecutive STN set.
-- @param #SPAWN self
-- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique!
-- @return #SPAWN self
function SPAWN:InitSTN(Octal)
self:F( { Octal = Octal } )
self.SpawnInitSTN = Octal or 77777
local num = UTILS.OctalToDecimal(Octal)
if num == nil or num < 1 then
self:E("WARNING - STN "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.STNS[num] ~= nil then
self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal])
end
return self
end
--- [Airplane - A10-C II only] Set the SADL TN starting number of the Group; each unit of the spawned group will have a consecutive SADL set.
-- @param #SPAWN self
-- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique!
-- @return #SPAWN self
function SPAWN:InitSADL(Octal)
self:F( { Octal = Octal } )
self.SpawnInitSADL = Octal or 7777
local num = UTILS.OctalToDecimal(Octal)
if num == nil or num < 1 then
self:E("WARNING - SADL "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.SADL[num] ~= nil then
self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal])
end
return self
end
--- [Airplane] Set the initial speed on spawning in meters per second. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number MPS The speed in MPS to use.
-- @return #SPAWN self
function SPAWN:InitSpeedMps(MPS)
self:F( { MPS = MPS } )
if MPS == nil or tonumber(MPS)<0 then
MPS=125
end
self.InitSpeed = MPS
return self
end
--- [Airplane] Set the initial speed on spawning in knots. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number Knots The speed in knots to use.
-- @return #SPAWN self
function SPAWN:InitSpeedKnots(Knots)
self:F( { Knots = Knots } )
if Knots == nil or tonumber(Knots)<0 then
Knots=300
end
self.InitSpeed = UTILS.KnotsToMps(Knots)
return self
end
--- [Airplane] Set the initial speed on spawning in kilometers per hour. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number KPH The speed in KPH to use.
-- @return #SPAWN self
function SPAWN:InitSpeedKph(KPH)
self:F( { KPH = KPH } )
if KPH == nil or tonumber(KPH)<0 then
KPH=UTILS.KnotsToKmph(300)
end
self.InitSpeed = UTILS.KmphToMps(KPH)
return self
end
--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor. --- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group.
@@ -1211,12 +1116,11 @@ end
-- @param #number Major Major number, i.e. the group number of this name, e.g. 1 - resulting in e.g. Texaco-2-1 -- @param #number Major Major number, i.e. the group number of this name, e.g. 1 - resulting in e.g. Texaco-2-1
-- @return #SPAWN self -- @return #SPAWN self
function SPAWN:InitCallSign(ID,Name,Minor,Major) function SPAWN:InitCallSign(ID,Name,Minor,Major)
local Name = Name or "Enfield"
self.SpawnInitCallSign = true self.SpawnInitCallSign = true
self.SpawnInitCallSignID = ID or 1 self.SpawnInitCallSignID = ID or 1
self.SpawnInitCallSignMinor = Minor or 1 self.SpawnInitCallSignMinor = Minor or 1
self.SpawnInitCallSignMajor = Major or 1 self.SpawnInitCallSignMajor = Major or 1
self.SpawnInitCallSignName=string.lower(Name):gsub("^%l", string.upper) self.SpawnInitCallSignName = string.lower(Name) or "enfield"
return self return self
end end
@@ -1471,30 +1375,6 @@ 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
@@ -1612,8 +1492,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
numTries = numTries + 1 numTries = numTries + 1
inZone = SpawnZone:IsVec2InZone(RandomVec2) inZone = SpawnZone:IsVec2InZone(RandomVec2)
--self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
--self:I(SpawnZone) self:I(SpawnZone)
end end
end end
if (not inZone) then if (not inZone) then
@@ -1769,19 +1649,6 @@ 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
@@ -1822,8 +1689,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
-- If there is a SpawnFunction hook defined, call it. -- If there is a SpawnFunction hook defined, call it.
if self.SpawnFunctionHook then if self.SpawnFunctionHook then
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group. -- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.3 ) self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.1 )
end end
-- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats. -- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
-- if self.Repeat then -- if self.Repeat then
@@ -1832,7 +1699,6 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
end end
self.SpawnGroups[self.SpawnIndex].Spawned = true self.SpawnGroups[self.SpawnIndex].Spawned = true
self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor = self.SpawnTemplatePrefix
return self.SpawnGroups[self.SpawnIndex].Group return self.SpawnGroups[self.SpawnIndex].Group
else else
-- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) -- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
@@ -3279,7 +3145,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:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } ) self:F2( { 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 )
@@ -3291,7 +3157,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:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
return self.SpawnMaxGroups return self.SpawnMaxGroups
end end
@@ -3440,27 +3306,15 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
if self.SpawnInitKeepUnitNames == false then if self.SpawnInitKeepUnitNames == false then
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
if not string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
end
SpawnTemplate.units[UnitID].unitId = nil SpawnTemplate.units[UnitID].unitId = nil
end end
else else
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
local SpawnInitKeepUnitIFF = false local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
SpawnInitKeepUnitIFF = true
end
local UnitPrefix, Rest
if SpawnInitKeepUnitIFF == false then
UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
self:T( { UnitPrefix, Rest } ) self:T( { UnitPrefix, Rest } )
--else
--UnitPrefix=SpawnTemplate.units[UnitID].name
end
--SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
SpawnTemplate.units[UnitID].unitId = nil SpawnTemplate.units[UnitID].unitId = nil
end end
end end
@@ -3541,60 +3395,36 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex
end end
end end
-- Speed
if self.InitSpeed then
SpawnTemplate.units[UnitID].speed = self.InitSpeed
end
-- Link16 -- Link16
local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft
if AddProps then if AddProps then
if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
if self.SpawnInitSTN then -- 4 digit octal with leading 0
local octal = self.SpawnInitSTN
if UnitID > 1 then
octal = _DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",octal)
else
-- 5 digit octal with leading 0
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
local num = UTILS.OctalToDecimal(octal) local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
if _DATABASE.STNS[num] ~= nil or UnitID > 1 then -- STN taken or next unit SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",UTILS.DecimalToOctal(decimal))
octal = _DATABASE:GetNextSTN(octal,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",octal)
else -- ED bug - chars in here else -- ED bug - chars in here
local OSTN = _DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name) local STN = math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088))
STN = STN+UnitID-1
local OSTN = UTILS.DecimalToOctal(STN)
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN) SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN)
end end
end end
end
-- A10CII -- A10CII
if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
-- 4 digit octal with leading 0 -- 3 digit octal with leading 0
if self.SpawnInitSADL then
local octal = self.SpawnInitSADL
if UnitID > 1 then
octal = _DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",octal)
else
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
local num = UTILS.OctalToDecimal(octal) local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
self.SpawnInitSADL = num -- we arrived here seeing that self.SpawnInitSADL == nil, but now that we have a SADL (num), we also need to set it to self.SpawnInitSADL in case SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",UTILS.DecimalToOctal(decimal))
-- we need to get the next SADL from _DATABASE, or else UTILS.OctalToDecimal() will fail in GetNextSADL
if _DATABASE.SADL[num] ~= nil or UnitID > 1 then -- SADL taken or next unit
octal = _DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
end
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",octal)
else -- ED bug - chars in here else -- ED bug - chars in here
local OSTN = _DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name) local STN = math.floor(UTILS.RandomGaussian(504/2,nil,100,504))
STN = STN+UnitID-1
local OSTN = UTILS.DecimalToOctal(STN)
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN) SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN)
end end
end end
end
-- VoiceCallsignNumber -- VoiceCallsignNumber
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type( Callsign ) ~= "number" then if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type( Callsign ) ~= "number" then
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber = SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber = SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]

View File

@@ -15,7 +15,7 @@
-- --
-- # Demo Missions -- # Demo Missions
-- --
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic) -- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Core/SpawnStatic)
-- --
-- --
-- === -- ===

View File

@@ -46,10 +46,6 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Zone)
--
-- ===
--
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit** -- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit**
-- --
@@ -330,14 +326,14 @@ function ZONE_BASE:GetRandomVec2()
return nil return nil
end end
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Define a random @{Core.Point#POINT_VEC2} within the zone.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_BASE:GetRandomPointVec2() function ZONE_BASE:GetRandomPointVec2()
return nil return nil
end end
--- Define a random @{Core.Point#POINT_VEC3} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. --- Define a random @{Core.Point#POINT_VEC3} within the zone.
-- @param #ZONE_BASE self -- @param #ZONE_BASE self
-- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. -- @return Core.Point#POINT_VEC3 The PointVec3 coordinates.
function ZONE_BASE:GetRandomPointVec3() function ZONE_BASE:GetRandomPointVec3()
@@ -903,7 +899,6 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
local Point = {} local Point = {}
local Vec2 = self:GetVec2() local Vec2 = self:GetVec2()
local countryID = CountryID or country.id.USA
Points = Points and Points or 360 Points = Points and Points or 360
@@ -915,7 +910,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
local CountryName = _DATABASE.COUNTRY_NAME[countryID] local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
local Tire = { local Tire = {
["country"] = CountryName, ["country"] = CountryName,
@@ -930,7 +925,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
["heading"] = 0, ["heading"] = 0,
} -- end of ["group"] } -- end of ["group"]
local Group = coalition.addStaticObject( countryID, Tire ) local Group = coalition.addStaticObject( CountryID, Tire )
if UnBound and UnBound == true then if UnBound and UnBound == true then
Group:destroy() Group:destroy()
end end
@@ -1180,7 +1175,7 @@ function ZONE_RADIUS:RemoveJunk()
return n return n
end end
--- Get a table of scanned units. --- Count the number of different coalitions inside the zone.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @return #table Table of DCS units and DCS statics inside the zone. -- @return #table Table of DCS units and DCS statics inside the zone.
function ZONE_RADIUS:GetScannedUnits() function ZONE_RADIUS:GetScannedUnits()
@@ -1215,7 +1210,7 @@ function ZONE_RADIUS:GetScannedSetUnit()
return SetUnit return SetUnit
end end
--- Get a set of scanned groups. --- Get a set of scanned units.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @return Core.Set#SET_GROUP Set of groups. -- @return Core.Set#SET_GROUP Set of groups.
function ZONE_RADIUS:GetScannedSetGroup() function ZONE_RADIUS:GetScannedSetGroup()
@@ -1515,7 +1510,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
return point return point
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@@ -1546,7 +1541,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer )
end end
--- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. --- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@@ -1990,7 +1985,7 @@ function ZONE_GROUP:GetRandomVec2()
return Point return Point
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
-- @param #ZONE_GROUP self -- @param #ZONE_GROUP self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@@ -2834,7 +2829,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2()
end end
end end
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @return @{Core.Point#POINT_VEC2} -- @return @{Core.Point#POINT_VEC2}
function ZONE_POLYGON_BASE:GetRandomPointVec2() function ZONE_POLYGON_BASE:GetRandomPointVec2()
@@ -2847,7 +2842,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2()
return PointVec2 return PointVec2
end end
--- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. --- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @return @{Core.Point#POINT_VEC3} -- @return @{Core.Point#POINT_VEC3}
function ZONE_POLYGON_BASE:GetRandomPointVec3() function ZONE_POLYGON_BASE:GetRandomPointVec3()
@@ -3076,26 +3071,10 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
-- points for the drawings are saved in local space, so add the object's map x and y coordinates to get -- points for the drawings are saved in local space, so add the object's map x and y coordinates to get
-- world space points we can use -- world space points we can use
for _, point in UTILS.spairs(object["points"]) do for _, point in UTILS.spairs(object["points"]) do
-- check if we want to skip adding a point
local skip = false
local p = {x = object["mapX"] + point["x"], local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] } y = object["mapY"] + point["y"] }
-- Check if the same coordinates already exist in the list, skip if they do
-- This can happen when drawing a Polygon in Free mode, DCS adds points on
-- top of each other that are in the `mission` file, but not visible in the
-- Mission Editor
for _, pt in pairs(points) do
if pt.x == p.x and pt.y == p.y then
skip = true
end
end
-- if it's a unique point, add it
if not skip then
table.add(points, p) table.add(points, p)
end end
end
elseif object["polygonMode"] == "rect" then elseif object["polygonMode"] == "rect" then
-- the points for a rect are saved as local coordinates with an angle. To get the world space points from this -- the points for a rect are saved as local coordinates with an angle. To get the world space points from this
-- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was -- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was
@@ -3112,7 +3091,6 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
points = {p1, p2, p3, p4} points = {p1, p2, p3, p4}
else else
-- bring the Arrow code over from Shape/Polygon
-- something else that might be added in the future -- something else that might be added in the future
end end
end end
@@ -3852,18 +3830,18 @@ function ZONE_OVAL:GetRandomVec2()
return {x=rx, y=ry} return {x=rx, y=ry}
end end
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Define a random @{Core.Point#POINT_VEC2} within the zone.
-- @param #ZONE_OVAL self -- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec2() function ZONE_OVAL:GetRandomPointVec2()
return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
end end
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table. --- Define a random @{Core.Point#POINT_VEC2} within the zone.
-- @param #ZONE_OVAL self -- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec3() function ZONE_OVAL:GetRandomPointVec3()
return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) return POINT_VEC2:NewFromVec3(self:GetRandomVec2())
end end
--- Draw the zone on the F10 map. --- Draw the zone on the F10 map.
@@ -4003,7 +3981,7 @@ do -- ZONE_AIRBASE
return ZoneVec2 return ZoneVec2
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table. --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
-- @param #ZONE_AIRBASE self -- @param #ZONE_AIRBASE self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.

File diff suppressed because it is too large Load Diff

View File

@@ -1,806 +0,0 @@
--- **Functional** -- Send a truck to supply artillery groups.
--
-- ===
--
-- **AMMOTRUCK** - Send a truck to supply artillery groups.
--
-- ===
--
-- ## Missions:
--
-- Demo missions can be found on [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Functional/AmmoTruck)
--
-- ===
--
-- ### Author : **applevangelist**
--
-- @module Functional.AmmoTruck
-- @image Artillery.JPG
--
-- Last update: July 2023
-------------------------------------------------------------------------
--- **AMMOTRUCK** class, extends Core.Fsm#FSM
-- @type AMMOTRUCK
-- @field #string ClassName Class Name
-- @field #string lid Lid for log entries
-- @field #string version Version string
-- @field #string alias Alias name
-- @field #boolean debug Debug flag
-- @field #table trucklist List of (alive) #AMMOTRUCK.data trucks
-- @field #table targetlist List of (alive) #AMMOTRUCK.data artillery
-- @field #number coalition Coalition this is for
-- @field Core.Set#SET_GROUP truckset SET of trucks
-- @field Core.Set#SET_GROUP targetset SET of artillery
-- @field #table remunitionqueue List of (alive) #AMMOTRUCK.data artillery to be reloaded
-- @field #table waitingtargets List of (alive) #AMMOTRUCK.data artillery waiting
-- @field #number ammothreshold Threshold (min) ammo before sending a truck
-- @field #number remunidist Max distance trucks will go
-- @field #number monitor Monitor interval in seconds
-- @field #number unloadtime Unload time in seconds
-- @field #number waitingtime Max waiting time in seconds
-- @field #boolean routeonroad Route truck on road if true (default)
-- @field #number reloads Number of reloads a single truck can do before he must return home
-- @extends Core.Fsm#FSM
--- *Amateurs talk about tactics, but professionals study logistics.* - General Robert H Barrow, USMC
--
-- Simple Class to re-arm your artillery with trucks.
--
-- #AMMOTRUCK
--
-- * Controls a SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition.
--
-- ## 1 The AMMOTRUCK concept
--
-- A SET\_GROUP of trucks which will re-arm a SET\_GROUP of artillery groups when they run out of ammunition. They will be based on a
-- homebase and drive from there to the artillery groups and then back home.
-- Trucks are the **only known in-game mechanic** to re-arm artillery and other units in DCS. Working units are e.g.: M-939 (blue), Ural-375 and ZIL-135 (both red).
--
-- ## 2 Set-up
--
-- Define a set of trucks and a set of artillery:
--
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
--
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
--
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
--
-- ## 2 Options and their default values
--
-- ammotruck.ammothreshold = 5 -- send a truck when down to this many rounds
-- ammotruck.remunidist = 20000 -- 20km - send trucks max this far from home
-- ammotruck.unloadtime = 600 -- 10 minutes - min time to unload ammunition
-- ammotruck.waitingtime = 1800 -- 30 mintes - wait max this long until remunition is done
-- ammotruck.monitor = -60 -- 1 minute - AMMOTRUCK checks run every one minute
-- ammotruck.routeonroad = true -- Trucks will **try** to drive on roads
-- ammotruck.usearmygroup = false -- If true, will make use of ARMYGROUP in the background (if used in DEV branch)
-- ammotruck.reloads = 5 -- Maxn re-arms a truck can do before he needs to go home and restock. Set to -1 for unlimited
--
-- ## 3 FSM Events to shape mission
--
-- Truck has been sent off:
--
-- function ammotruck:OnAfterRouteTruck(From, Event, To, Truckdata, Aridata)
-- ...
-- end
--
-- Truck has arrived:
--
-- function ammotruck:OnAfterTruckArrived(From, Event, To, Truckdata)
-- ...
-- end
--
-- Truck is unloading:
--
-- function ammotruck:OnAfterTruckUnloading(From, Event, To, Truckdata)
-- ...
-- end
--
-- Truck is returning home:
--
-- function ammotruck:OnAfterTruckReturning(From, Event, To, Truckdata)
-- ...
-- end
--
-- Truck is arrived at home:
--
-- function ammotruck:OnAfterTruckHome(From, Event, To, Truckdata)
-- ...
-- end
--
-- @field #AMMOTRUCK
AMMOTRUCK = {
ClassName = "AMMOTRUCK",
lid = "",
version = "0.0.12",
alias = "",
debug = false,
trucklist = {},
targetlist = {},
coalition = nil,
truckset = nil,
targetset = nil,
remunitionqueue = {},
waitingtargets = {},
ammothreshold = 5,
remunidist = 20000,
monitor = -60,
unloadtime = 600,
waitingtime = 1800,
routeonroad = true,
reloads = 5,
}
---
-- @type AMMOTRUCK.State
AMMOTRUCK.State = {
IDLE = "idle",
DRIVING = "driving",
ARRIVED = "arrived",
UNLOADING = "unloading",
RETURNING = "returning",
WAITING = "waiting",
RELOADING = "reloading",
OUTOFAMMO = "outofammo",
REQUESTED = "requested",
}
---
--@type AMMOTRUCK.data
--@field Wrapper.Group#GROUP group
--@field #string name
--@field #AMMOTRUCK.State statusquo
--@field #number timestamp
--@field #number ammo
--@field Core.Point#COORDINATE coordinate
--@field #string targetname
--@field Wrapper.Group#GROUP targetgroup
--@field Core.Point#COORDINATE targetcoordinate
--@field #number reloads
---
-- @param #AMMOTRUCK self
-- @param Core.Set#SET_GROUP Truckset Set of truck groups
-- @param Core.Set#SET_GROUP Targetset Set of artillery groups
-- @param #number Coalition Coalition
-- @param #string Alias Alias Name
-- @param Core.Zone#ZONE Homezone Home, return zone for trucks
-- @return #AMMOTRUCK self
-- @usage
-- Define a set of trucks and a set of artillery:
-- local truckset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Ammo Truck"):FilterStart()
-- local ariset = SET_GROUP:New():FilterCoalitions("blue"):FilterActive(true):FilterCategoryGround():FilterPrefixes("Artillery"):FilterStart()
--
-- Create an AMMOTRUCK object to take care of the artillery using the trucks, with a homezone:
-- local ammotruck = AMMOTRUCK:New(truckset,ariset,coalition.side.BLUE,"Logistics",ZONE:FindByName("HomeZone")
function AMMOTRUCK:New(Truckset,Targetset,Coalition,Alias,Homezone)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, FSM:New()) -- #AMMOTRUCK
self.truckset = Truckset -- Core.Set#SET_GROUP
self.targetset = Targetset -- Core.Set#SET_GROUP
self.coalition = Coalition -- #number
self.alias = Alias -- #string
self.debug = false
self.remunitionqueue = {}
self.trucklist = {}
self.targetlist = {}
self.ammothreshold = 5
self.remunidist = 20000
self.homezone = Homezone -- Core.Zone#ZONE
self.waitingtime = 1800
self.usearmygroup = false
self.hasarmygroup = false
-- Log id.
self.lid=string.format("AMMOTRUCK %s | %s | ", self.version, self.alias)
self:SetStartState("Stopped")
self:AddTransition("Stopped", "Start", "Running")
self:AddTransition("*", "Monitor", "*")
self:AddTransition("*", "RouteTruck", "*")
self:AddTransition("*", "TruckArrived", "*")
self:AddTransition("*", "TruckUnloading", "*")
self:AddTransition("*", "TruckReturning", "*")
self:AddTransition("*", "TruckHome", "*")
self:AddTransition("*", "Stop", "Stopped")
self:__Start(math.random(5,10))
self:I(self.lid .. "Started")
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Stop". Stops the AMMOTRUCK and all its event handlers.
-- @function [parent=#AMMOTRUCK] Stop
-- @param #AMMOTRUCK self
--- Triggers the FSM event "Stop" after a delay. Stops the AMMOTRUCK and all its event handlers.
-- @function [parent=#AMMOTRUCK] __Stop
-- @param #AMMOTRUCK self
-- @param #number delay Delay in seconds.
--- On after "RouteTruck" event.
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
-- @param #AMMOTRUCK self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AMMOTRUCK.data Truck
-- @param #AMMOTRUCK.data Artillery
--- On after "TruckUnloading" event.
-- @function [parent=#AMMOTRUCK] OnAfterTruckUnloading
-- @param #AMMOTRUCK self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AMMOTRUCK.data Truck
--- On after "TruckReturning" event.
-- @function [parent=#AMMOTRUCK] OnAfterTruckReturning
-- @param #AMMOTRUCK self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AMMOTRUCK.data Truck
--- On after "RouteTruck" event.
-- @function [parent=#AMMOTRUCK] OnAfterRouteTruck
-- @param #AMMOTRUCK self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AMMOTRUCK.data Truck
--- On after "TruckHome" event.
-- @function [parent=#AMMOTRUCK] OnAfterTruckHome
-- @param #AMMOTRUCK self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #AMMOTRUCK.data Truck
return self
end
---
-- @param #AMMOTRUCK self
-- @param #table dataset table of #AMMOTRUCK.data entries
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckDrivingTrucks(dataset)
self:T(self.lid .. " CheckDrivingTrucks")
local data = dataset
for _,_data in pairs (data) do
local truck = _data -- #AMMOTRUCK.data
-- see if we arrived at destination
local coord = truck.group:GetCoordinate()
local tgtcoord = truck.targetcoordinate
local dist = coord:Get2DDistance(tgtcoord)
if dist <= 150 then
-- arrived
truck.statusquo = AMMOTRUCK.State.ARRIVED
truck.timestamp = timer.getAbsTime()
truck.coordinate = coord
self:__TruckArrived(1,truck)
end
-- still driving?
local Tnow = timer.getAbsTime()
if Tnow - truck.timestamp > 30 then
local group = truck.group
if self.usearmygroup then
group = truck.group:GetGroup()
end
local currspeed = group:GetVelocityKMH()
if truck.lastspeed then
if truck.lastspeed == 0 and currspeed == 0 then
self:T(truck.group:GetName().." Is not moving!")
-- try and move it
truck.timestamp = timer.getAbsTime()
if self.routeonroad then
group:RouteGroundOnRoad(truck.targetcoordinate,30,2,"Vee")
else
group:RouteGroundTo(truck.targetcoordinate,30,"Vee",2)
end
end
truck.lastspeed = currspeed
else
truck.lastspeed = currspeed
truck.timestamp = timer.getAbsTime()
end
self:I({truck=truck.group:GetName(),currspeed=currspeed,lastspeed=truck.lastspeed})
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param Wrapper.Group#GROUP Group
-- @return #AMMOTRUCK self
function AMMOTRUCK:GetAmmoStatus(Group)
local ammotot, shells, rockets, bombs, missiles, narti = Group:GetAmmunition()
return rockets+missiles+narti
end
---
-- @param #AMMOTRUCK self
-- @param #table dataset table of #AMMOTRUCK.data entries
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckWaitingTargets(dataset)
self:T(self.lid .. " CheckWaitingTargets")
local data = dataset
for _,_data in pairs (data) do
local truck = _data -- #AMMOTRUCK.data
-- see how long we're waiting - maybe ammo truck is dead?
local Tnow = timer.getAbsTime()
local Tdiff = Tnow - truck.timestamp
if Tdiff > self.waitingtime then
local hasammo = self:GetAmmoStatus(truck.group)
if hasammo <= self.ammothreshold then
truck.statusquo = AMMOTRUCK.State.OUTOFAMMO
else
truck.statusquo = AMMOTRUCK.State.IDLE
end
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #table dataset table of #AMMOTRUCK.data entries
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckReturningTrucks(dataset)
self:T(self.lid .. " CheckReturningTrucks")
local data = dataset
local tgtcoord = self.homezone:GetCoordinate()
local radius = self.homezone:GetRadius()
for _,_data in pairs (data) do
local truck = _data -- #AMMOTRUCK.data
-- see if we arrived at destination
local coord = truck.group:GetCoordinate()
local dist = coord:Get2DDistance(tgtcoord)
self:T({name=truck.name,radius=radius,distance=dist})
if dist <= radius then
-- arrived
truck.statusquo = AMMOTRUCK.State.IDLE
truck.timestamp = timer.getAbsTime()
truck.coordinate = coord
truck.reloads = self.reloads or 5
self:__TruckHome(1,truck)
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string name Artillery group name to find
-- @return #AMMOTRUCK.data Data
function AMMOTRUCK:FindTarget(name)
self:T(self.lid .. " FindTarget")
local data = nil
local dataset = self.targetlist
for _,_entry in pairs(dataset) do
local entry = _entry -- #AMMOTRUCK.data
if entry.name == name then
data = entry
break
end
end
return data
end
---
-- @param #AMMOTRUCK self
-- @param #string name Truck group name to find
-- @return #AMMOTRUCK.data Data
function AMMOTRUCK:FindTruck(name)
self:T(self.lid .. " FindTruck")
local data = nil
local dataset = self.trucklist
for _,_entry in pairs(dataset) do
local entry = _entry -- #AMMOTRUCK.data
if entry.name == name then
data = entry
break
end
end
return data
end
---
-- @param #AMMOTRUCK self
-- @param #table dataset table of #AMMOTRUCK.data entries
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckArrivedTrucks(dataset)
self:T(self.lid .. " CheckArrivedTrucks")
local data = dataset
for _,_data in pairs (data) do
-- set to unloading
local truck = _data -- #AMMOTRUCK.data
truck.statusquo = AMMOTRUCK.State.UNLOADING
truck.timestamp = timer.getAbsTime()
self:__TruckUnloading(2,truck)
-- set target to reloading
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
if aridata then
aridata.statusquo = AMMOTRUCK.State.RELOADING
aridata.timestamp = timer.getAbsTime()
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #table dataset table of #AMMOTRUCK.data entries
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckUnloadingTrucks(dataset)
self:T(self.lid .. " CheckUnloadingTrucks")
local data = dataset
for _,_data in pairs (data) do
-- check timestamp
local truck = _data -- #AMMOTRUCK.data
local Tnow = timer.getAbsTime()
local Tpassed = Tnow - truck.timestamp
local hasammo = self:GetAmmoStatus(truck.targetgroup)
if Tpassed > self.unloadtime and hasammo > self.ammothreshold then
truck.statusquo = AMMOTRUCK.State.RETURNING
truck.timestamp = timer.getAbsTime()
self:__TruckReturning(2,truck)
-- set target to reloaded
local aridata = self:FindTarget(truck.targetname) -- #AMMOTRUCK.data
if aridata then
aridata.statusquo = AMMOTRUCK.State.IDLE
aridata.timestamp = timer.getAbsTime()
end
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckTargetsAlive()
self:T(self.lid .. " CheckTargetsAlive")
local arilist = self.targetlist
for _,_ari in pairs(arilist) do
local ari = _ari -- #AMMOTRUCK.data
if ari.group and ari.group:IsAlive() then
-- everything fine
else
-- ari dead
self.targetlist[ari.name] = nil
end
end
-- new arrivals?
local aritable = self.targetset:GetSetObjects() --#table
for _,_ari in pairs(aritable) do
local ari = _ari -- Wrapper.Group#GROUP
if ari and ari:IsAlive() and not self.targetlist[ari:GetName()] then
local name = ari:GetName()
local newari = {} -- #AMMOTRUCK.data
newari.name = name
newari.group = ari
newari.statusquo = AMMOTRUCK.State.IDLE
newari.timestamp = timer.getAbsTime()
newari.coordinate = ari:GetCoordinate()
local hasammo = self:GetAmmoStatus(ari)
--newari.ammo = ari:GetAmmunition()
newari.ammo = hasammo
self.targetlist[name] = newari
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @return #AMMOTRUCK self
function AMMOTRUCK:CheckTrucksAlive()
self:T(self.lid .. " CheckTrucksAlive")
local trucklist = self.trucklist
for _,_truck in pairs(trucklist) do
local truck = _truck -- #AMMOTRUCK.data
if truck.group and truck.group:IsAlive() then
-- everything fine
else
-- truck dead
local tgtname = truck.targetname
local targetdata = self:FindTarget(tgtname) -- #AMMOTRUCK.data
if targetdata then
if targetdata.statusquo ~= AMMOTRUCK.State.IDLE then
targetdata.statusquo = AMMOTRUCK.State.IDLE
end
end
self.trucklist[truck.name] = nil
end
end
-- new arrivals?
local trucktable = self.truckset:GetSetObjects() --#table
for _,_truck in pairs(trucktable) do
local truck = _truck -- Wrapper.Group#GROUP
if truck and truck:IsAlive() and not self.trucklist[truck:GetName()] then
local name = truck:GetName()
local newtruck = {} -- #AMMOTRUCK.data
newtruck.name = name
newtruck.group = truck
if self.hasarmygroup then
-- is (not) already ARMYGROUP?
if truck.ClassName and truck.ClassName == "GROUP" then
local trucker = ARMYGROUP:New(truck)
trucker:Activate()
newtruck.group = trucker
end
end
newtruck.statusquo = AMMOTRUCK.State.IDLE
newtruck.timestamp = timer.getAbsTime()
newtruck.coordinate = truck:GetCoordinate()
newtruck.reloads = self.reloads or 5
self.trucklist[name] = newtruck
end
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterStart(From, Event, To)
self:T({From, Event, To})
if ARMYGROUP and self.usearmygroup then
self.hasarmygroup = true
else
self.hasarmygroup = false
end
if self.debug then
BASE:TraceOn()
BASE:TraceClass("AMMOTRUCK")
end
self:CheckTargetsAlive()
self:CheckTrucksAlive()
self:__Monitor(-30)
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterMonitor(From, Event, To)
self:T({From, Event, To})
self:CheckTargetsAlive()
self:CheckTrucksAlive()
-- update ammo state
local remunition = false
local remunitionqueue = {}
local waitingtargets = {}
for _,_ari in pairs(self.targetlist) do
local data = _ari -- #AMMOTRUCK.data
if data.group and data.group:IsAlive() then
data.ammo = self:GetAmmoStatus(data.group)
data.timestamp = timer.getAbsTime()
local text = string.format("Ari %s | Ammo %d | State %s",data.name,data.ammo,data.statusquo)
self:T(text)
if data.ammo <= self.ammothreshold and (data.statusquo == AMMOTRUCK.State.IDLE or data.statusquo == AMMOTRUCK.State.OUTOFAMMO) then
-- add to remu queue
data.statusquo = AMMOTRUCK.State.OUTOFAMMO
remunitionqueue[#remunitionqueue+1] = data
remunition = true
elseif data.statusquo == AMMOTRUCK.State.WAITING then
waitingtargets[#waitingtargets+1] = data
end
else
self.targetlist[data.name] = nil
end
end
-- sort trucks in buckets
local idletrucks = {}
local drivingtrucks = {}
local unloadingtrucks = {}
local arrivedtrucks = {}
local returningtrucks = {}
local found = false
for _,_truckdata in pairs(self.trucklist) do
local data = _truckdata -- #AMMOTRUCK.data
if data.group and data.group:IsAlive() then
-- check state
local text = string.format("Truck %s | State %s",data.name,data.statusquo)
self:T(text)
if data.statusquo == AMMOTRUCK.State.IDLE then
idletrucks[#idletrucks+1] = data
found = true
elseif data.statusquo == AMMOTRUCK.State.DRIVING then
drivingtrucks[#drivingtrucks+1] = data
elseif data.statusquo == AMMOTRUCK.State.ARRIVED then
arrivedtrucks[#arrivedtrucks+1] = data
elseif data.statusquo == AMMOTRUCK.State.UNLOADING then
unloadingtrucks[#unloadingtrucks+1] = data
elseif data.statusquo == AMMOTRUCK.State.RETURNING then
returningtrucks[#returningtrucks+1] = data
if data.reloads > 0 or data.reloads == -1 then
idletrucks[#idletrucks+1] = data
found = true
end
end
else
self.truckset[data.name] = nil
end
end
-- see if we can/need route one
local n=0
if found and remunition then
-- match
--local match = false
for _,_truckdata in pairs(idletrucks) do
local truckdata = _truckdata -- #AMMOTRUCK.data
local truckcoord = truckdata.group:GetCoordinate() -- Core.Point#COORDINATE
for _,_aridata in pairs(remunitionqueue) do
local aridata = _aridata -- #AMMOTRUCK.data
local aricoord = aridata.coordinate
local distance = truckcoord:Get2DDistance(aricoord)
if distance <= self.remunidist and aridata.statusquo == AMMOTRUCK.State.OUTOFAMMO and n <= #idletrucks then
n = n + 1
aridata.statusquo = AMMOTRUCK.State.REQUESTED
self:__RouteTruck(n*5,truckdata,aridata)
break
end
end
end
end
-- check driving trucks
if #drivingtrucks > 0 then
self:CheckDrivingTrucks(drivingtrucks)
end
-- check arrived trucks
if #arrivedtrucks > 0 then
self:CheckArrivedTrucks(arrivedtrucks)
end
-- check unloading trucks
if #unloadingtrucks > 0 then
self:CheckUnloadingTrucks(unloadingtrucks)
end
-- check returningtrucks trucks
if #returningtrucks > 0 then
self:CheckReturningTrucks(returningtrucks)
end
-- check waiting targets
if #waitingtargets > 0 then
self:CheckWaitingTargets(waitingtargets)
end
self:__Monitor(self.monitor)
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param #AMMOTRUCK.data Truckdata
-- @param #AMMOTRUCK.data Aridata
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterRouteTruck(From, Event, To, Truckdata, Aridata)
self:T({From, Event, To, Truckdata.name, Aridata.name})
local truckdata = Truckdata -- #AMMOTRUCK.data
local aridata = Aridata -- #AMMOTRUCK.data
local tgtgrp = aridata.group
local tgtzone = ZONE_GROUP:New(aridata.name,tgtgrp,30)
local tgtcoord = tgtzone:GetRandomCoordinate(15)
if self.hasarmygroup then
local mission = AUFTRAG:NewONGUARD(tgtcoord)
local oldmission = truckdata.group:GetMissionCurrent()
if oldmission then oldmission:Cancel() end
mission:SetTime(5)
mission:SetTeleport(false)
truckdata.group:AddMission(mission)
elseif self.routeonroad then
truckdata.group:RouteGroundOnRoad(tgtcoord,30)
else
truckdata.group:RouteGroundTo(tgtcoord,30)
end
truckdata.statusquo = AMMOTRUCK.State.DRIVING
truckdata.targetgroup = tgtgrp
truckdata.targetname = aridata.name
truckdata.targetcoordinate = tgtcoord
aridata.statusquo = AMMOTRUCK.State.WAITING
aridata.timestamp = timer.getAbsTime()
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param #AMMOTRUCK.data Truckdata
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterTruckUnloading(From, Event, To, Truckdata)
local m = MESSAGE:New("Truck "..Truckdata.name.." unloading!",15,"AmmoTruck"):ToCoalitionIf(self.coalition,self.debug)
local truck = Truckdata -- Functional.AmmoTruck#AMMOTRUCK.data
local coord = truck.group:GetCoordinate()
local heading = truck.group:GetHeading()
heading = heading < 180 and (360-heading) or (heading - 180)
local cid = self.coalition == coalition.side.BLUE and country.id.USA or country.id.RUSSIA
cid = self.coalition == coalition.side.NEUTRAL and country.id.UN_PEACEKEEPERS or cid
local ammo = {}
for i=1,5 do
ammo[i] = SPAWNSTATIC:NewFromType("ammo_cargo","Cargos",cid)
:InitCoordinate(coord:Translate((15+((i-1)*4)),heading))
:Spawn(0,"AmmoCrate-"..math.random(1,10000))
end
local function destroyammo(ammo)
for _,_crate in pairs(ammo) do
_crate:Destroy(false)
end
end
local scheduler = SCHEDULER:New(nil,destroyammo,{ammo},self.waitingtime)
-- one reload less
if truck.reloads ~= -1 then
truck.reloads = truck.reloads - 1
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param #AMMOTRUCK.data Truck
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterTruckReturning(From, Event, To, Truck)
self:T({From, Event, To, Truck.name})
-- route home
local truckdata = Truck -- #AMMOTRUCK.data
local tgtzone = self.homezone
local tgtcoord = tgtzone:GetRandomCoordinate()
if self.hasarmygroup then
local mission = AUFTRAG:NewONGUARD(tgtcoord)
local oldmission = truckdata.group:GetMissionCurrent()
if oldmission then oldmission:Cancel() end
mission:SetTime(5)
mission:SetTeleport(false)
truckdata.group:AddMission(mission)
elseif self.routeonroad then
truckdata.group:RouteGroundOnRoad(tgtcoord,30,1,"Cone")
else
truckdata.group:RouteGroundTo(tgtcoord,30,"Cone",1)
end
return self
end
---
-- @param #AMMOTRUCK self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AMMOTRUCK self
function AMMOTRUCK:onafterStop(From, Event, To)
self:T({From, Event, To})
return self
end

File diff suppressed because it is too large Load Diff

View File

@@ -184,7 +184,7 @@
do -- DESIGNATE do -- DESIGNATE
-- @type DESIGNATE --- @type DESIGNATE
-- @extends Core.Fsm#FSM_PROCESS -- @extends Core.Fsm#FSM_PROCESS
--- Manage the designation of detected targets. --- Manage the designation of detected targets.
@@ -525,7 +525,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive( self.AttackSet:ForEachGroupAlive(
-- @param Wrapper.Group#GROUP AttackGroup --- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup ) function( AttackGroup )
self.FlashStatusMenu[AttackGroup] = FlashMenu self.FlashStatusMenu[AttackGroup] = FlashMenu
end end
@@ -554,7 +554,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive( self.AttackSet:ForEachGroupAlive(
-- @param Wrapper.Group#GROUP AttackGroup --- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup ) function( AttackGroup )
self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage
end end
@@ -826,7 +826,7 @@ do -- DESIGNATE
-- This Detection is obsolete, remove from the designate scope -- This Detection is obsolete, remove from the designate scope
self.Designating[DesignateIndex] = nil self.Designating[DesignateIndex] = nil
self.AttackSet:ForEachGroupAlive( self.AttackSet:ForEachGroupAlive(
-- @param Wrapper.Group#GROUP AttackGroup --- @param Wrapper.Group#GROUP AttackGroup
function( AttackGroup ) function( AttackGroup )
if AttackGroup:IsAlive() == true then if AttackGroup:IsAlive() == true then
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " ) local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
@@ -903,7 +903,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive( self.AttackSet:ForEachGroupAlive(
-- @param Wrapper.Group#GROUP GroupReport --- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup ) function( AttackGroup )
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
@@ -1060,7 +1060,7 @@ do -- DESIGNATE
self.AttackSet:ForEachGroupAlive( self.AttackSet:ForEachGroupAlive(
-- @param Wrapper.Group#GROUP GroupReport --- @param Wrapper.Group#GROUP GroupReport
function( AttackGroup ) function( AttackGroup )
self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup ) self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup )
@@ -1198,7 +1198,7 @@ do -- DESIGNATE
--local ReportTypes = REPORT:New() --local ReportTypes = REPORT:New()
--local ReportLaserCodes = REPORT:New() --local ReportLaserCodes = REPORT:New()
--TargetSetUnit:Flush( self ) TargetSetUnit:Flush( self )
--self:F( { Recces = self.Recces } ) --self:F( { Recces = self.Recces } )
for TargetUnit, RecceData in pairs( self.Recces ) do for TargetUnit, RecceData in pairs( self.Recces ) do
@@ -1229,12 +1229,10 @@ do -- DESIGNATE
end end
end end
if TargetSetUnit == nil then return end
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0, TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
-- @param Wrapper.Unit#UNIT SmokeUnit --- @param Wrapper.Unit#UNIT SmokeUnit
function( TargetUnit ) function( TargetUnit )
self:F( { TargetUnit = TargetUnit:GetName() } ) self:F( { TargetUnit = TargetUnit:GetName() } )
@@ -1255,7 +1253,7 @@ do -- DESIGNATE
local RecceUnit = UnitData -- Wrapper.Unit#UNIT local RecceUnit = UnitData -- Wrapper.Unit#UNIT
local RecceUnitDesc = RecceUnit:GetDesc() local RecceUnitDesc = RecceUnit:GetDesc()
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )x --self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
if RecceUnit:IsLasing() == false then if RecceUnit:IsLasing() == false then
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } ) --self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
@@ -1277,10 +1275,9 @@ do -- DESIGNATE
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration ) local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
local AttackSet = self.AttackSet local AttackSet = self.AttackSet
local DesignateName = self.DesignateName local DesignateName = self.DesignateName
local typename = TargetUnit:GetTypeName()
function Spot:OnAfterDestroyed( From, Event, To ) function Spot:OnAfterDestroyed( From, Event, To )
self.Recce:MessageToSetGroup( "Target " ..typename .. " destroyed. " .. TargetSetUnit:CountAlive() .. " targets left.", self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.",
5, AttackSet, self.DesignateName ) 5, AttackSet, self.DesignateName )
end end
@@ -1288,7 +1285,7 @@ do -- DESIGNATE
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function. -- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
MarkingCount = MarkingCount + 1 MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName() local TargetUnitType = TargetUnit:GetTypeName()
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnitType .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.", RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
10, self.AttackSet, DesignateName ) 10, self.AttackSet, DesignateName )
if not MarkedTypes[TargetUnitType] then if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true MarkedTypes[TargetUnitType] = true
@@ -1395,7 +1392,7 @@ do -- DESIGNATE
local MarkedCount = 0 local MarkedCount = 0
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0, TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
-- @param Wrapper.Unit#UNIT SmokeUnit --- @param Wrapper.Unit#UNIT SmokeUnit
function( SmokeUnit ) function( SmokeUnit )
if MarkedCount < self.MaximumMarkings then if MarkedCount < self.MaximumMarkings then
@@ -1460,11 +1457,10 @@ do -- DESIGNATE
-- @param #DESIGNATE self -- @param #DESIGNATE self
-- @return #DESIGNATE -- @return #DESIGNATE
function DESIGNATE:onafterDoneSmoking( From, Event, To, Index ) function DESIGNATE:onafterDoneSmoking( From, Event, To, Index )
if self.Designating[Index] ~= nil then
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" ) self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
self:SetDesignateMenu() self:SetDesignateMenu()
end end
end
--- DoneIlluminating --- DoneIlluminating
-- @param #DESIGNATE self -- @param #DESIGNATE self
@@ -1476,3 +1472,5 @@ do -- DESIGNATE
end end
end end

View File

@@ -545,7 +545,7 @@ do -- DETECTION_BASE
-- @param #string To The To State string. -- @param #string To The To State string.
function DETECTION_BASE:onafterDetect( From, Event, To ) function DETECTION_BASE:onafterDetect( From, Event, To )
local DetectDelay = 0.15 local DetectDelay = 0.1
self.DetectionCount = 0 self.DetectionCount = 0
self.DetectionRun = 0 self.DetectionRun = 0
self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table
@@ -604,7 +604,7 @@ do -- DETECTION_BASE
-- @param #number DetectionTimeStamp Time stamp of detection event. -- @param #number DetectionTimeStamp Time stamp of detection event.
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp ) function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
self:I( { DetectedObjects = self.DetectedObjects } ) -- self:F( { DetectedObjects = self.DetectedObjects } )
self.DetectionRun = self.DetectionRun + 1 self.DetectionRun = self.DetectionRun + 1
@@ -612,14 +612,14 @@ do -- DETECTION_BASE
if Detection and Detection:IsAlive() then if Detection and Detection:IsAlive() then
self:I( { "DetectionGroup is Alive", Detection:GetName() } ) -- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
local DetectionGroupName = Detection:GetName() local DetectionGroupName = Detection:GetName()
local DetectionUnit = Detection:GetUnit( 1 ) local DetectionUnit = Detection:GetUnit( 1 )
local DetectedUnits = {} local DetectedUnits = {}
local DetectedTargets = DetectionUnit:GetDetectedTargets( local DetectedTargets = Detection:GetDetectedTargets(
self.DetectVisual, self.DetectVisual,
self.DetectOptical, self.DetectOptical,
self.DetectRadar, self.DetectRadar,
@@ -628,9 +628,7 @@ do -- DETECTION_BASE
self.DetectDLINK self.DetectDLINK
) )
--self:I( { DetectedTargets = DetectedTargets } ) self:F( { DetectedTargets = DetectedTargets } )
--self:I(UTILS.PrintTableToLog(DetectedTargets))
for DetectionObjectID, Detection in pairs( DetectedTargets ) do for DetectionObjectID, Detection in pairs( DetectedTargets ) do
local DetectedObject = Detection.object -- DCS#Object local DetectedObject = Detection.object -- DCS#Object

View File

@@ -19,10 +19,6 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Functional/FOX)
--
-- ===
--
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
-- @module Functional.Fox -- @module Functional.Fox
-- @image Functional_FOX.png -- @image Functional_FOX.png
@@ -441,7 +437,7 @@ function FOX:SetProtectedGroupSet(groupset)
return self return self
end end
--- Add a group to the protected set. Works only with AI! --- Add a group to the protected set.
-- @param #FOX self -- @param #FOX self
-- @param Wrapper.Group#GROUP group Protected group. -- @param Wrapper.Group#GROUP group Protected group.
-- @return #FOX self -- @return #FOX self

View File

@@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: May 2024 -- Last Update: Dec 2023
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
@@ -58,7 +58,6 @@
-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled
-- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range -- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range
-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius. -- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius.
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
@@ -188,34 +187,29 @@
-- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when -- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when
-- -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of -- -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of
-- -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted. -- -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted.
-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones) -- `mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)`
-- --
-- --
-- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target: -- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target:
-- --
-- -- parameters are numbers. Defaults are 1,2,2,6 respectively -- -- parameters are numbers. Defaults are 1,2,2,6 respectively
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic) -- `mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)`
-- --
-- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range" -- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range"
-- --
-- ### 2.1.4 Advanced features -- ### 2.1.4 Advanced features
-- --
-- -- switch off auto mode **before** you start MANTIS. -- -- switch off auto mode **before** you start MANTIS.
-- mybluemantis.automode = false -- `mybluemantis.automode = false`
-- --
-- -- switch off auto shorad **before** you start MANTIS. -- -- switch off auto shorad **before** you start MANTIS.
-- mybluemantis.autoshorad = false -- `mybluemantis.autoshorad = false`
-- --
-- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below. -- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- -- also see engagerange below. -- -- also see engagerange below.
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1 -- ` self.radiusscale[MANTIS.SamType.LONG] = 1.1`
-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2 -- ` self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2`
-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3 -- ` self.radiusscale[MANTIS.SamType.SHORT] = 1.3`
--
-- ### 2.1.5 Friendlies check in firing range
--
-- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire.
-- mybluemantis.checkforfriendlies = true
-- --
-- # 3. Default settings [both modes unless stated otherwise] -- # 3. Default settings [both modes unless stated otherwise]
-- --
@@ -327,7 +321,6 @@ MANTIS = {
automode = true, automode = true,
autoshorad = true, autoshorad = true,
ShoradGroupSet = nil, ShoradGroupSet = nil,
checkforfriendlies = false,
} }
--- Advanced state enumerator --- Advanced state enumerator
@@ -354,17 +347,17 @@ MANTIS.SamType = {
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range) -- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key) -- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamData = { MANTIS.SamData = {
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km ["Hawk"] = { Range=44, Blindspot=0, Height=9, Type="Medium", Radar="Hawk" }, -- measures in km
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B ["NASAMS"] = { Range=14, Blindspot=0, Height=3, Type="Short", Radar="NSAMS" },
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot" }, ["Patriot"] = { Range=99, Blindspot=0, Height=9, Type="Long", Radar="Patriot" },
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" }, ["Rapier"] = { Range=6, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" }, ["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" }, ["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" }, ["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" }, ["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"}, ["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" }, ["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" }, ["Roland"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Roland" },
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" }, ["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" }, ["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" }, ["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
@@ -510,7 +503,6 @@ do
-- DONE: Allow tables of prefixes for the setup -- DONE: Allow tables of prefixes for the setup
-- DONE: Auto-Mode with range setups for various known SAM types. -- DONE: Auto-Mode with range setups for various known SAM types.
self.name = name or "mymantis"
self.SAM_Templates_Prefix = samprefix or "Red SAM" self.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
self.HQ_Template_CC = hq or nil self.HQ_Template_CC = hq or nil
@@ -639,7 +631,7 @@ do
-- TODO Version -- TODO Version
-- @field #string version -- @field #string version
self.version="0.8.18" self.version="0.8.16"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@@ -1230,10 +1222,10 @@ do
function MANTIS:_PreFilterHeight(height) function MANTIS:_PreFilterHeight(height)
self:T(self.lid.."_PreFilterHeight") self:T(self.lid.."_PreFilterHeight")
local set = {} local set = {}
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
local detectedgroups = dlink:GetContactTable() local detectedgroups = dlink:GetContactTable()
for _,_contact in pairs(detectedgroups) do for _,_contact in pairs(detectedgroups) do
local contact = _contact -- Ops.Intel#INTEL.Contact local contact = _contact -- Ops.Intelligence#INTEL.Contact
local grp = contact.group -- Wrapper.Group#GROUP local grp = contact.group -- Wrapper.Group#GROUP
if grp:IsAlive() then if grp:IsAlive() then
if grp:GetHeight(true) < height then if grp:GetHeight(true) < height then
@@ -1263,10 +1255,6 @@ do
-- DEBUG -- DEBUG
set = self:_PreFilterHeight(height) set = self:_PreFilterHeight(height)
end end
local friendlyset -- Core.Set#SET_GROUP
if self.checkforfriendlies == true then
friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce()
end
for _,_coord in pairs (set) do for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check local coord = _coord -- get current coord to check
-- output for cross-check -- output for cross-check
@@ -1291,16 +1279,8 @@ do
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text) self:T(self.lid..text)
end end
-- friendlies around?
local nofriendlies = true
if self.checkforfriendlies == true then
local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate)
if closestfriend and distance and distance < rad then
nofriendlies = false
end
end
-- end output to cross-check -- end output to cross-check
if targetdistance <= rad and zonecheck == true and nofriendlies == true then if targetdistance <= rad and zonecheck then
return true, targetdistance return true, targetdistance
end end
end end
@@ -1797,7 +1777,7 @@ do
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:_CheckDLinkState() function MANTIS:_CheckDLinkState()
self:T(self.lid .. "_CheckDLinkState") self:T(self.lid .. "_CheckDLinkState")
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
local TS = timer.getAbsTime() local TS = timer.getAbsTime()
if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
self.DLink = false self.DLink = false

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
-- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by Ciribob, which itself was motivated -- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by Ciribob, which itself was motivated
-- by a script by SNAFU [see here](https://forums.eagle.ru/showthread.php?t=109174). -- by a script by SNAFU [see here](https://forums.eagle.ru/showthread.php?t=109174).
-- --
-- [476th - Air Weapons Range Objects mod](https://www.476vfightergroup.com/downloads.php?do=download&downloadid=482) is highly recommended for this class. -- [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is highly recommended for this class.
-- --
-- **Main Features:** -- **Main Features:**
-- --

View File

@@ -79,7 +79,6 @@
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- * **Applevangelist**: Additional functionality, fixes.
-- * **Wingthor (TAW)**: Testing & Advice. -- * **Wingthor (TAW)**: Testing & Advice.
-- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice.
-- * **Whisper**: Testing and Advice. -- * **Whisper**: Testing and Advice.
@@ -117,13 +116,11 @@
-- Special targets can be set that will give extra scores to the players when these are destroyed. -- Special targets can be set that will give extra scores to the players when these are destroyed.
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. -- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s.
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s. -- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s.
-- Use the method @{#SCORING.AddScoreSetGroup}() to specify a special additional score for a specific @{Wrapper.Group}s gathered in a @{Core.Set#SET_GROUP}. -- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s.
-- --
-- local Scoring = SCORING:New( "Scoring File" ) -- local Scoring = SCORING:New( "Scoring File" )
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
-- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) -- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 )
-- local GroupSet = SET_GROUP:New():FilterPrefixes("RAT"):FilterStart()
-- Scoring:AddScoreSetGroup( GroupSet, 100)
-- --
-- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. -- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed.
-- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over.
@@ -229,7 +226,7 @@ SCORING = {
ClassID = 0, ClassID = 0,
Players = {}, Players = {},
AutoSave = true, AutoSave = true,
version = "1.18.4" version = "1.17.1"
} }
local _SCORINGCoalition = { local _SCORINGCoalition = {
@@ -248,15 +245,13 @@ local _SCORINGCategory = {
--- Creates a new SCORING object to administer the scoring achieved by players. --- Creates a new SCORING object to administer the scoring achieved by players.
-- @param #SCORING self -- @param #SCORING self
-- @param #string GameName The name of the game. This name is also logged in the CSV score file. -- @param #string GameName The name of the game. This name is also logged in the CSV score file.
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder.
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
-- @return #SCORING self -- @return #SCORING self
-- @usage -- @usage
-- --
-- -- Define a new scoring object for the mission Gori Valley. -- -- Define a new scoring object for the mission Gori Valley.
-- ScoringObject = SCORING:New( "Gori Valley" ) -- ScoringObject = SCORING:New( "Gori Valley" )
-- --
function SCORING:New( GameName, SavePath, AutoSave ) function SCORING:New( GameName )
-- Inherits from BASE -- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
@@ -319,8 +314,7 @@ function SCORING:New( GameName, SavePath, AutoSave )
end ) end )
-- Create the CSV file. -- Create the CSV file.
self.AutoSavePath = SavePath self.AutoSave = true
self.AutoSave = AutoSave or true
self:OpenCSV( GameName ) self:OpenCSV( GameName )
return self return self
@@ -434,31 +428,6 @@ function SCORING:AddScoreGroup( ScoreGroup, Score )
return self return self
end end
--- Specify a special additional score for a @{Core.Set#SET_GROUP}.
-- @param #SCORING self
-- @param Core.Set#SET_GROUP Set The @{Core.Set#SET_GROUP} for which each @{Wrapper.Unit} in each Group a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreSetGroup(Set, Score)
local set = Set:GetSetObjects()
for _,_group in pairs (set) do
if _group and _group:IsAlive() then
self:AddScoreGroup(_group,Score)
end
end
local function AddScore(group)
self:AddScoreGroup(group,Score)
end
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
AddScore(Object)
end
return self
end
--- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone. --- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone.
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
-- This allows for a dynamic destruction zone evolution within your mission. -- This allows for a dynamic destruction zone evolution within your mission.
@@ -1061,11 +1030,11 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1 PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown" PlayerHit.ThreatType = "Unknown"
end end
@@ -1172,7 +1141,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
@@ -1319,17 +1288,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
--self:OnKillPvP(PlayerName, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) self:OnKillPvP(Player, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
self:OnKillPvP(PlayerName, TargetPlayerName, true) self:OnKillPvP(Player, TargetPlayerName, true)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(PlayerName, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) self:OnKillPvE(Player, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@@ -1357,14 +1326,14 @@ function SCORING:_EventOnDeadOrCrash( Event )
else else
Player.PlayerKills = 1 Player.PlayerKills = 1
end end
self:OnKillPvP(PlayerName, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) self:OnKillPvP(Player, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(PlayerName, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) self:OnKillPvE(Player, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@@ -1842,11 +1811,10 @@ end
function SCORING:OpenCSV( ScoringCSV ) function SCORING:OpenCSV( ScoringCSV )
self:F( ScoringCSV ) self:F( ScoringCSV )
if lfs and io and os and self.AutoSave == true then if lfs and io and os and self.AutoSave then
if ScoringCSV then if ScoringCSV then
self.ScoringCSV = ScoringCSV self.ScoringCSV = ScoringCSV
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]] local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
local fdir = path .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
self.CSVFile, self.err = io.open( fdir, "w+" ) self.CSVFile, self.err = io.open( fdir, "w+" )
if not self.CSVFile then if not self.CSVFile then
@@ -1967,23 +1935,23 @@ end
--- Handles the event when one player kill another player --- Handles the event when one player kill another player
-- @param #SCORING self -- @param #SCORING self
-- @param #string PlayerName The attacking player -- @param #PLAYER Player the ataching player
-- @param #string TargetPlayerName The name of the killed player -- @param #string TargetPlayerName the name of the killed player
-- @param #boolean IsTeamKill true if this kill was a team kill -- @param #bool IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Threat level of the target -- @param #number TargetThreatLevel Thread level of the target
-- @param #number PlayerThreatLevel Threat level of the player -- @param #number PlayerThreatLevelThread level of the player
-- @param #number Score The score based on both threat levels -- @param #number Score The score based on both threat levels
function SCORING:OnKillPvP(PlayerName, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
end end
--- Handles the event when one player kill another player --- Handles the event when one player kill another player
-- @param #SCORING self -- @param #SCORING self
-- @param #string PlayerName The attacking player -- @param #PLAYER Player the ataching player
-- @param #string TargetUnitName the name of the killed unit -- @param #string TargetUnitName the name of the killed unit
-- @param #boolean IsTeamKill true if this kill was a team kill -- @param #bool IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Threat level of the target -- @param #number TargetThreatLevel Thread level of the target
-- @param #number PlayerThreatLevel Threat level of the player -- @param #number PlayerThreatLevelThread level of the player
-- @param #number Score The score based on both threat levels -- @param #number Score The score based on both threat levels
function SCORING:OnKillPvE(PlayerName, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
end end

View File

@@ -320,6 +320,9 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
end end
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce() local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtcoord = targetzone:GetRandomPointVec2()
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtgrp = seadset:GetRandom() local tgtgrp = seadset:GetRandom()
local _targetgroup = nil local _targetgroup = nil
local _targetgroupname = "none" local _targetgroupname = "none"

File diff suppressed because it is too large Load Diff

View File

@@ -1,590 +0,0 @@
--- **Functional** - TIRESIAS - manages AI behaviour.
--
-- ===
--
-- The @{#TIRESIAS} class is working in the back to keep your large-scale ground units in check.
--
-- ## Features:
--
-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units.
-- * Does not affect ships to keep the Navy guys happy.
-- * Does not affect OpsGroup type groups.
-- * Distinguishes between SAM groups, AAA groups and other ground groups.
-- * Exceptions can be defined to keep certain actions going.
-- * Works coalition-independent in the back
-- * Easy setup.
--
-- ===
--
-- ## Missions:
--
-- ### [TIRESIAS](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master)
--
-- ===
--
-- ### Author : **applevangelist **
--
-- @module Functional.Tiresias
-- @image Functional.Tiresias.jpg
--
-- Last Update: Dec 2023
-------------------------------------------------------------------------
--- **TIRESIAS** class, extends Core.Base#BASE
-- @type TIRESIAS
-- @field #string ClassName
-- @field #booelan debug
-- @field #string version
-- @field #number Interval
-- @field Core.Set#SET_GROUP GroundSet
-- @field #number Coalition
-- @field Core.Set#SET_GROUP VehicleSet
-- @field Core.Set#SET_GROUP AAASet
-- @field Core.Set#SET_GROUP SAMSet
-- @field Core.Set#SET_GROUP ExceptionSet
-- @field Core.Set#SET_OPSGROUP OpsGroupSet
-- @field #number AAARange
-- @field #number HeloSwitchRange
-- @field #number PlaneSwitchRange
-- @field Core.Set#SET_GROUP FlightSet
-- @field #boolean SwitchAAA
-- @extends Core.Fsm#FSM
---
-- @type TIRESIAS.Data
-- @field #string type
-- @field #number range
-- @field #boolean invisible
-- @field #boolean AIOff
-- @field #boolean exception
--- *Tiresias, Greek demi-god and shapeshifter, blinded by the Gods, works as oracle for you.* (Wiki)
--
-- ===
--
-- ## TIRESIAS Concept
--
-- * Designed to keep CPU and Network usage lower on missions with a lot of ground units.
-- * Does not affect ships to keep the Navy guys happy.
-- * Does not affect OpsGroup type groups.
-- * Distinguishes between SAM groups, AAA groups and other ground groups.
-- * Exceptions can be defined in SET_GROUP objects to keep certain actions going.
-- * Works coalition-independent in the back
-- * Easy setup.
--
-- ## Setup
--
-- Setup is a one-liner:
--
-- local blinder = TIRESIAS:New()
--
-- Optionally you can set up exceptions, e.g. for convoys driving around
--
-- local exceptionset = SET_GROUP:New():FilterCoalitions("red"):FilterPrefixes("Convoy"):FilterStart()
-- local blinder = TIRESIAS:New()
-- blinder:AddExceptionSet(exceptionset)
--
-- Options
--
-- -- Setup different radius for activation around helo and airplane groups (applies to AI and humans)
-- blinder:SetActivationRanges(10,25) -- defaults are 10, and 25
--
-- -- Setup engagement ranges for AAA (non-advanced SAM units like Flaks etc) and if you want them to be AIOff
-- blinder:SetAAARanges(60,true) -- defaults are 60, and true
--
-- @field #TIRESIAS
TIRESIAS = {
ClassName = "TIRESIAS",
debug = false,
version = "0.0.4",
Interval = 20,
GroundSet = nil,
VehicleSet = nil,
AAASet = nil,
SAMSet = nil,
ExceptionSet = nil,
AAARange = 60, -- 60%
HeloSwitchRange = 10, -- NM
PlaneSwitchRange = 25, -- NM
SwitchAAA = true,
}
--- [USER] Create a new Tiresias object and start it up.
-- @param #TIRESIAS self
-- @return #TIRESIAS self
function TIRESIAS:New()
-- Inherit everything from FSM class.
local self = BASE:Inherit(self, FSM:New()) -- #TIRESIAS
--- FSM Functions ---
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
self:AddTransition("*", "Status", "*") -- TIRESIAS status update.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self.ExceptionSet = SET_GROUP:New():Clear(false)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self.lid = string.format("TIRESIAS %s | ",self.version)
self:I(self.lid.."Managing ground groups!")
--- Triggers the FSM event "Stop". Stops TIRESIAS and all its event handlers.
-- @function [parent=#TIRESIAS] Stop
-- @param #TIRESIAS self
--- Triggers the FSM event "Stop" after a delay. Stops TIRESIAS and all its event handlers.
-- @function [parent=#TIRESIAS] __Stop
-- @param #TIRESIAS self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Start". Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance.
-- @function [parent=#TIRESIAS] Start
-- @param #TIRESIAS self
--- Triggers the FSM event "Start" after a delay. Starts TIRESIAS and all its event handlers. Note - `:New()` already starts the instance.
-- @function [parent=#TIRESIAS] __Start
-- @param #TIRESIAS self
-- @param #number delay Delay in seconds.
self:__Start(1)
return self
end
-------------------------------------------------------------------------------------------------------------
--
-- Helper Functions
--
-------------------------------------------------------------------------------------------------------------
---[USER] Set activation radius for Helos and Planes in Nautical Miles.
-- @param #TIRESIAS self
-- @param #number HeloMiles Radius around a Helicopter in which AI ground units will be activated. Defaults to 10NM.
-- @param #number PlaneMiles Radius around an Airplane in which AI ground units will be activated. Defaults to 25NM.
-- @return #TIRESIAS self
function TIRESIAS:SetActivationRanges(HeloMiles,PlaneMiles)
self.HeloSwitchRange = HeloMiles or 10
self.PlaneSwitchRange = PlaneMiles or 25
return self
end
---[USER] Set AAA Ranges - AAA equals non-SAM systems which qualify as AAA in DCS world.
-- @param #TIRESIAS self
-- @param #number FiringRange The engagement range that AAA units will be set to. Can be 0 to 100 (percent). Defaults to 60.
-- @param #boolean SwitchAAA Decide if these system will have their AI switched off, too. Defaults to true.
-- @return #TIRESIAS self
function TIRESIAS:SetAAARanges(FiringRange,SwitchAAA)
self.AAARange = FiringRange or 60
self.SwitchAAA = (SwitchAAA == false) and false or true
return self
end
--- [USER] Add a SET_GROUP of GROUP objects as exceptions. Can be done multiple times.
-- @param #TIRESIAS self
-- @param Core.Set#SET_GROUP Set to add to the exception list.
-- @return #TIRESIAS self
function TIRESIAS:AddExceptionSet(Set)
self:T(self.lid.."AddExceptionSet")
local exceptions = self.ExceptionSet
Set:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp.Tiresias = { -- #TIRESIAS.Data
type = "Exception",
exception = true,
}
exceptions:AddGroup(grp,true)
end
BASE:I("TIRESIAS: Added exception group: "..grp:GetName())
end
)
return self
end
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterNotAAA(Group)
local grp = Group -- Wrapper.Group#GROUP
local isaaa = grp:IsAAA()
if isaaa == true and grp:IsGround() and not grp:IsShip() then
return false -- remove from SET
else
return true -- keep in SET
end
end
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterNotSAM(Group)
local grp = Group -- Wrapper.Group#GROUP
local issam = grp:IsSAM()
if issam == true and grp:IsGround() and not grp:IsShip() then
return false -- remove from SET
else
return true -- keep in SET
end
end
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterAAA(Group)
local grp = Group -- Wrapper.Group#GROUP
local isaaa = grp:IsAAA()
if isaaa == true and grp:IsGround() and not grp:IsShip() then
return true -- remove from SET
else
return false -- keep in SET
end
end
--- [INTERNAL] Filter Function
-- @param Wrapper.Group#GROUP Group
-- @return #boolean isin
function TIRESIAS._FilterSAM(Group)
local grp = Group -- Wrapper.Group#GROUP
local issam = grp:IsSAM()
if issam == true and grp:IsGround() and not grp:IsShip() then
return true -- remove from SET
else
return false -- keep in SET
end
end
--- [INTERNAL] Init Groups
-- @param #TIRESIAS self
-- @return #TIRESIAS self
function TIRESIAS:_InitGroups()
self:T(self.lid.."_InitGroups")
-- Set all groups invisible/motionless
local EngageRange = self.AAARange
local SwitchAAA = self.SwitchAAA
--- AAA
self.AAASet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp:OptionEngageRange(EngageRange)
grp:SetCommandInvisible(true)
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
end
grp.Tiresias = { -- #TIRESIAS.Data
type = "AAA",
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias.invisible and grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
grp.Tiresias.invisible = true
if SwitchAAA then
grp:SetAIOff()
grp:EnableEmission(false)
grp.Tiresias.AIOff = true
end
end
end
--BASE:I(string.format("Init/Switch off AAA %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
--- Vehicles
self.VehicleSet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp:SetAIOff()
grp:SetCommandInvisible(true)
grp.Tiresias = { -- #TIRESIAS.Data
type = "Vehicle",
invisible = true,
AIOff = true,
exception = false,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
grp:SetAIOff()
grp.Tiresias.invisible = true
end
end
--BASE:I(string.format("Init/Switch off Vehicle %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
--- SAM
self.SAMSet:ForEachGroupAlive(
function(grp)
if not grp.Tiresias then
grp:SetCommandInvisible(true)
grp.Tiresias = { -- #TIRESIAS.Data
type = "SAM",
invisible = true,
exception = false,
}
end
if grp.Tiresias and (not grp.Tiresias.exception == true) then
if grp.Tiresias and grp.Tiresias.invisible and grp.Tiresias.invisible == false then
grp:SetCommandInvisible(true)
grp.Tiresias.invisible = true
end
end
--BASE:I(string.format("Init/Switch off SAM %s (Exception %s)",grp:GetName(),tostring(grp.Tiresias.exception)))
end
)
return self
end
--- [INTERNAL] Event handler function
-- @param #TIRESIAS self
-- @param Core.Event#EVENTDATA EventData
-- @return #TIRESIAS self
function TIRESIAS:_EventHandler(EventData)
self:T(string.format("%s Event = %d",self.lid, EventData.id))
local event = EventData -- Core.Event#EVENTDATA
if event.id == EVENTS.PlayerEnterAircraft or event.id == EVENTS.PlayerEnterUnit then
--local _coalition = event.IniCoalition
--if _coalition ~= self.Coalition then
-- return --ignore!
--end
local unitname = event.IniUnitName or "none"
local _unit = event.IniUnit
local _group = event.IniGroup
if _group and _group:IsAlive() then
local radius = self.PlaneSwitchRange
if _group:IsHelicopter() then
radius = self.HeloSwitchRange
end
self:_SwitchOnGroups(_group,radius)
end
end
return self
end
--- [INTERNAL] Switch Groups Behaviour
-- @param #TIRESIAS self
-- @param Wrapper.Group#GROUP group
-- @param #number radius Radius in NM
-- @return #TIRESIAS self
function TIRESIAS:_SwitchOnGroups(group,radius)
self:T(self.lid.."_SwitchOnGroups "..group:GetName().." Radius "..radius.." NM")
local zone = ZONE_GROUP:New("Zone-"..group:GetName(),group,UTILS.NMToMeters(radius))
local ground = SET_GROUP:New():FilterCategoryGround():FilterZones({zone}):FilterOnce()
local count = ground:CountAlive()
if self.debug then
local text = string.format("There are %d groups around this plane or helo!",count)
self:I(text)
end
local SwitchAAA = self.SwitchAAA
if ground:CountAlive() > 0 then
ground:ForEachGroupAlive(
function(grp)
if grp.Tiresias and grp.Tiresias.type and (not grp.Tiresias.exception == true ) then
if grp.Tiresias.invisible == true then
grp:SetCommandInvisible(false)
grp.Tiresias.invisible = false
end
if grp.Tiresias.type == "Vehicle" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then
grp:SetAIOn()
grp.Tiresias.AIOff = false
end
if SwitchAAA and grp.Tiresias.type == "AAA" and grp.Tiresias.AIOff and grp.Tiresias.AIOff == true then
grp:SetAIOn()
grp:EnableEmission(true)
grp.Tiresias.AIOff = false
end
--BASE:I(string.format("TIRESIAS - Switch on %s %s (Exception %s)",tostring(grp.Tiresias.type),grp:GetName(),tostring(grp.Tiresias.exception)))
else
BASE:E("TIRESIAS - This group has not been initialized or is an exception!")
end
end
)
end
return self
end
-------------------------------------------------------------------------------------------------------------
--
-- FSM Functions
--
-------------------------------------------------------------------------------------------------------------
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #TIRESIAS self
function TIRESIAS:onafterStart(From, Event, To)
self:T({From, Event, To})
local VehicleSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterNotAAA):FilterFunction(TIRESIAS._FilterNotSAM):FilterStart()
local AAASet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterAAA):FilterStart()
local SAMSet = SET_GROUP:New():FilterCategoryGround():FilterFunction(TIRESIAS._FilterSAM):FilterStart()
local OpsGroupSet = SET_OPSGROUP:New():FilterActive(true):FilterStart()
self.FlightSet = SET_GROUP:New():FilterCategories({"plane","helicopter"}):FilterStart()
local EngageRange = self.AAARange
local ExceptionSet = self.ExceptionSet
if self.ExceptionSet then
function ExceptionSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: EXCEPTION Object Added: "..Object:GetName())
if Object and Object:IsAlive() then
Object.Tiresias = { -- #TIRESIAS.Data
type = "Exception",
exception = true,
}
Object:SetAIOn()
Object:SetCommandInvisible(false)
Object:EnableEmission(true)
end
end
local OGS = OpsGroupSet:GetAliveSet()
for _,_OG in pairs(OGS or {}) do
local OG = _OG -- Ops.OpsGroup#OPSGROUP
local grp = OG:GetGroup()
ExceptionSet:AddGroup(grp,true)
end
function OpsGroupSet:OnAfterAdded(From,Event,To,ObjectName,Object)
local grp = Object:GetGroup()
ExceptionSet:AddGroup(grp,true)
end
end
function VehicleSet:OnAfterAdded(From,Event,To,ObjectName,Object)
BASE:I("TIRESIAS: VEHCILE Object Added: "..Object:GetName())
if Object and Object:IsAlive() then
Object:SetAIOff()
Object:SetCommandInvisible(true)
Object.Tiresias = { -- #TIRESIAS.Data
type = "Vehicle",
invisible = true,
AIOff = true,
exception = false,
}
end
end
local SwitchAAA = self.SwitchAAA
function AAASet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive() then
BASE:I("TIRESIAS: AAA Object Added: "..Object:GetName())
Object:OptionEngageRange(EngageRange)
Object:SetCommandInvisible(true)
if SwitchAAA then
Object:SetAIOff()
Object:EnableEmission(false)
end
Object.Tiresias = { -- #TIRESIAS.Data
type = "AAA",
invisible = true,
range = EngageRange,
exception = false,
AIOff = SwitchAAA,
}
end
end
function SAMSet:OnAfterAdded(From,Event,To,ObjectName,Object)
if Object and Object:IsAlive() then
BASE:I("TIRESIAS: SAM Object Added: "..Object:GetName())
Object:SetCommandInvisible(true)
Object.Tiresias = { -- #TIRESIAS.Data
type = "SAM",
invisible = true,
exception = false,
}
end
end
self.VehicleSet = VehicleSet
self.AAASet = AAASet
self.SAMSet = SAMSet
self.OpsGroupSet = OpsGroupSet
self:_InitGroups()
self:__Status(1)
return self
end
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #TIRESIAS self
function TIRESIAS:onbeforeStatus(From, Event, To)
self:T({From, Event, To})
if self:GetState() == "Stopped" then
return false
end
return self
end
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #TIRESIAS self
function TIRESIAS:onafterStatus(From, Event, To)
self:T({From, Event, To})
if self.debug then
local count = self.VehicleSet:CountAlive()
local AAAcount = self.AAASet:CountAlive()
local SAMcount = self.SAMSet:CountAlive()
local text = string.format("Overall: %d | Vehicles: %d | AAA: %d | SAM: %d",count+AAAcount+SAMcount,count,AAAcount,SAMcount)
self:I(text)
end
self:_InitGroups()
if self.FlightSet:CountAlive() > 0 then
local Set = self.FlightSet:GetAliveSet()
for _,_plane in pairs(Set) do
local plane = _plane -- Wrapper.Group#GROUP
local radius = self.PlaneSwitchRange
if plane:IsHelicopter() then
radius = self.HeloSwitchRange
end
self:_SwitchOnGroups(_plane,radius)
end
end
if self:GetState() ~= "Stopped" then
self:__Status(self.Interval)
end
return self
end
--- [INTERNAL] FSM Function
-- @param #TIRESIAS self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #TIRESIAS self
function TIRESIAS:onafterStop(From, Event, To)
self:T({From, Event, To})
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
return self
end
-------------------------------------------------------------------------------------------------------------
--
-- End
--
-------------------------------------------------------------------------------------------------------------

View File

@@ -1629,7 +1629,7 @@ WAREHOUSE = {
-- @field #boolean arrived If true, asset arrived at its destination. -- @field #boolean arrived If true, asset arrived at its destination.
-- --
-- @field #number damage Damage of asset group in percent. -- @field #number damage Damage of asset group in percent.
-- @field Ops.Airwing#AIRWING.Payload payload The payload of the asset. -- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset.
-- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object. -- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object.
-- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to. -- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to.
-- @field Ops.Legion#LEGION legion The legion this asset belonts to. -- @field Ops.Legion#LEGION legion The legion this asset belonts to.
@@ -3414,7 +3414,7 @@ end
-- FSM states -- FSM states
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the warehouse. Adds event handlers and schedules status updates of reqests and queue. --- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
@@ -3595,7 +3595,6 @@ function WAREHOUSE:onafterStatus(From, Event, To)
local Trepair=self:GetRunwayRepairtime() local Trepair=self:GetRunwayRepairtime()
self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec", Trepair)) self:I(self.lid..string.format("Runway destroyed! Will be repaired in %d sec", Trepair))
if Trepair==0 then if Trepair==0 then
self.runwaydestroyed = nil
self:RunwayRepaired() self:RunwayRepaired()
end end
end end
@@ -5394,7 +5393,6 @@ function WAREHOUSE:onafterRunwayDestroyed(From, Event, To)
self.runwaydestroyed=timer.getAbsTime() self.runwaydestroyed=timer.getAbsTime()
return self
end end
--- On after "RunwayRepaired" event. --- On after "RunwayRepaired" event.
@@ -5410,7 +5408,6 @@ function WAREHOUSE:onafterRunwayRepaired(From, Event, To)
self.runwaydestroyed=nil self.runwaydestroyed=nil
return self
end end

View File

@@ -1,4 +1,4 @@
--- **Functional (WIP)** - Base class modeling processes to achieve goals involving coalition zones. --- **Functional** - Base class that models processes to achieve goals involving a Zone for a Coalition.
-- --
-- === -- ===
-- --

View File

@@ -33,7 +33,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Spot.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/MarkerOps_Base.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/TextAndSound.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/TextAndSound.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Pathline.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/Pathline.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Core/ClientMenu.lua')
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Object.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Object.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Identifiable.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Wrapper/Identifiable.lua' )
@@ -78,12 +77,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Warehouse.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Fox.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Fox.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Mantis.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Mantis.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Shorad.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Shorad.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/AICSAR.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/AmmoTruck.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Autolase.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/ZoneGoalCargo.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Tiresias.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Functional/Stratego.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Airboss.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RecoveryTanker.lua' )
@@ -91,31 +84,6 @@ __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/RescueHelo.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/ATIS.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/ATIS.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CTLD.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CTLD.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CSAR.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/CSAR.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/AirWing.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/ArmyGroup.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Auftrag.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Awacs.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Brigade.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Chief.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Cohort.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Commander.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Fleet.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/FlightControl.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/FlightGroup.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Flotilla.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Intelligence.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Legion.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/NavyGroup.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Operation.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsGroup.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsTransport.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/OpsZone.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Platoon.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerTask.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/PlayerRecce.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Squadron.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/Target.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/Ops/EasyGCICAP.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Balancer.lua' )
__Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' ) __Moose.Include( MOOSE_DEVELOPMENT_FOLDER..'/Moose/AI/AI_Air.lua' )
@@ -154,14 +122,6 @@ __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

@@ -1,172 +0,0 @@
__Moose.Include( 'Utilities\\Enums.lua' )
__Moose.Include( 'Utilities\\Routines.lua' )
__Moose.Include( 'Utilities\\Utils.lua' )
__Moose.Include( 'Utilities\\Profiler.lua' )
__Moose.Include( 'Utilities\\Templates.lua' )
__Moose.Include( 'Utilities\\STTS.lua' )
__Moose.Include( 'Utilities\\FiFo.lua' )
__Moose.Include( 'Utilities\\Socket.lua' )
__Moose.Include( 'Core\\Base.lua' )
__Moose.Include( 'Core\\Beacon.lua' )
__Moose.Include( 'Core\\UserFlag.lua' )
__Moose.Include( 'Core\\Report.lua' )
__Moose.Include( 'Core\\Scheduler.lua' )
__Moose.Include( 'Core\\ScheduleDispatcher.lua' )
__Moose.Include( 'Core\\Event.lua' )
__Moose.Include( 'Core\\Settings.lua' )
__Moose.Include( 'Core\\Menu.lua' )
__Moose.Include( 'Core\\Zone.lua' )
__Moose.Include( 'Core\\Zone_Detection.lua' )
__Moose.Include( 'Core\\Database.lua' )
__Moose.Include( 'Core\\Set.lua' )
__Moose.Include( 'Core\\Point.lua' )
__Moose.Include( 'Core\\Velocity.lua' )
__Moose.Include( 'Core\\Message.lua' )
__Moose.Include( 'Core\\Fsm.lua' )
__Moose.Include( 'Core\\Spawn.lua' )
__Moose.Include( 'Core\\SpawnStatic.lua' )
__Moose.Include( 'Core\\Timer.lua' )
__Moose.Include( 'Core\\Goal.lua' )
__Moose.Include( 'Core\\Spot.lua' )
__Moose.Include( 'Core\\Astar.lua' )
__Moose.Include( 'Core\\MarkerOps_Base.lua' )
__Moose.Include( 'Core\\TextAndSound.lua' )
__Moose.Include( 'Core\\Condition.lua' )
__Moose.Include( 'Core\\ClientMenu.lua' )
__Moose.Include( 'Wrapper\\Object.lua' )
__Moose.Include( 'Wrapper\\Identifiable.lua' )
__Moose.Include( 'Wrapper\\Positionable.lua' )
__Moose.Include( 'Wrapper\\Controllable.lua' )
__Moose.Include( 'Wrapper\\Group.lua' )
__Moose.Include( 'Wrapper\\Unit.lua' )
__Moose.Include( 'Wrapper\\Client.lua' )
__Moose.Include( 'Wrapper\\Static.lua' )
__Moose.Include( 'Wrapper\\Airbase.lua' )
__Moose.Include( 'Wrapper\\Scenery.lua' )
__Moose.Include( 'Wrapper\\Marker.lua' )
__Moose.Include( 'Cargo\\Cargo.lua' )
__Moose.Include( 'Cargo\\CargoUnit.lua' )
__Moose.Include( 'Cargo\\CargoSlingload.lua' )
__Moose.Include( 'Cargo\\CargoCrate.lua' )
__Moose.Include( 'Cargo\\CargoGroup.lua' )
__Moose.Include( 'Functional\\Scoring.lua' )
__Moose.Include( 'Functional\\CleanUp.lua' )
__Moose.Include( 'Functional\\Movement.lua' )
__Moose.Include( 'Functional\\Sead.lua' )
__Moose.Include( 'Functional\\Escort.lua' )
__Moose.Include( 'Functional\\MissileTrainer.lua' )
__Moose.Include( 'Functional\\ATC_Ground.lua' )
__Moose.Include( 'Functional\\Detection.lua' )
__Moose.Include( 'Functional\\DetectionZones.lua' )
__Moose.Include( 'Functional\\Designate.lua' )
__Moose.Include( 'Functional\\RAT.lua' )
__Moose.Include( 'Functional\\Range.lua' )
__Moose.Include( 'Functional\\ZoneGoal.lua' )
__Moose.Include( 'Functional\\ZoneGoalCoalition.lua' )
__Moose.Include( 'Functional\\ZoneCaptureCoalition.lua' )
__Moose.Include( 'Functional\\Artillery.lua' )
__Moose.Include( 'Functional\\Suppression.lua' )
__Moose.Include( 'Functional\\PseudoATC.lua' )
__Moose.Include( 'Functional\\Warehouse.lua' )
__Moose.Include( 'Functional\\Fox.lua' )
__Moose.Include( 'Functional\\Mantis.lua' )
__Moose.Include( 'Functional\\Shorad.lua' )
__Moose.Include( 'Functional\\Autolase.lua' )
__Moose.Include( 'Functional\\AICSAR.lua' )
__Moose.Include( 'Ops\\Airboss.lua' )
__Moose.Include( 'Ops\\RecoveryTanker.lua' )
__Moose.Include( 'Ops\\RescueHelo.lua' )
__Moose.Include( 'Ops\\ATIS.lua' )
__Moose.Include( 'Ops\\Auftrag.lua' )
__Moose.Include( 'Ops\\Target.lua' )
__Moose.Include( 'Ops\\OpsGroup.lua' )
__Moose.Include( 'Ops\\FlightGroup.lua' )
__Moose.Include( 'Ops\\NavyGroup.lua' )
__Moose.Include( 'Ops\\ArmyGroup.lua' )
__Moose.Include( 'Ops\\Cohort.lua' )
__Moose.Include( 'Ops\\Squadron.lua' )
__Moose.Include( 'Ops\\Platoon.lua' )
__Moose.Include( 'Ops\\Legion.lua' )
__Moose.Include( 'Ops\\AirWing.lua' )
__Moose.Include( 'Ops\\Brigade.lua' )
__Moose.Include( 'Ops\\Intelligence.lua' )
__Moose.Include( 'Ops\\Commander.lua' )
__Moose.Include( 'Ops\\OpsTransport.lua' )
__Moose.Include( 'Ops\\CSAR.lua' )
__Moose.Include( 'Ops\\CTLD.lua' )
__Moose.Include( 'Ops\\OpsZone.lua' )
__Moose.Include( 'Ops\\Chief.lua' )
__Moose.Include( 'Ops\\Flotilla.lua' )
__Moose.Include( 'Ops\\Fleet.lua' )
__Moose.Include( 'Ops\\Awacs.lua' )
__Moose.Include( 'Ops\\PlayerTask.lua' )
__Moose.Include( 'Ops\\Operation.lua' )
__Moose.Include( 'Ops\\FlightControl.lua' )
__Moose.Include( 'AI\\AI_Balancer.lua' )
__Moose.Include( 'AI\\AI_Air.lua' )
__Moose.Include( 'AI\\AI_Air_Patrol.lua' )
__Moose.Include( 'AI\\AI_Air_Engage.lua' )
__Moose.Include( 'AI\\AI_A2A_Patrol.lua' )
__Moose.Include( 'AI\\AI_A2A_Cap.lua' )
__Moose.Include( 'AI\\AI_A2A_Gci.lua' )
__Moose.Include( 'AI\\AI_A2A_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_A2G_BAI.lua' )
__Moose.Include( 'AI\\AI_A2G_CAS.lua' )
__Moose.Include( 'AI\\AI_A2G_SEAD.lua' )
__Moose.Include( 'AI\\AI_A2G_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Patrol.lua' )
__Moose.Include( 'AI\\AI_Cap.lua' )
__Moose.Include( 'AI\\AI_Cas.lua' )
__Moose.Include( 'AI\\AI_Bai.lua' )
__Moose.Include( 'AI\\AI_Formation.lua' )
__Moose.Include( 'AI\\AI_Escort.lua' )
__Moose.Include( 'AI\\AI_Escort_Request.lua' )
__Moose.Include( 'AI\\AI_Escort_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Escort_Dispatcher_Request.lua' )
__Moose.Include( 'AI\\AI_Cargo.lua' )
__Moose.Include( 'AI\\AI_Cargo_APC.lua' )
__Moose.Include( 'AI\\AI_Cargo_Helicopter.lua' )
__Moose.Include( 'AI\\AI_Cargo_Airplane.lua' )
__Moose.Include( 'AI\\AI_Cargo_Ship.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_APC.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Helicopter.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Airplane.lua' )
__Moose.Include( 'AI\\AI_Cargo_Dispatcher_Ship.lua' )
__Moose.Include( 'Actions\\Act_Assign.lua' )
__Moose.Include( 'Actions\\Act_Route.lua' )
__Moose.Include( 'Actions\\Act_Account.lua' )
__Moose.Include( 'Actions\\Act_Assist.lua' )
__Moose.Include( 'Sound\\UserSound.lua' )
__Moose.Include( 'Sound\\SoundOutput.lua' )
__Moose.Include( 'Sound\\Radio.lua' )
__Moose.Include( 'Sound\\RadioQueue.lua' )
__Moose.Include( 'Sound\\RadioSpeech.lua' )
__Moose.Include( 'Sound\\SRS.lua' )
__Moose.Include( 'Tasking\\CommandCenter.lua' )
__Moose.Include( 'Tasking\\Mission.lua' )
__Moose.Include( 'Tasking\\Task.lua' )
__Moose.Include( 'Tasking\\TaskInfo.lua' )
__Moose.Include( 'Tasking\\Task_Manager.lua' )
__Moose.Include( 'Tasking\\DetectionManager.lua' )
__Moose.Include( 'Tasking\\Task_A2G_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_A2G.lua' )
__Moose.Include( 'Tasking\\Task_A2A_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_A2A.lua' )
__Moose.Include( 'Tasking\\Task_Cargo.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_Transport.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_CSAR.lua' )
__Moose.Include( 'Tasking\\Task_Cargo_Dispatcher.lua' )
__Moose.Include( 'Tasking\\Task_Capture_Zone.lua' )
__Moose.Include( 'Tasking\\Task_Capture_Dispatcher.lua' )
__Moose.Include( 'Globals.lua' )

File diff suppressed because it is too large Load Diff

View File

@@ -255,7 +255,6 @@
-- @field #boolean skipperUturn U-turn on/off via menu. -- @field #boolean skipperUturn U-turn on/off via menu.
-- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries. -- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries.
-- @field #number skipperTime Recovery time in min for manual recovery. -- @field #number skipperTime Recovery time in min for manual recovery.
-- @field #boolean intowindold If true, use old into wind calculation.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Be the boss! --- Be the boss!
@@ -2725,18 +2724,6 @@ function AIRBOSS:SetLSOCallInterval( TimeInterval )
return self return self
end end
--- Set if old into wind calculation is used when carrier turns into the wind for a recovery.
-- @param #AIRBOSS self
-- @param #boolean SwitchOn If `true` or `nil`, use old into wind calculation.
-- @return #AIRBOSS self
function AIRBOSS:SetIntoWindLegacy( SwitchOn )
if SwitchOn==nil then
SwitchOn=true
end
self.intowindold=SwitchOn
return self
end
--- Airboss is a rather nice guy and not strictly following the rules. Fore example, he does allow you into the landing pattern if you are not coming from the Marshal stack. --- Airboss is a rather nice guy and not strictly following the rules. Fore example, he does allow you into the landing pattern if you are not coming from the Marshal stack.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #boolean Switch If true or nil, Airboss bends the rules a bit. -- @param #boolean Switch If true or nil, Airboss bends the rules a bit.
@@ -3088,8 +3075,7 @@ function AIRBOSS:EnableSRS(PathToSRS,Port,Culture,Gender,Voice,GoogleCreds,Volum
self.SRS:SetVolume(Volume or 1) self.SRS:SetVolume(Volume or 1)
--self.SRS:SetModulations(Modulations) --self.SRS:SetModulations(Modulations)
if GoogleCreds then if GoogleCreds then
self.SRS:SetProviderOptionsGoogle(GoogleCreds,GoogleCreds) self.SRS:SetGoogle(GoogleCreds)
self.SRS:SetProvider(MSRS.Provider.GOOGLE)
end end
if Voice then if Voice then
self.SRS:SetVoice(Voice) self.SRS:SetVoice(Voice)
@@ -3655,12 +3641,6 @@ function AIRBOSS:onafterStatus( From, Event, To )
local pos = self:GetCoordinate() local pos = self:GetCoordinate()
local speed = self.carrier:GetVelocityKNOTS() local speed = self.carrier:GetVelocityKNOTS()
-- Update magnetic variation if we can get it from DCS.
if require then
self.magvar=pos:GetMagneticDeclination()
--env.info(string.format("FF magvar=%.1f", self.magvar))
end
-- Check water is ahead. -- Check water is ahead.
local collision = false -- self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) local collision = false -- self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg))
@@ -5220,7 +5200,6 @@ function AIRBOSS:_InitVoiceOvers()
TOMCAT = { file = "PILOT-Tomcat", suffix = "ogg", loud = false, subtitle = "", duration = 0.66, subduration = 5 }, TOMCAT = { file = "PILOT-Tomcat", suffix = "ogg", loud = false, subtitle = "", duration = 0.66, subduration = 5 },
HORNET = { file = "PILOT-Hornet", suffix = "ogg", loud = false, subtitle = "", duration = 0.56, subduration = 5 }, HORNET = { file = "PILOT-Hornet", suffix = "ogg", loud = false, subtitle = "", duration = 0.56, subduration = 5 },
VIKING = { file = "PILOT-Viking", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 }, VIKING = { file = "PILOT-Viking", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
GREYHOUND = { file = "PILOT-Greyhound", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
BALL = { file = "PILOT-Ball", suffix = "ogg", loud = false, subtitle = "", duration = 0.50, subduration = 5 }, BALL = { file = "PILOT-Ball", suffix = "ogg", loud = false, subtitle = "", duration = 0.50, subduration = 5 },
BINGOFUEL = { file = "PILOT-BingoFuel", suffix = "ogg", loud = false, subtitle = "", duration = 0.80 }, BINGOFUEL = { file = "PILOT-BingoFuel", suffix = "ogg", loud = false, subtitle = "", duration = 0.80 },
GASATDIVERT = { file = "PILOT-GasAtDivert", suffix = "ogg", loud = false, subtitle = "", duration = 1.80 }, GASATDIVERT = { file = "PILOT-GasAtDivert", suffix = "ogg", loud = false, subtitle = "", duration = 1.80 },
@@ -6495,7 +6474,7 @@ function AIRBOSS:_LandAI( flight )
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
Speed = UTILS.KnotsToKmph( 200 ) Speed = UTILS.KnotsToKmph( 200 )
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
Speed = UTILS.KnotsToKmph( 150 ) Speed = UTILS.KnotsToKmph( 150 )
elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
Speed = UTILS.KnotsToKmph( 175 ) Speed = UTILS.KnotsToKmph( 175 )
@@ -9775,7 +9754,7 @@ function AIRBOSS:_Groove( playerData )
local glideslopeError = groovedata.GSE local glideslopeError = groovedata.GSE
local AoA = groovedata.AoA local AoA = groovedata.AoA
if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 and playerData.unit:IsInZone( self:_GetZoneLineup() )) then if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then
-- Start time in groove -- Start time in groove
playerData.TIG0 = timer.getTime() playerData.TIG0 = timer.getTime()
@@ -11496,7 +11475,7 @@ end
--- Get wind direction and speed at carrier position. --- Get wind direction and speed at carrier position.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number alt Altitude ASL in meters. Default 18 m. -- @param #number alt Altitude ASL in meters. Default 15 m.
-- @param #boolean magnetic Direction including magnetic declination. -- @param #boolean magnetic Direction including magnetic declination.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position. -- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position.
-- @return #number Direction the wind is blowing **from** in degrees. -- @return #number Direction the wind is blowing **from** in degrees.
@@ -11568,31 +11547,10 @@ end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway. --- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
-- @param #AIRBOSS self -- @param #AIRBOSS self
-- @param #number vdeck Desired wind velocity over deck in knots.
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned. -- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position. -- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees. -- @return #number Carrier heading in degrees.
-- @return #number Carrier speed in knots to reach desired wind speed on deck. function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord )
function AIRBOSS:GetHeadingIntoWind(vdeck, magnetic, coord )
if self.intowindold then
--env.info("FF use OLD into wind")
return self:GetHeadingIntoWind_old(vdeck, magnetic, coord)
else
--env.info("FF use NEW into wind")
return self:GetHeadingIntoWind_new(vdeck, magnetic, coord)
end
end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
-- @param #AIRBOSS self
-- @param #number vdeck Desired wind velocity over deck in knots.
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees.
function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord )
local function adjustDegreesForWindSpeed(windSpeed) local function adjustDegreesForWindSpeed(windSpeed)
local degreesAdjustment = 0 local degreesAdjustment = 0
@@ -11649,13 +11607,7 @@ function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord )
intowind = intowind + 360 intowind = intowind + 360
end end
-- Wind speed. return intowind
--local _, vwind = self:GetWind()
-- Speed of carrier in m/s but at least 4 knots.
local vtot = math.max(vdeck-UTILS.MpsToKnots(vwind), 4)
return intowind, vtot
end end
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway. --- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
@@ -11666,7 +11618,7 @@ end
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position. -- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
-- @return #number Carrier heading in degrees. -- @return #number Carrier heading in degrees.
-- @return #number Carrier speed in knots to reach desired wind speed on deck. -- @return #number Carrier speed in knots to reach desired wind speed on deck.
function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord ) function AIRBOSS:GetHeadingIntoWind( vdeck, magnetic, coord )
-- Default offset angle. -- Default offset angle.
local Offset=self.carrierparam.rwyangle or 0 local Offset=self.carrierparam.rwyangle or 0
@@ -12170,18 +12122,16 @@ function AIRBOSS:_LSOgrade( playerData )
local GIC, nIC = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IC ) local GIC, nIC = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IC )
local GAR, nAR = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.AR ) local GAR, nAR = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.AR )
-- VTOL approach, which is graded differently (currently only Harrier).
local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
-- Put everything together. -- Put everything together.
local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR
-- Count number of minor/small nS, normal nN and major/large deviations nL. -- Count number of minor, normal and major deviations.
local N=nXX+nIM+nIC+nAR local N=nXX+nIM+nIC+nAR
local Nv=nXX+nIM
local nL=count(G, '_')/2 local nL=count(G, '_')/2
local nS=count(G, '%(') local nS=count(G, '%(')
local nN=N-nS-nL local nN=N-nS-nL
local nNv=Nv-nS-nL
-- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn. -- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn.
local Tgroove=playerData.Tgroove local Tgroove=playerData.Tgroove
@@ -12197,37 +12147,26 @@ function AIRBOSS:_LSOgrade( playerData )
G = "Unicorn" G = "Unicorn"
else else
if vtol then
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing -- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation. -- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
if nL > 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Normal laning part at the beginning -- Larger deviations ==> "No grade" 2.0 points.
local Gb = GXX .. " " .. GIM
-- Number of deviations that occurred at the the beginning of the landing (XX or IM). These are graded like in non-VTOL landings, i.e. on deviations is
local N=nXX+nIM
local nL=count(Gb, '_')/2
local nS=count(Gb, '%(')
local nN=N-nS-nL
-- VTOL part of the landing
local Gv = GIC .. " " .. GAR
-- Number of deviations that occurred at the the end (VTOL part) of the landing (IC or AR).
local Nv=nIC+nAR
local nLv=count(Gv, '_')/2
local nSv=count(Gv, '%(')
local nNv=Nv-nSv-nLv
if nL>0 or nLv>1 then
-- Larger deviations at XX or IM or at least one larger deviation IC or AR==> "No grade" 2.0 points.
-- In other words, we allow one larger deviation at IC+AR
grade="--" grade="--"
points=2.0 points=2.0
elseif nN>0 or nNv>1 or nLv==1 then elseif nNv >= 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Average deviations at XX+IM or more than one normal deviation IC or AR ==> "Fair Pass" Pass with average deviations and corrections. -- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
elseif nNv < 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Only minor average deviations ==> "OK" Pass with minor deviations and corrections. (test nNv<=1 and)
grade="OK"
points=4.0
elseif nL > 0 then
-- Larger deviations ==> "No grade" 2.0 points.
grade="--"
points=2.0
elseif nN> 0 then
-- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)" grade="(OK)"
points=3.0 points=3.0
else else
@@ -12236,25 +12175,6 @@ function AIRBOSS:_LSOgrade( playerData )
points=4.0 points=4.0
end end
else
-- This is a normal (non-VTOL) landing.
if nL > 0 then
-- Larger deviations ==> "No grade" 2.0 points.
grade="--"
points=2.0
elseif nN> 0 then
-- No larger but average/normal deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
else
-- Only minor corrections ==> "Okay pass" 4.0 points.
grade="OK"
points=4.0
end
end
end end
-- Replace" )"( and "__" -- Replace" )"( and "__"
@@ -14327,8 +14247,6 @@ function AIRBOSS:_GetACNickname( actype )
nickname = "Harrier" nickname = "Harrier"
elseif actype == AIRBOSS.AircraftCarrier.E2D then elseif actype == AIRBOSS.AircraftCarrier.E2D then
nickname = "Hawkeye" nickname = "Hawkeye"
elseif actype == AIRBOSS.AircraftCarrier.C2A then
nickname = "Greyhound"
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then
nickname = "Tomcat" nickname = "Tomcat"
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
@@ -14366,15 +14284,11 @@ function AIRBOSS:_GetOnboardNumbers( group, playeronly )
-- Debug text. -- Debug text.
local text = string.format( "Onboard numbers of group %s:", groupname ) local text = string.format( "Onboard numbers of group %s:", groupname )
local template=group:GetTemplate()
local numbers = {}
if template then
-- Units of template group. -- Units of template group.
local units = template.units local units = group:GetTemplate().units
-- Get numbers. -- Get numbers.
local numbers = {}
for _, unit in pairs( units ) do for _, unit in pairs( units ) do
-- Onboard number and unit name. -- Onboard number and unit name.
@@ -14397,25 +14311,6 @@ function AIRBOSS:_GetOnboardNumbers( group, playeronly )
-- Debug info. -- Debug info.
self:T2( self.lid .. text ) self:T2( self.lid .. text )
else
if playeronly then
return 101
else
local units=group:GetUnits()
for i,_unit in pairs(units) do
local name=_unit:GetName()
numbers[name]=100+i
end
end
end
return numbers return numbers
end end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,621 +0,0 @@
--- **Ops** - Brigade Warehouse.
--
-- **Main Features:**
--
-- * Manage platoons
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
-- * Define rearming zones
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/Brigade).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Brigade
-- @image OPS_Brigade_.png
--- BRIGADE class.
-- @type BRIGADE
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
-- @extends Ops.Legion#LEGION
--- *I am not afraid of an Army of lions lead by a sheep; I am afraid of sheep lead by a lion* -- Alexander the Great
--
-- ===
--
-- # The BRIGADE Concept
--
-- A BRIGADE consists of one or multiple PLATOONs. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
--
--
-- @field #BRIGADE
BRIGADE = {
ClassName = "BRIGADE",
verbose = 0,
rearmingZones = {},
refuellingZones = {},
}
--- Supply Zone.
-- @type BRIGADE.SupplyZone
-- @field Core.Zone#ZONE zone The zone.
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
-- @field #boolean markerOn If `true`, marker is on.
-- @field Wrapper.Marker#MARKER marker F10 marker.
--- BRIGADE class version.
-- @field #string version
BRIGADE.version="0.1.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Spawn when hosting warehouse is a ship or oil rig or gas platform.
-- TODO: Rearming zones.
-- TODO: Retreat zones.
-- DONE: Add weapon range.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new BRIGADE class object.
-- @param #BRIGADE self
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
-- @param #string BrigadeName Name of the brigade.
-- @return #BRIGADE self
function BRIGADE:New(WarehouseName, BrigadeName)
-- Inherit everything from LEGION class.
local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE
-- Nil check.
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
return nil
end
-- Set some string id for output to DCS.log file.
self.lid=string.format("BRIGADE %s | ", self.alias)
-- Defaults
self:SetRetreatZones()
-- Turn ship into NAVYGROUP.
if self:IsShip() then
local wh=self.warehouse --Wrapper.Unit#UNIT
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG).
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the BRIGADE. Initializes parameters and starts event handlers.
-- @function [parent=#BRIGADE] Start
-- @param #BRIGADE self
--- Triggers the FSM event "Start" after a delay. Starts the BRIGADE. Initializes parameters and starts event handlers.
-- @function [parent=#BRIGADE] __Start
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the BRIGADE and all its event handlers.
-- @param #BRIGADE self
--- Triggers the FSM event "Stop" after a delay. Stops the BRIGADE and all its event handlers.
-- @function [parent=#BRIGADE] __Stop
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "ArmyOnMission".
-- @function [parent=#BRIGADE] ArmyOnMission
-- @param #BRIGADE self
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "ArmyOnMission" after a delay.
-- @function [parent=#BRIGADE] __ArmyOnMission
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- On after "ArmyOnMission" event.
-- @function [parent=#BRIGADE] OnAfterArmyOnMission
-- @param #BRIGADE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a platoon to the brigade.
-- @param #BRIGADE self
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
-- @return #BRIGADE self
function BRIGADE:AddPlatoon(Platoon)
-- Add platoon to brigade.
table.insert(self.cohorts, Platoon)
-- Add assets to platoon.
self:AddAssetToPlatoon(Platoon, Platoon.Ngroups)
-- Set brigade of platoon.
Platoon:SetBrigade(self)
-- Start platoon.
if Platoon:IsStopped() then
Platoon:Start()
end
return self
end
--- Add asset group(s) to platoon.
-- @param #BRIGADE self
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
-- @param #number Nassets Number of asset groups to add.
-- @return #BRIGADE self
function BRIGADE:AddAssetToPlatoon(Platoon, Nassets)
if Platoon then
-- Get the template group of the platoon.
local Group=GROUP:FindByName(Platoon.templatename)
if Group then
-- Debug text.
local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name)
self:T(self.lid..text)
-- Add assets to airwing warehouse.
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Platoon does not exit!")
end
return self
end
--- Define a set of retreat zones.
-- @param #BRIGADE self
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
-- @return #BRIGADE self
function BRIGADE:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
--- Add a retreat zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
-- @return #BRIGADE self
function BRIGADE:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
--- Get retreat zones.
-- @param #BRIGADE self
-- @return Core.Set#SET_ZONE Set of retreat zones.
function BRIGADE:GetRetreatZones()
return self.retreatZones
end
--- Add a rearming zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
-- @return #BRIGADE.SupplyZone The rearming zone data.
function BRIGADE:AddRearmingZone(RearmingZone)
local rearmingzone={} --#BRIGADE.SupplyZone
rearmingzone.zone=RearmingZone
rearmingzone.mission=nil
rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition())
table.insert(self.rearmingZones, rearmingzone)
return rearmingzone
end
--- Add a refuelling zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RefuellingZone Refuelling zone.
-- @return #BRIGADE.SupplyZone The refuelling zone data.
function BRIGADE:AddRefuellingZone(RefuellingZone)
local supplyzone={} --#BRIGADE.SupplyZone
supplyzone.zone=RefuellingZone
supplyzone.mission=nil
supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition())
table.insert(self.refuellingZones, supplyzone)
return supplyzone
end
--- Get platoon by name.
-- @param #BRIGADE self
-- @param #string PlatoonName Name of the platoon.
-- @return Ops.Platoon#PLATOON The Platoon object.
function BRIGADE:GetPlatoon(PlatoonName)
local platoon=self:_GetCohort(PlatoonName)
return platoon
end
--- Get platoon of an asset.
-- @param #BRIGADE self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
-- @return Ops.Platoon#PLATOON The platoon object.
function BRIGADE:GetPlatoonOfAsset(Asset)
local platoon=self:GetPlatoon(Asset.squadname)
return platoon
end
--- Remove asset from platoon.
-- @param #BRIGADE self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
function BRIGADE:RemoveAssetFromPlatoon(Asset)
local platoon=self:GetPlatoonOfAsset(Asset)
if platoon then
platoon:DelAsset(Asset)
end
end
--- [ GROUND ] Function to load back an asset in the field that has been filed before.
-- @param #BRIGADE self
-- @param #string Templatename e.g."1 PzDv LogRg I\_AID-976" - that's the alias (name) of an platoon spawned as `"platoon - alias"_AID-"asset-ID"`
-- @param Core.Point#COORDINATE Position where to spawn the platoon
-- @return #BRIGADE self
-- @usage
-- Prerequisites:
-- Save the assets spawned by BRIGADE/CHIEF regularly (~every 5 mins) into a file, e.g. like this:
--
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- local BlueSaveOps = SET_OPSGROUP:New():FilterCoalitions("blue"):FilterCategoryGround():FilterOnce()
-- UTILS.SaveSetOfOpsGroups(BlueSaveOps,Path,BlueOpsFilename)
--
-- where Path and Filename are strings, as chosen by you.
-- You can then load back the assets at the start of your next mission run. Be aware that it takes a couple of seconds for the
-- platoon data to arrive in brigade, so make this an action after ~20 seconds, e.g. like so:
--
-- function LoadBackAssets()
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then
-- local loadback = UTILS.LoadSetOfOpsGroups(Path,BlueOpsFilename,false)
-- for _,_platoondata in pairs (loadback) do
-- local groupname = _platoondata.groupname -- #string
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE
-- Your_Brigade:LoadBackAssetInPosition(groupname,coordinate)
-- end
-- end
-- end
--
-- local AssetLoader = TIMER:New(LoadBackAssets)
-- AssetLoader:Start(20)
--
-- The assets loaded back into the mission will be considered for AUFTRAG type missions from CHIEF and BRIGADE.
function BRIGADE:LoadBackAssetInPosition(Templatename,Position)
self:T(self.lid .. "LoadBackAssetInPosition: " .. tostring(Templatename))
-- get Platoon alias from Templatename
local nametbl = UTILS.Split(Templatename,"_")
local name = nametbl[1]
self:T(string.format("*** Target Platoon = %s ***",name))
-- find a matching asset table from BRIGADE
local cohorts = self.cohorts or {}
local thisasset = nil --Functional.Warehouse#WAREHOUSE.Assetitem
local found = false
for _,_cohort in pairs(cohorts) do
local asset = _cohort:GetName()
self:T(string.format("*** Looking at Platoon = %s ***",asset))
if asset == name then
self:T("**** Found Platoon ****")
local cohassets = _cohort.assets or {}
for _,_zug in pairs (cohassets) do
local zug = _zug -- Functional.Warehouse#WAREHOUSE.Assetitem
if zug.assignment == name and zug.requested == false then
self:T("**** Found Asset ****")
found = true
thisasset = zug --Functional.Warehouse#WAREHOUSE.Assetitem
break
end
end
end
end
if found then
-- prep asset
thisasset.rid = thisasset.uid
thisasset.requested = false
thisasset.score=100
thisasset.missionTask="CAS"
thisasset.spawned = true
local template = thisasset.templatename
local alias = thisasset.spawngroupname
-- Spawn group
local spawnasset = SPAWN:NewWithAlias(template,alias)
:InitDelayOff()
:SpawnFromCoordinate(Position)
-- build a new self request
local request = {} --Functional.Warehouse#WAREHOUSE.Pendingitem
request.assignment = name
request.warehouse = self
request.assets = {thisasset}
request.ntransporthome = 0
request.ndelivered = 0
request.ntransport = 0
request.cargoattribute = thisasset.attribute
request.category = thisasset.category
request.cargoassets = {thisasset}
request.assetdesc = WAREHOUSE.Descriptor.ASSETLIST
request.cargocategory = thisasset.category
request.toself = true
request.transporttype = WAREHOUSE.TransportType.SELFPROPELLED
request.assetproblem = {}
request.born = true
request.prio = 50
request.uid = thisasset.uid
request.airbase = nil
request.timestamp = timer.getAbsTime()
request.assetdescval = {thisasset}
request.nasset = 1
request.cargogroupset = SET_GROUP:New()
request.cargogroupset:AddGroup(spawnasset)
request.iscargo = true
-- Call Brigade self
self:__AssetSpawned(2, spawnasset, thisasset, request)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start BRIGADE FSM.
-- @param #BRIGADE self
function BRIGADE:onafterStart(From, Event, To)
-- Start parent Warehouse.
self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version))
end
--- Update status.
-- @param #BRIGADE self
function BRIGADE:onafterStatus(From, Event, To)
-- Status of parent Warehouse.
self:GetParent(self).onafterStatus(self, From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
----------------
-- Transport ---
----------------
self:CheckTransportQueue()
--------------
-- Mission ---
--------------
-- Check if any missions should be cancelled.
self:CheckMissionQueue()
---------------------
-- Rearming Zones ---
---------------------
for _,_rearmingzone in pairs(self.rearmingZones) do
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
self:AddMission(rearmingzone.mission)
end
end
-----------------------
-- Refuelling Zones ---
-----------------------
-- Check refuelling zones.
for _,_supplyzone in pairs(self.refuellingZones) do
local supplyzone=_supplyzone --#BRIGADE.SupplyZone
-- Check if mission is nil or over.
if (not supplyzone.mission) or supplyzone.mission:IsOver() then
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
self:AddMission(supplyzone.mission)
end
end
-----------
-- Info ---
-----------
-- General info:
if self.verbose>=1 then
-- Count missions not over yet.
local Nmissions=self:CountMissionsInQueue()
-- Asset count.
local Npq, Np, Nq=self:CountAssetsOnMission()
-- Asset string.
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
-- Output.
local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
self:I(self.lid..text)
end
------------------
-- Mission Info --
------------------
if self.verbose>=2 then
local text=string.format("Missions Total=%d:", #self.missionqueue)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
end
self:I(self.lid..text)
end
--------------------
-- Transport Info --
--------------------
if self.verbose>=2 then
local text=string.format("Transports Total=%d:", #self.transportqueue)
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
end
self:I(self.lid..text)
end
-------------------
-- Platoon Info --
-------------------
if self.verbose>=3 then
local text="Platoons:"
for i,_platoon in pairs(self.cohorts) do
local platoon=_platoon --Ops.Platoon#PLATOON
local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName) or "N/A"
local modex=platoon.modex and platoon.modex or -1
local skill=platoon.skill and tostring(platoon.skill) or "N/A"
-- Platoon text.
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", platoon.name, platoon:GetState(), platoon.aircrafttype, platoon:CountAssets(true), #platoon.assets, callsign, modex, skill)
end
self:I(self.lid..text)
end
-------------------
-- Rearming Info --
-------------------
if self.verbose>=4 then
local text="Rearming Zones:"
for i,_rearmingzone in pairs(self.rearmingZones) do
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
-- Info text.
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", rearmingzone.zone:GetName(), rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
---------------------
-- Refuelling Info --
---------------------
if self.verbose>=4 then
local text="Refuelling Zones:"
for i,_refuellingzone in pairs(self.refuellingZones) do
local refuellingzone=_refuellingzone --#BRIGADE.SupplyZone
-- Info text.
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
----------------
-- Asset Info --
----------------
if self.verbose>=5 then
local text="Assets in stock:"
for i,_asset in pairs(self.stock) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
-- Info text.
text=text..string.format("\n* %s: spawned=%s", asset.spawngroupname, tostring(asset.spawned))
end
self:I(self.lid..text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ArmyOnMission".
-- @param #BRIGADE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission)
-- Debug info.
self:T(self.lid..string.format("Group %s on %s mission %s", ArmyGroup:GetName(), Mission:GetType(), Mission:GetName()))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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 April 2024 -- Last: Update Dec 2024
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -290,13 +290,10 @@ CSAR.AircraftType["Bell-47"] = 2
CSAR.AircraftType["UH-60L"] = 10 CSAR.AircraftType["UH-60L"] = 10
CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2
CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH58D"] = 2
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.24" CSAR.version="1.0.19"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@@ -465,7 +462,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 = MSRS.Voices.Google.Standard.en_GB_Standard_B self.SRSVoice = nil
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
@@ -736,7 +733,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
:NewWithAlias(template,alias) :NewWithAlias(template,alias)
:InitCoalition(coalition) :InitCoalition(coalition)
:InitCountry(country) :InitCountry(country)
--:InitAIOnOff(pilotcacontrol) :InitAIOnOff(pilotcacontrol)
:InitDelayOff() :InitDelayOff()
:SpawnFromCoordinate(point) :SpawnFromCoordinate(point)
@@ -1192,7 +1189,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,true) self:_ScheduledSARFlight(_event.IniUnitName,_event.IniGroupName,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
@@ -1240,24 +1237,10 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla
if not _nomessage then if not _nomessage then
if _freq ~= 0 then --shagrat if _freq ~= 0 then --shagrat
local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute' local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute'
if self.coordtype ~= 2 then --not MGRS
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk)
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
else --shagrat CASEVAC msg else --shagrat CASEVAC msg
local _text = string.format("Pickup Zone at %s.", _coordinatesText ) local _text = string.format("Pickup Zone at %s.", _coordinatesText )
if self.coordtype ~= 2 then --not MGRS
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
local _text = string.format("Pickup Zone at %s.", coordtext )
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
end
end end
end end
@@ -1545,7 +1528,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)
@@ -1554,16 +1537,14 @@ 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()
@@ -1574,15 +1555,11 @@ 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
@@ -1590,32 +1567,28 @@ 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
@@ -1623,7 +1596,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
@@ -1633,28 +1606,22 @@ 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
@@ -1679,8 +1646,7 @@ 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
-- @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)
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)
@@ -1700,29 +1666,20 @@ function CSAR:_ScheduledSARFlight(heliname,groupname, isairport, noreschedule)
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
if not noreschedule then self:__Returning(-5,heliname,_woundedGroupName, isairport)
self:__Returning(5,heliname,_woundedGroupName, isairport)
self:ScheduleOnce(5,self._ScheduledSARFlight,self,heliname,groupname, isairport, noreschedule)
end
return self return self
end end
@@ -1794,7 +1751,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
@@ -1963,28 +1920,23 @@ end
-- @param #string _message Message to display. -- @param #string _message Message to display.
-- @param #number _side Coalition of message. -- @param #number _side Coalition of message.
-- @param #number _messagetime How long to show. -- @param #number _messagetime How long to show.
-- @param #boolean ToSRS If true or nil, send to SRS TTS function CSAR:_DisplayToAllSAR(_message, _side, _messagetime)
-- @param #boolean ToScreen If true or nil, send to Screen
function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
self:T(self.lid .. " _DisplayToAllSAR") self:T(self.lid .. " _DisplayToAllSAR")
local messagetime = _messagetime or self.messageTime local messagetime = _messagetime or self.messageTime
self:T({_message,ToSRS=ToSRS,ToScreen=ToScreen}) if self.msrs then
if self.msrs and (ToSRS == true or ToSRS == nil) then
local voice = self.CSARVoice or MSRS.Voices.Google.Standard.en_GB_Standard_F local voice = self.CSARVoice or MSRS.Voices.Google.Standard.en_GB_Standard_F
if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then
voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
end end
self:F("Voice = "..voice) self:I("Voice = "..voice)
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate) self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
end end
if ToScreen == true or ToScreen == nil then
for _, _unitName in pairs(self.csarUnits) do for _, _unitName in pairs(self.csarUnits) do
local _unit = self:_GetSARHeli(_unitName) local _unit = self:_GetSARHeli(_unitName)
if _unit and not self.suppressmessages then if _unit and not self.suppressmessages then
self:_DisplayMessageToSAR(_unit, _message, _messagetime) self:_DisplayMessageToSAR(_unit, _message, _messagetime)
end end
end end
end
return self return self
end end
@@ -2028,7 +1980,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
-- @return #CSAR self -- @retunr
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
@@ -2266,7 +2218,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.name}) self:T({_pilot})
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
@@ -2548,7 +2500,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 April 2024 -- Last Update December 2023
do do
@@ -44,8 +44,6 @@ do
-- @field #number PerCrateMass Mass in kg. -- @field #number PerCrateMass Mass in kg.
-- @field #number Stock Number of builds available, -1 for unlimited. -- @field #number Stock Number of builds available, -1 for unlimited.
-- @field #string Subcategory Sub-category name. -- @field #string Subcategory Sub-category name.
-- @field #boolean DontShowInMenu Show this item in menu or not.
-- @field Core.Zone#ZONE Location Location (if set) where to get this cargo item.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- ---
@@ -64,8 +62,6 @@ CTLD_CARGO = {
PerCrateMass = 0, PerCrateMass = 0,
Stock = nil, Stock = nil,
Mark = nil, Mark = nil,
DontShowInMenu = false,
Location = nil,
} }
--- Define cargo types. --- Define cargo types.
@@ -101,10 +97,8 @@ CTLD_CARGO = {
-- @param #number PerCrateMass Mass in kg -- @param #number PerCrateMass Mass in kg
-- @param #number Stock Number of builds available, nil for unlimited -- @param #number Stock Number of builds available, nil for unlimited
-- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load.
-- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it).
-- @param Core.Zone#ZONE Location (optional) Where the cargo is available (one location only).
-- @return #CTLD_CARGO self -- @return #CTLD_CARGO self
function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory, DontShowInMenu, Location) function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
-- Inherit everything from BASE class. -- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
@@ -121,21 +115,9 @@ CTLD_CARGO = {
self.Stock = Stock or nil --#number self.Stock = Stock or nil --#number
self.Mark = nil self.Mark = nil
self.Subcategory = Subcategory or "Other" self.Subcategory = Subcategory or "Other"
self.DontShowInMenu = DontShowInMenu or false
if type(Location) == "string" then
Location = ZONE:New(Location)
end
self.Location = Location
return self return self
end end
--- Query Location.
-- @param #CTLD_CARGO self
-- @return Core.Zone#ZONE location or `nil` if not set
function CTLD_CARGO:GetLocation()
return self.Location
end
--- Query ID. --- Query ID.
-- @param #CTLD_CARGO self -- @param #CTLD_CARGO self
-- @return #number ID -- @return #number ID
@@ -674,8 +656,6 @@ do
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775) -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775)
-- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock. -- -- if you want to limit your stock, add a number (here: 10) as parameter after weight. No parameter / nil means unlimited stock.
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10) -- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10)
-- -- additionally, you can limit **where** the stock is available (one location only!) - this one is available in a zone called "Vehicle Store".
-- my_ctld:AddCratesCargo("Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,2,2775,10,nil,nil,"Vehicle Store")
-- --
-- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build: -- -- add infantry unit called "Forward Ops Base" using template "FOB", of type FOB, size 4, i.e. needs four crates to be build:
-- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4) -- my_ctld:AddCratesCargo("Forward Ops Base",{"FOB"},CTLD_CARGO.Enum.FOB,4)
@@ -777,9 +757,6 @@ do
-- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000},
-- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500},
-- ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200},
-- ["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
-- ["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
-- ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
-- --
-- ### 2.1.2 Activate and deactivate zones -- ### 2.1.2 Activate and deactivate zones
-- --
@@ -1245,17 +1222,13 @@ CTLD.UnitTypeCapabilities = {
["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
--Actually it's longer, but the center coord is off-center of the model. --Actually it's longer, but the center coord is off-center of the model.
["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550},
["OH58D"] = {type="OH58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400},
} }
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.54" CTLD.version="1.0.45"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@@ -2242,9 +2215,7 @@ 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)
local TNow = timer.getTime() if distance <= extractdistance and distance ~= -1 then
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
@@ -2264,7 +2235,7 @@ end
local secondarygroups = {} local secondarygroups = {}
for i=1,#distancekeys do for i=1,#distancekeys do
local nearestGroup = nearestList[distancekeys[i]] -- Wrapper.Group#GROUP local nearestGroup = nearestList[distancekeys[i]]
-- find matching cargo type -- find matching cargo type
local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$") local groupType = string.match(nearestGroup:GetName(), "(.+)-(.+)$")
local Cargotype = nil local Cargotype = nil
@@ -2295,38 +2266,25 @@ 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
loaded.Troopsloaded = loaded.Troopsloaded + troopsize loaded.Troopsloaded = loaded.Troopsloaded + troopsize
table.insert(loaded.Cargo,loadcargotype) table.insert(loaded.Cargo,loadcargotype)
self.Loaded_Cargo[unitname] = loaded self.Loaded_Cargo[unitname] = loaded
self:ScheduleOnce(running,self._SendMessage,self,"Troops boarded!", 10, false, Group) self:_SendMessage("Troops boarded!", 10, false, Group)
self:_SendMessage("Troops boarding!", 10, false, Group)
self:_UpdateUnitCargoMass(Unit) self:_UpdateUnitCargoMass(Unit)
self:__TroopsExtracted(running,Group, Unit, nearestGroup) self:__TroopsExtracted(1,Group, Unit, nearestGroup)
local coord = Unit:GetCoordinate() or Group:GetCoordinate() -- Core.Point#COORDINATE
local Point
if coord then
local heading = unit:GetHeading() or 0
local Angle = math.floor((heading+160)%360)
Point = coord:Translate(8,Angle):GetVec2()
if Point then
nearestGroup:RouteToVec2(Point,4)
end
end
-- clean up: -- clean up:
if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then if type(Cargotype.Templates) == "table" and Cargotype.Templates[2] then
for _,_key in pairs (Cargotype.Templates) do for _,_key in pairs (Cargotype.Templates) do
table.insert(secondarygroups,_key) table.insert(secondarygroups,_key)
end end
end end
nearestGroup:Destroy(false,running) nearestGroup:Destroy(false)
end end
end end
end end
@@ -2336,7 +2294,7 @@ end
if _group and _group:IsAlive() then if _group and _group:IsAlive() then
local groupname = string.match(_group:GetName(), "(.+)-(.+)$") local groupname = string.match(_group:GetName(), "(.+)-(.+)$")
if _name == groupname then if _name == groupname then
_group:Destroy(false,15) _group:Destroy(false)
end end
end end
end end
@@ -2393,20 +2351,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack)
if not self.debug then return self end if not self.debug then return self end
end end
-- Check cargo location if available
local location = Cargo:GetLocation()
if location then
local unitcoord = Unit:GetCoordinate() or Group:GetCoordinate()
if unitcoord then
if not location:IsCoordinateInZone(unitcoord) then
-- no we're not at the right spot
self:_SendMessage("The requested cargo is not available in this zone!", 10, false, Group)
if not self.debug then return self end
end
end
end
-- avoid crate spam -- avoid crate spam
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
local canloadcratesno = capabilities.cratelimit local canloadcratesno = capabilities.cratelimit
@@ -3080,10 +3024,9 @@ end
function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
local Positions = {} local Positions = {}
local template = _DATABASE:GetGroupTemplate(Template) local template = _DATABASE:GetGroupTemplate(Template)
--UTILS.PrintTableToLog(template) UTILS.PrintTableToLog(template)
local numbertroops = #template.units local numbertroops = #template.units
local slightshift = math.abs(math.random(0,200)/100) local newcenter = Coordinate:Translate(Radius,((Heading+270)%360))
local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270)%360))
for i=1,360,math.floor(360/numbertroops) do for i=1,360,math.floor(360/numbertroops) do
local phead = ((Heading+270+i)%360) local phead = ((Heading+270+i)%360)
local post = newcenter:Translate(Radius,phead) local post = newcenter:Translate(Radius,phead)
@@ -3095,7 +3038,7 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
} }
table.insert(Positions,p1t) table.insert(Positions,p1t)
end end
--UTILS.PrintTableToLog(Positions) UTILS.PrintTableToLog(Positions)
return Positions return Positions
end end
@@ -3609,7 +3552,7 @@ function CTLD:_MoveGroupToZone(Group)
local groupcoord = Group:GetCoordinate() local groupcoord = Group:GetCoordinate()
-- Get closest zone of type -- Get closest zone of type
local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE) local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then if (distance <= self.movetroopsdistance) and zone then
-- yes, we can ;) -- yes, we can ;)
local groupname = Group:GetName() local groupname = Group:GetName()
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
@@ -3757,22 +3700,16 @@ function CTLD:_RefreshF10Menus()
for _,_entry in pairs(self.Cargo_Troops) do for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
local noshow = entry.DontShowInMenu
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry)
end end
end
else else
for _,_entry in pairs(self.Cargo_Troops) do for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local noshow = entry.DontShowInMenu
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry)
end end
end end
end
local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh()
local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh() local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh()
end end
@@ -3791,61 +3728,33 @@ function CTLD:_RefreshF10Menus()
for _,_entry in pairs(self.Cargo_Crates) do for _,_entry in pairs(self.Cargo_Crates) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
local noshow = entry.DontShowInMenu
local zone = entry.Location
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
if zone then
menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
end
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
end end
end
for _,_entry in pairs(self.Cargo_Statics) do for _,_entry in pairs(self.Cargo_Statics) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
local noshow = entry.DontShowInMenu
local zone = entry.Location
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
if zone then
menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
end
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
end end
end
else else
for _,_entry in pairs(self.Cargo_Crates) do for _,_entry in pairs(self.Cargo_Crates) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local noshow = entry.DontShowInMenu
local zone = entry.Location
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
if zone then
menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
end
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
end end
end
for _,_entry in pairs(self.Cargo_Statics) do for _,_entry in pairs(self.Cargo_Statics) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local noshow = entry.DontShowInMenu
local zone = entry.Location
if not noshow then
menucount = menucount + 1 menucount = menucount + 1
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
if zone then
menutext = string.format("Crate %s (%dkg)[R]",entry.Name,entry.PerCrateMass or 0)
end
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
end end
end end
end
listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit)
local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit)
local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit)
if not self.nobuildmenu then if not self.nobuildmenu then
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
@@ -3920,9 +3829,7 @@ end
-- @param #number PerCrateMass Mass in kg of each crate -- @param #number PerCrateMass Mass in kg of each crate
-- @param #number Stock Number of buildable groups in stock. Nil for unlimited. -- @param #number Stock Number of buildable groups in stock. Nil for unlimited.
-- @param #string SubCategory Name of sub-category (optional). -- @param #string SubCategory Name of sub-category (optional).
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory)
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location)
self:T(self.lid .. " AddCratesCargo") self:T(self.lid .. " AddCratesCargo")
if not self:_CheckTemplates(Templates) then if not self:_CheckTemplates(Templates) then
self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" ) self:E(self.lid .. "Crates Cargo for " .. Name .. " has missing template(s)!" )
@@ -3930,7 +3837,7 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub
end end
self.CargoCounter = self.CargoCounter + 1 self.CargoCounter = self.CargoCounter + 1
-- Crates are not directly loadable -- Crates are not directly loadable
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory)
table.insert(self.Cargo_Crates,cargo) table.insert(self.Cargo_Crates,cargo)
return self return self
end end
@@ -3941,15 +3848,13 @@ end
-- @param #number Mass Mass in kg of each static in kg, e.g. 100. -- @param #number Mass Mass in kg of each static in kg, e.g. 100.
-- @param #number Stock Number of groups in stock. Nil for unlimited. -- @param #number Stock Number of groups in stock. Nil for unlimited.
-- @param #string SubCategory Name of sub-category (optional). -- @param #string SubCategory Name of sub-category (optional).
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory)
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location)
self:T(self.lid .. " AddStaticsCargo") self:T(self.lid .. " AddStaticsCargo")
self.CargoCounter = self.CargoCounter + 1 self.CargoCounter = self.CargoCounter + 1
local type = CTLD_CARGO.Enum.STATIC local type = CTLD_CARGO.Enum.STATIC
local template = STATIC:FindByName(Name,true):GetTypeName() local template = STATIC:FindByName(Name,true):GetTypeName()
-- Crates are not directly loadable -- Crates are not directly loadable
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location) local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory)
table.insert(self.Cargo_Statics,cargo) table.insert(self.Cargo_Statics,cargo)
return self return self
end end
@@ -3979,9 +3884,7 @@ end
-- @param #number PerCrateMass Mass in kg of each crate -- @param #number PerCrateMass Mass in kg of each crate
-- @param #number Stock Number of groups in stock. Nil for unlimited. -- @param #number Stock Number of groups in stock. Nil for unlimited.
-- @param #string SubCategory Name of the sub-category (optional). -- @param #string SubCategory Name of the sub-category (optional).
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu. function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory)
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,SubCategory,DontShowInMenu,Location)
self:T(self.lid .. " AddCratesRepair") self:T(self.lid .. " AddCratesRepair")
if not self:_CheckTemplates(Template) then if not self:_CheckTemplates(Template) then
self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" ) self:E(self.lid .. "Repair Cargo for " .. Name .. " has a missing template!" )
@@ -3989,7 +3892,7 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su
end end
self.CargoCounter = self.CargoCounter + 1 self.CargoCounter = self.CargoCounter + 1
-- Crates are not directly loadable -- Crates are not directly loadable
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory,DontShowInMenu,Location) local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Template,Type,false,false,NoCrates,nil,nil,PerCrateMass,Stock,SubCategory)
table.insert(self.Cargo_Crates,cargo) table.insert(self.Cargo_Crates,cargo)
return self return self
end end
@@ -4466,9 +4369,10 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
zonewidth = zoneradius zonewidth = zoneradius
end end
local distance = self:_GetDistance(zonecoord,unitcoord) local distance = self:_GetDistance(zonecoord,unitcoord)
self:T("Distance Zone: "..distance) if zone:IsVec2InZone(unitVec2) and active then
if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then
outcome = true outcome = true
end
if maxdist > distance then
maxdist = distance maxdist = distance
zoneret = zone zoneret = zone
zonenameret = zonename zonenameret = zonename

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,425 +0,0 @@
--- **Ops** - Fleet Warehouse.
--
-- **Main Features:**
--
-- * Manage flotillas
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Ops/Fleet).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Fleet
-- @image OPS_Fleet.png
--- FLEET class.
-- @type FLEET
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
-- @field #boolean pathfinding Set pathfinding on for all spawned navy groups.
-- @extends Ops.Legion#LEGION
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The FLEET Concept
--
-- A FLEET consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
--
-- # Basic Setup
--
-- A new `FLEET` object can be created with the @{#FLEET.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
--
-- myFleet=FLEET:New("myWarehouseName", "1st Fleet")
-- myFleet:SetPortZone(ZonePort1stFleet)
-- myFleet:Start()
--
-- A fleet needs a *port zone*, which is set via the @{#FLEET.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
--
-- Finally, the fleet needs to be started using the @{#FLEET.Start}() function. If the fleet is not started, it will not process any requests.
--
-- ## Adding Flotillas
--
-- Flotillas can be added via the @{#FLEET.AddFlotilla}(`Flotilla`) function. See @{Ops.Flotilla#FLOTILLA} for how to create a flotilla.
--
-- myFleet:AddFlotilla(FlotillaTiconderoga)
-- myFleet:AddFlotilla(FlotillaPerry)
--
--
--
-- @field #FLEET
FLEET = {
ClassName = "FLEET",
verbose = 0,
pathfinding = false,
}
--- Supply Zone.
-- @type FLEET.SupplyZone
-- @field Core.Zone#ZONE zone The zone.
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
-- @field #boolean markerOn If `true`, marker is on.
-- @field Wrapper.Marker#MARKER marker F10 marker.
--- FLEET class version.
-- @field #string version
FLEET.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add routes?
-- DONE: Add weapon range.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLEET class object.
-- @param #FLEET self
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
-- @param #string FleetName Name of the fleet.
-- @return #FLEET self
function FLEET:New(WarehouseName, FleetName)
-- Inherit everything from LEGION class.
local self=BASE:Inherit(self, LEGION:New(WarehouseName, FleetName)) -- #FLEET
-- Nil check.
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
return nil
end
-- Set some string id for output to DCS.log file.
self.lid=string.format("FLEET %s | ", self.alias)
-- Defaults
self:SetRetreatZones()
-- Turn ship into NAVYGROUP.
if self:IsShip() then
local wh=self.warehouse --Wrapper.Unit#UNIT
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "NavyOnMission", "*") -- An NAVYGROUP was send on a Mission (AUFTRAG).
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the FLEET. Initializes parameters and starts event handlers.
-- @function [parent=#FLEET] Start
-- @param #FLEET self
--- Triggers the FSM event "Start" after a delay. Starts the FLEET. Initializes parameters and starts event handlers.
-- @function [parent=#FLEET] __Start
-- @param #FLEET self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the FLEET and all its event handlers.
-- @param #FLEET self
--- Triggers the FSM event "Stop" after a delay. Stops the FLEET and all its event handlers.
-- @function [parent=#FLEET] __Stop
-- @param #FLEET self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "NavyOnMission".
-- @function [parent=#FLEET] NavyOnMission
-- @param #FLEET self
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "NavyOnMission" after a delay.
-- @function [parent=#FLEET] __NavyOnMission
-- @param #FLEET self
-- @param #number delay Delay in seconds.
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- On after "NavyOnMission" event.
-- @function [parent=#FLEET] OnAfterNavyOnMission
-- @param #FLEET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a flotilla to the fleet.
-- @param #FLEET self
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
-- @return #FLEET self
function FLEET:AddFlotilla(Flotilla)
-- Add flotilla to fleet.
table.insert(self.cohorts, Flotilla)
-- Add assets to flotilla.
self:AddAssetToFlotilla(Flotilla, Flotilla.Ngroups)
-- Set fleet of flotilla.
Flotilla:SetFleet(self)
-- Start flotilla.
if Flotilla:IsStopped() then
Flotilla:Start()
end
return self
end
--- Add asset group(s) to flotilla.
-- @param #FLEET self
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
-- @param #number Nassets Number of asset groups to add.
-- @return #FLEET self
function FLEET:AddAssetToFlotilla(Flotilla, Nassets)
if Flotilla then
-- Get the template group of the flotilla.
local Group=GROUP:FindByName(Flotilla.templatename)
if Group then
-- Debug text.
local text=string.format("Adding asset %s to flotilla %s", Group:GetName(), Flotilla.name)
self:T(self.lid..text)
-- Add assets to airwing warehouse.
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Flotilla.skill, Flotilla.livery, Flotilla.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Flotilla does not exit!")
end
return self
end
--- Set pathfinding for all spawned naval groups.
-- @param #FLEET self
-- @param #boolean Switch If `true`, pathfinding is used.
-- @return #FLEET self
function FLEET:SetPathfinding(Switch)
self.pathfinding=Switch
return self
end
--- Define a set of retreat zones.
-- @param #FLEET self
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
-- @return #FLEET self
function FLEET:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
--- Add a retreat zone.
-- @param #FLEET self
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
-- @return #FLEET self
function FLEET:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
--- Get retreat zones.
-- @param #FLEET self
-- @return Core.Set#SET_ZONE Set of retreat zones.
function FLEET:GetRetreatZones()
return self.retreatZones
end
--- Get flotilla by name.
-- @param #FLEET self
-- @param #string FlotillaName Name of the flotilla.
-- @return Ops.Flotilla#FLOTILLA The Flotilla object.
function FLEET:GetFlotilla(FlotillaName)
local flotilla=self:_GetCohort(FlotillaName)
return flotilla
end
--- Get flotilla of an asset.
-- @param #FLEET self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
-- @return Ops.Flotilla#FLOTILLA The flotilla object.
function FLEET:GetFlotillaOfAsset(Asset)
local flotilla=self:GetFlotilla(Asset.squadname)
return flotilla
end
--- Remove asset from flotilla.
-- @param #FLEET self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
function FLEET:RemoveAssetFromFlotilla(Asset)
local flotilla=self:GetFlotillaOfAsset(Asset)
if flotilla then
flotilla:DelAsset(Asset)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start FLEET FSM.
-- @param #FLEET self
function FLEET:onafterStart(From, Event, To)
-- Start parent Warehouse.
self:GetParent(self, FLEET).onafterStart(self, From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting FLEET v%s", FLEET.version))
end
--- Update status.
-- @param #FLEET self
function FLEET:onafterStatus(From, Event, To)
-- Status of parent Warehouse.
self:GetParent(self).onafterStatus(self, From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
----------------
-- Transport ---
----------------
self:CheckTransportQueue()
--------------
-- Mission ---
--------------
-- Check if any missions should be cancelled.
self:CheckMissionQueue()
-----------
-- Info ---
-----------
-- General info:
if self.verbose>=1 then
-- Count missions not over yet.
local Nmissions=self:CountMissionsInQueue()
-- Asset count.
local Npq, Np, Nq=self:CountAssetsOnMission()
-- Asset string.
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
-- Output.
local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
self:I(self.lid..text)
end
------------------
-- Mission Info --
------------------
if self.verbose>=2 then
local text=string.format("Missions Total=%d:", #self.missionqueue)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
end
self:I(self.lid..text)
end
--------------------
-- Transport Info --
--------------------
if self.verbose>=2 then
local text=string.format("Transports Total=%d:", #self.transportqueue)
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
end
self:I(self.lid..text)
end
-------------------
-- Flotilla Info --
-------------------
if self.verbose>=3 then
local text="Flotillas:"
for i,_flotilla in pairs(self.cohorts) do
local flotilla=_flotilla --Ops.Flotilla#FLOTILLA
local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName) or "N/A"
local modex=flotilla.modex and flotilla.modex or -1
local skill=flotilla.skill and tostring(flotilla.skill) or "N/A"
-- Flotilla text.
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", flotilla.name, flotilla:GetState(), flotilla.aircrafttype, flotilla:CountAssets(true), #flotilla.assets, callsign, modex, skill)
end
self:I(self.lid..text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "NavyOnMission".
-- @param #FLEET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
function FLEET:onafterNavyOnMission(From, Event, To, NavyGroup, Mission)
-- Debug info.
self:T(self.lid..string.format("Group %s on %s mission %s", NavyGroup:GetName(), Mission:GetType(), Mission:GetName()))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,175 +0,0 @@
--- **Ops** - Flotilla is a small naval group belonging to a fleet.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all flotilla members.
-- * Define mission types, this flotilla can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause flotilla operations.
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Flotilla
-- @image OPS_Flotilla.png
--- FLOTILLA class.
-- @type FLOTILLA
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
-- @extends Ops.Cohort#COHORT
--- *No captain can do very wrong if he places his ship alongside that of an enemy.* -- Horation Nelson
--
-- ===
--
-- # The FLOTILLA Concept
--
-- A FLOTILLA is an essential part of a FLEET.
--
--
--
-- @field #FLOTILLA
FLOTILLA = {
ClassName = "FLOTILLA",
verbose = 0,
weaponData = {},
}
--- FLOTILLA class version.
-- @field #string version
FLOTILLA.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLOTILLA object and start the FSM.
-- @param #FLOTILLA self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this flotilla. Default 3.
-- @param #string FlotillaName Name of the flotilla. Must be **unique**!
-- @return #FLOTILLA self
function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
-- Inherit everything from COHORT class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, FlotillaName)) -- #FLOTILLA
-- All flotillas get mission type Nothing.
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
-- Is naval.
self.isNaval=true
-- Get initial ammo.
self.ammo=self:_CheckAmmo()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Flotilla specific user functions.
--- Set fleet of this flotilla.
-- @param #FLOTILLA self
-- @param Ops.Fleet#FLEET Fleet The fleet.
-- @return #FLOTILLA self
function FLOTILLA:SetFleet(Fleet)
self.legion=Fleet
return self
end
--- Get fleet of this flotilla.
-- @param #FLOTILLA self
-- @return Ops.Fleet#FLEET The fleet.
function FLOTILLA:GetFleet()
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #FLOTILLA self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLOTILLA:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
self:I(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #FLOTILLA self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLOTILLA:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:T(self.lid..text)
-- Weapon data info.
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData) do
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,175 +0,0 @@
--- **Ops** - Brigade Platoon.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all platoon members.
-- * Define modex and callsigns.
-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause platoon operations.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Platoon
-- @image OPS_Platoon.png
--- PLATOON class.
-- @type PLATOON
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
-- @extends Ops.Cohort#COHORT
--- *Some cool cohort quote* -- Known Author
--
-- ===
--
-- # The PLATOON Concept
--
-- A PLATOON is essential part of an BRIGADE.
--
--
--
-- @field #PLATOON
PLATOON = {
ClassName = "PLATOON",
verbose = 0,
weaponData = {},
}
--- PLATOON class version.
-- @field #string version
PLATOON.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new PLATOON object and start the FSM.
-- @param #PLATOON self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this platoon. Default 3.
-- @param #string PlatoonName Name of the platoon. Must be **unique**!
-- @return #PLATOON self
function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
-- Inherit everything from COHORT class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON
-- All platoons get mission type Nothing.
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
-- Is ground.
self.isGround=true
-- Get ammo.
self.ammo=self:_CheckAmmo()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Platoon specific user functions.
--- Set brigade of this platoon.
-- @param #PLATOON self
-- @param Ops.Brigade#BRIGADE Brigade The brigade.
-- @return #PLATOON self
function PLATOON:SetBrigade(Brigade)
self.legion=Brigade
return self
end
--- Get brigade of this platoon.
-- @param #PLATOON self
-- @return Ops.Brigade#BRIGADE The brigade.
function PLATOON:GetBrigade()
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--[[
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #PLATOON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function PLATOON:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
self:I(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
]]
--- On after "Status" event.
-- @param #PLATOON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function PLATOON:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:T(self.lid..text)
-- Weapon data info.
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData) do
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Misc functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,324 +0,0 @@
--- **Ops** - Airwing Squadron.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all squadron members.
-- * Define modex and callsigns.
-- * Define mission types, this squadron can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause squadron operations.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Squadron
-- @image OPS_Squadron.png
--- SQUADRON class.
-- @type SQUADRON
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the squadron.
-- @field #string templatename Name of the template group.
-- @field #string aircrafttype Type of the airframe the squadron is using.
-- @field Wrapper.Group#GROUP templategroup Template group.
-- @field #number ngrouping User defined number of units in the asset group.
-- @field #table assets Squadron assets.
-- @field #table missiontypes Capabilities (mission types and performances) of the squadron.
-- @field #number fuellow Low fuel threshold.
-- @field #boolean fuellowRefuel If `true`, flight tries to refuel at the nearest tanker.
-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight.
-- @field #number repairtime Time in seconds for each
-- @field #string livery Livery of the squadron.
-- @field #number skill Skill of squadron members.
-- @field #number modex Modex.
-- @field #number modexcounter Counter to incease modex number for assets.
-- @field #string callsignName Callsign name.
-- @field #number callsigncounter Counter to increase callsign names for new assets.
-- @field #number Ngroups Number of asset flight groups this squadron has.
-- @field #number engageRange Mission range in meters.
-- @field #string attribute Generalized attribute of the squadron template group.
-- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil.
-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probe=1). Default nil.
-- @field #table tacanChannel List of TACAN channels available to the squadron.
-- @field #number radioFreq Radio frequency in MHz the squad uses.
-- @field #number radioModu Radio modulation the squad uses.
-- @field #string takeoffType Take of type.
-- @field #table parkingIDs Parking IDs for this squadron.
-- @field #boolean despawnAfterLanding Aircraft are despawned after landing.
-- @field #boolean despawnAfterHolding Aircraft are despawned after holding.
-- @extends Ops.Cohort#COHORT
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance* -- Adolf Galland
--
-- ===
--
-- # The SQUADRON Concept
--
-- A SQUADRON is essential part of an @{Ops.Airwing#AIRWING} and consists of **one** type of aircraft.
--
--
--
-- @field #SQUADRON
SQUADRON = {
ClassName = "SQUADRON",
verbose = 0,
modex = nil,
modexcounter = 0,
callsignName = nil,
callsigncounter= 11,
tankerSystem = nil,
refuelSystem = nil,
}
--- SQUADRON class version.
-- @field #string version
SQUADRON.version="0.8.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DONE: Parking spots for squadrons?
-- DONE: Engage radius.
-- DONE: Modex.
-- DONE: Call signs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new SQUADRON object and start the FSM.
-- @param #SQUADRON self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this squadron. Default 3.
-- @param #string SquadronName Name of the squadron, e.g. "VFA-37". Must be **unique**!
-- @return #SQUADRON self
function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, SquadronName)) -- #SQUADRON
-- Everyone can ORBIT.
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
-- Is air.
self.isAir=true
-- Refueling system.
self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable())
self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker())
------------------------
--- Pseudo Functions ---
------------------------
-- See COHORT class
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set number of units in groups.
-- @param #SQUADRON self
-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2.
-- @return #SQUADRON self
function SQUADRON:SetGrouping(nunits)
self.ngrouping=nunits or 2
if self.ngrouping<1 then self.ngrouping=1 end
if self.ngrouping>4 then self.ngrouping=4 end
return self
end
--- Set valid parking spot IDs. Assets of this squad are only allowed to be spawned at these parking spots. **Note** that the IDs are different from the ones displayed in the mission editor!
-- @param #SQUADRON self
-- @param #table ParkingIDs Table of parking ID numbers or a single `#number`.
-- @return #SQUADRON self
function SQUADRON:SetParkingIDs(ParkingIDs)
if type(ParkingIDs)~="table" then
ParkingIDs={ParkingIDs}
end
self.parkingIDs=ParkingIDs
return self
end
--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines.
-- Spawning on runways is not supported.
-- @param #SQUADRON self
-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on or "Air" for spawning in air.
-- @return #SQUADRON self
function SQUADRON:SetTakeoffType(TakeoffType)
TakeoffType=TakeoffType or "Cold"
if TakeoffType:lower()=="hot" then
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
elseif TakeoffType:lower()=="cold" then
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
elseif TakeoffType:lower()=="air" then
self.takeoffType=COORDINATE.WaypointType.TurningPoint
else
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
end
return self
end
--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold).
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffCold()
self:SetTakeoffType("Cold")
return self
end
--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot).
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffHot()
self:SetTakeoffType("Hot")
return self
end
--- Set takeoff type air. All assets of this squadron will be spawned in air above the airbase.
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffAir()
self:SetTakeoffType("Air")
return self
end
--- Set despawn after landing. Aircraft will be despawned after the landing event.
-- Can help to avoid DCS AI taxiing issues.
-- @param #SQUADRON self
-- @param #boolean Switch If `true` (default), activate despawn after landing.
-- @return #SQUADRON self
function SQUADRON:SetDespawnAfterLanding(Switch)
if Switch then
self.despawnAfterLanding=Switch
else
self.despawnAfterLanding=true
end
return self
end
--- Set despawn after holding. Aircraft will be despawned when they arrive at their holding position at the airbase.
-- Can help to avoid DCS AI taxiing issues.
-- @param #SQUADRON self
-- @param #boolean Switch If `true` (default), activate despawn after holding.
-- @return #SQUADRON self
function SQUADRON:SetDespawnAfterHolding(Switch)
if Switch then
self.despawnAfterHolding=Switch
else
self.despawnAfterHolding=true
end
return self
end
--- Set low fuel threshold.
-- @param #SQUADRON self
-- @param #number LowFuel Low fuel threshold in percent. Default 25.
-- @return #SQUADRON self
function SQUADRON:SetFuelLowThreshold(LowFuel)
self.fuellow=LowFuel or 25
return self
end
--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker.
-- @param #SQUADRON self
-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off.
-- @return #SQUADRON self
function SQUADRON:SetFuelLowRefuel(switch)
if switch==false then
self.fuellowRefuel=false
else
self.fuellowRefuel=true
end
return self
end
--- Set airwing.
-- @param #SQUADRON self
-- @param Ops.Airwing#AIRWING Airwing The airwing.
-- @return #SQUADRON self
function SQUADRON:SetAirwing(Airwing)
self.legion=Airwing
return self
end
--- Get airwing.
-- @param #SQUADRON self
-- @return Ops.Airwing#AIRWING The airwing.
function SQUADRON:GetAirwing(Airwing)
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting SQUADRON", self.name)
self:T(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local modex=self.modex and self.modex or -1
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:I(self.lid..text)
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -1,260 +0,0 @@
--
--
-- ### 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

@@ -1,85 +0,0 @@
---
--
-- ### 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

@@ -1,333 +0,0 @@
---
--
-- ### 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

@@ -1,213 +0,0 @@
---
--
-- ### 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

@@ -1,458 +0,0 @@
---
--
-- ### 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

@@ -1,214 +0,0 @@
--- **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

@@ -1,101 +0,0 @@
--- 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

@@ -30,10 +30,6 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/Radio)
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Sound.Radio -- @module Sound.Radio

View File

@@ -14,7 +14,7 @@
-- --
-- === -- ===
-- --
-- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/MSRS). -- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Sound/MSRS).
-- --
-- === -- ===
-- --
@@ -824,7 +824,7 @@ function MSRS:SetVoiceProvider(Voice, Provider)
self:F( {Voice=Voice, Provider=Provider} ) self:F( {Voice=Voice, Provider=Provider} )
self.poptions=self.poptions or {} self.poptions=self.poptions or {}
self.poptions[Provider or self:GetProvider()].voice=Voice self.poptions[Provider or self:GetProvider()]=Voice
return self return self
end end

View File

@@ -175,19 +175,19 @@ do -- TASK_A2G
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Set#SET_UNIT TargetSetUnit The set of targets. -- @param Core.Set#SET_UNIT TargetSetUnit The set of targets.
function TASK_A2G:SetTargetSetUnit( TargetSetUnit ) function TASK_A2G:SetTargetSetUnit( TargetSetUnit )
self.TargetSetUnit = TargetSetUnit self.TargetSetUnit = TargetSetUnit
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
function TASK_A2G:GetPlannedMenuText() function TASK_A2G:GetPlannedMenuText()
return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )" return self:GetStateString() .. " - " .. self:GetTaskName() .. " ( " .. self.TargetSetUnit:GetUnitTypesText() .. " )"
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @param Core.Point#COORDINATE RendezVousCoordinate The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @param #number RendezVousRange The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
@@ -200,7 +200,7 @@ do -- TASK_A2G
ActRouteRendezVous:SetRange( RendezVousRange ) ActRouteRendezVous:SetRange( RendezVousRange )
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map. -- @return Core.Point#COORDINATE The Coordinate object referencing to the 2D point where the RendezVous point is located on the map.
-- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point. -- @return #number The RendezVousRange that defines when the player is considered to have arrived at the RendezVous point.
@@ -212,7 +212,7 @@ do -- TASK_A2G
return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange() return ActRouteRendezVous:GetCoordinate(), ActRouteRendezVous:GetRange()
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map. -- @param Core.Zone#ZONE_BASE RendezVousZone The Zone object where the RendezVous is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit ) function TASK_A2G:SetRendezVousZone( RendezVousZone, TaskUnit )
@@ -223,7 +223,7 @@ do -- TASK_A2G
ActRouteRendezVous:SetZone( RendezVousZone ) ActRouteRendezVous:SetZone( RendezVousZone )
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map. -- @return Core.Zone#ZONE_BASE The Zone object where the RendezVous is located on the map.
function TASK_A2G:GetRendezVousZone( TaskUnit ) function TASK_A2G:GetRendezVousZone( TaskUnit )
@@ -234,7 +234,7 @@ do -- TASK_A2G
return ActRouteRendezVous:GetZone() return ActRouteRendezVous:GetZone()
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map. -- @param Core.Point#COORDINATE TargetCoordinate The Coordinate object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit ) function TASK_A2G:SetTargetCoordinate( TargetCoordinate, TaskUnit )
@@ -245,7 +245,7 @@ do -- TASK_A2G
ActRouteTarget:SetCoordinate( TargetCoordinate ) ActRouteTarget:SetCoordinate( TargetCoordinate )
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map. -- @return Core.Point#COORDINATE The Coordinate object where the Target is located on the map.
function TASK_A2G:GetTargetCoordinate( TaskUnit ) function TASK_A2G:GetTargetCoordinate( TaskUnit )
@@ -256,7 +256,7 @@ do -- TASK_A2G
return ActRouteTarget:GetCoordinate() return ActRouteTarget:GetCoordinate()
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map. -- @param Core.Zone#ZONE_BASE TargetZone The Zone object where the Target is located on the map.
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
function TASK_A2G:SetTargetZone( TargetZone, TaskUnit ) function TASK_A2G:SetTargetZone( TargetZone, TaskUnit )
@@ -267,7 +267,7 @@ do -- TASK_A2G
ActRouteTarget:SetZone( TargetZone ) ActRouteTarget:SetZone( TargetZone )
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
-- @param Wrapper.Unit#UNIT TaskUnit -- @param Wrapper.Unit#UNIT TaskUnit
-- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map. -- @return Core.Zone#ZONE_BASE The Zone object where the Target is located on the map.
function TASK_A2G:GetTargetZone( TaskUnit ) function TASK_A2G:GetTargetZone( TaskUnit )
@@ -280,7 +280,7 @@ do -- TASK_A2G
function TASK_A2G:SetGoalTotal() function TASK_A2G:SetGoalTotal()
self.GoalTotal = self.TargetSetUnit:CountAlive() self.GoalTotal = self.TargetSetUnit:Count()
end end
function TASK_A2G:GetGoalTotal() function TASK_A2G:GetGoalTotal()
@@ -304,14 +304,14 @@ do -- TASK_A2G
function TASK_A2G:onafterGoal( TaskUnit, From, Event, To ) function TASK_A2G:onafterGoal( TaskUnit, From, Event, To )
local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT local TargetSetUnit = self.TargetSetUnit -- Core.Set#SET_UNIT
if TargetSetUnit:CountAlive() == 0 then if TargetSetUnit:Count() == 0 then
self:Success() self:Success()
end end
self:__Goal( -10 ) self:__Goal( -10 )
end end
-- @param #TASK_A2G self --- @param #TASK_A2G self
function TASK_A2G:UpdateTaskInfo( DetectedItem ) function TASK_A2G:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then if self:IsStatePlanned() or self:IsStateAssigned() then
@@ -328,7 +328,7 @@ do -- TASK_A2G
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true ) self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 10, "MOD", true )
if self.Detection then if self.Detection then
local DetectedItemsCount = self.TargetSetUnit:CountAlive() local DetectedItemsCount = self.TargetSetUnit:Count()
local ReportTypes = REPORT:New() local ReportTypes = REPORT:New()
local TargetTypes = {} local TargetTypes = {}
for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do for TargetUnitName, TargetUnit in pairs( self.TargetSetUnit:GetSet() ) do
@@ -341,7 +341,7 @@ do -- TASK_A2G
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, ReportTypes:Text( ", " ), 20, "D", true )
else else
local DetectedItemsCount = self.TargetSetUnit:CountAlive() local DetectedItemsCount = self.TargetSetUnit:Count()
local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames() local DetectedItemsTypes = self.TargetSetUnit:GetTypeNames()
self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true ) self.TaskInfo:AddTargetCount( DetectedItemsCount, 11, "O", true )
self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true ) self.TaskInfo:AddTargets( DetectedItemsCount, DetectedItemsTypes, 20, "D", true )

View File

@@ -55,7 +55,6 @@ BIGSMOKEPRESET = {
-- @field #string MarianaIslands Mariana Islands map. -- @field #string MarianaIslands Mariana Islands map.
-- @field #string Falklands South Atlantic map. -- @field #string Falklands South Atlantic map.
-- @field #string Sinai Sinai map. -- @field #string Sinai Sinai map.
-- @field #string Kola Kola map.
DCSMAP = { DCSMAP = {
Caucasus="Caucasus", Caucasus="Caucasus",
NTTR="Nevada", NTTR="Nevada",
@@ -65,8 +64,7 @@ DCSMAP = {
Syria="Syria", Syria="Syria",
MarianaIslands="MarianaIslands", MarianaIslands="MarianaIslands",
Falklands="Falklands", Falklands="Falklands",
Sinai="SinaiMap", Sinai="SinaiMap"
Kola="Kola"
} }
@@ -446,11 +444,10 @@ end
--- Print a table to log in a nice format --- Print a table to log in a nice format
-- @param #table table The table to print -- @param #table table The table to print
-- @param #number indent Number of indents -- @param #number indent Number of indents
-- @param #boolean noprint Don't log but return text
-- @return #string text Text created on the fly of the log output -- @return #string text Text created on the fly of the log output
function UTILS.PrintTableToLog(table, indent, noprint) function UTILS.PrintTableToLog(table, indent)
local text = "\n" local text = "\n"
if not table or type(table) ~= "table" then if not table then
env.warning("No table passed!") env.warning("No table passed!")
return nil return nil
end end
@@ -458,16 +455,11 @@ function UTILS.PrintTableToLog(table, indent, noprint)
for k, v in pairs(table) do for k, v in pairs(table) do
if string.find(k," ") then k='"'..k..'"'end if string.find(k," ") then k='"'..k..'"'end
if type(v) == "table" then if type(v) == "table" then
if not noprint then
env.info(string.rep(" ", indent) .. tostring(k) .. " = {") env.info(string.rep(" ", indent) .. tostring(k) .. " = {")
end
text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n" text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n"
text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n" text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n"
if not noprint then
env.info(string.rep(" ", indent) .. "},") env.info(string.rep(" ", indent) .. "},")
end
text = text .. string.rep(" ", indent) .. "},\n" text = text .. string.rep(" ", indent) .. "},\n"
elseif type(v) == "function" then
else else
local value local value
if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then
@@ -475,9 +467,7 @@ function UTILS.PrintTableToLog(table, indent, noprint)
else else
value = '"'..tostring(v)..'"' value = '"'..tostring(v)..'"'
end end
if not noprint then
env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n") env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n")
end
text = text .. string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n" text = text .. string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n"
end end
end end
@@ -835,64 +825,6 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
end end
end end
--[[acc:
in DM: decimal point of minutes.
In DMS: decimal point of seconds.
position after the decimal of the least significant digit:
So:
42.32 - acc of 2.
]]
UTILS.tostringLLM2KData = function( lat, lon, acc)
local latHemi, lonHemi
if lat > 0 then
latHemi = 'N'
else
latHemi = 'S'
end
if lon > 0 then
lonHemi = 'E'
else
lonHemi = 'W'
end
lat = math.abs(lat)
lon = math.abs(lon)
local latDeg = math.floor(lat)
local latMin = (lat - latDeg)*60
local lonDeg = math.floor(lon)
local lonMin = (lon - lonDeg)*60
-- degrees, decimal minutes.
latMin = UTILS.Round(latMin, acc)
lonMin = UTILS.Round(lonMin, acc)
if latMin == 60 then
latMin = 0
latDeg = latDeg + 1
end
if lonMin == 60 then
lonMin = 0
lonDeg = lonDeg + 1
end
local minFrmtStr -- create the formatting string for the minutes place
if acc <= 0 then -- no decimal place.
minFrmtStr = '%02d'
else
local width = 3 + acc -- 01.310 - that's a width of 6, for example.
minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end
-- 024 23'N or 024 23.123'N
return latHemi..string.format('%02d:', latDeg) .. string.format(minFrmtStr, latMin), lonHemi..string.format('%02d:', lonDeg) .. string.format(minFrmtStr, lonMin)
end
-- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5. -- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1 UTILS.tostringMGRS = function(MGRS, acc) --R2.1
@@ -1705,7 +1637,6 @@ end
-- * Mariana Islands +2 (East) -- * Mariana Islands +2 (East)
-- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole -- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole
-- * Sinai +4.8 (East) -- * Sinai +4.8 (East)
-- * Kola +15 (East) - not there is a lot of deviation across the map (-1° to +24°), as we are close to the North pole
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre -- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees. -- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map) function UTILS.GetMagneticDeclination(map)
@@ -1732,8 +1663,6 @@ function UTILS.GetMagneticDeclination(map)
declination=12 declination=12
elseif map==DCSMAP.Sinai then elseif map==DCSMAP.Sinai then
declination=4.8 declination=4.8
elseif map==DCSMAP.Kola then
declination=15
else else
declination=0 declination=0
end end
@@ -1951,8 +1880,6 @@ function UTILS.GMTToLocalTimeDifference()
return -3 -- Fireland is UTC-3 hours. return -3 -- Fireland is UTC-3 hours.
elseif theatre==DCSMAP.Sinai then elseif theatre==DCSMAP.Sinai then
return 2 -- Currently map is +2 but should be +3 (DCS bug?) return 2 -- Currently map is +2 but should be +3 (DCS bug?)
elseif theatre==DCSMAP.Kola then
return 3 -- Currently map is +2 but should be +3 (DCS bug?)
else else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0 return 0
@@ -2244,16 +2171,6 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true -- no doors on this one ;) return true -- no doors on this one ;)
end end
if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then
BASE:T(unit_name .. " cargo door is open")
return true
end
if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then
BASE:T(unit_name .. " cargo door is open")
return true
end
return false return false
end -- nil end -- nil
@@ -2484,7 +2401,7 @@ function UTILS.SaveToFile(Path,Filename,Data)
return true return true
end end
--- Function to load an object from a file. --- Function to save an object to a file
-- @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.
-- @param #string Filename The name of the file. -- @param #string Filename The name of the file.
-- @return #boolean outcome True if reading is possible and successful, else false. -- @return #boolean outcome True if reading is possible and successful, else false.
@@ -2661,9 +2578,6 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
if group and group:IsAlive() then if group and group:IsAlive() then
local name = group:GetName() local name = group:GetName()
local template = string.gsub(name,"-(.+)$","") local template = string.gsub(name,"-(.+)$","")
if string.find(name,"AID") then
template = string.gsub(name,"(.AID.%d+$","")
end
if string.find(template,"#") then if string.find(template,"#") then
template = string.gsub(name,"#(%d+)$","") template = string.gsub(name,"#(%d+)$","")
end end
@@ -3528,25 +3442,6 @@ 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
@@ -3764,178 +3659,3 @@ end
function UTILS.OctalToDecimal(Number) 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.
-- @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 Filename The name of the file.
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
-- @return #boolean outcome True if saving is successful, else false.
function UTILS.SaveSetOfOpsGroups(Set,Path,Filename,Structured)
local filename = Filename or "SetOfGroups"
local data = "--Save SET of groups: (name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) "..Filename .."\n"
local List = Set:GetSetObjects()
for _,_group in pairs (List) do
local group = _group:GetGroup() -- Wrapper.Group#GROUP
if group and group:IsAlive() then
local name = group:GetName()
local template = string.gsub(name,"(.AID.%d+$","")
if string.find(template,"#") then
template = string.gsub(name,"#(%d+)$","")
end
local alttemplate = _group.templatename or "none"
local legiono = _group.legion -- Ops.Legion#LEGION
local legion = "none"
if legiono and type(legiono) == "table" and legiono.ClassName then
legion = legiono:GetName()
local asset = legiono:GetAssetByName(name) -- Functional.Warehouse#WAREHOUSE.Assetitem
alttemplate=asset.templatename
end
local units = group:CountAliveUnits()
local position = group:GetVec3()
if Structured then
local structure = UTILS.GetCountPerTypeName(group)
local strucdata = ""
for typen,anzahl in pairs (structure) do
strucdata = strucdata .. typen .. "=="..anzahl..";"
end
data = string.format("%s%s,%s,%s,%s,%d,%d,%d,%d,%s\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata)
else
data = string.format("%s%s,%s,%s,%s,%d,%d,%d,%d\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z)
end
end
end
-- save the data
local outcome = UTILS.SaveToFile(Path,Filename,data)
return outcome
end
--- Load back a #OPSGROUP (ARMYGROUP) data from file for use with @{Ops.Brigade#BRIGADE.LoadBackAssetInPosition}()
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #table Returns a table of data entries: `{ groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure, legion=legion, alttemplate=alttemplate }`
-- Returns nil when the file cannot be read.
function UTILS.LoadSetOfOpsGroups(Path,Filename)
local filename = Filename or "SetOfGroups"
local datatable = {}
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- 1name,2legion,3template,4alttemplate,5units,6position.x,7position.y,8position.z,9strucdata
local groupname = dataset[1]
local legion = dataset[2]
local template = dataset[3]
local alttemplate = dataset[4]
local size = tonumber(dataset[5])
local posx = tonumber(dataset[6])
local posy = tonumber(dataset[7])
local posz = tonumber(dataset[8])
local structure = dataset[9]
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
if size > 0 then
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure, legion=legion, alttemplate=alttemplate }
table.insert(datatable,data)
end
end
else
return nil
end
return datatable
end
--- Get the clock position from a relative heading
-- @param #number refHdg The heading of the reference object (such as a Wrapper.UNIT) in 0-360
-- @param #number tgtHdg The absolute heading from the reference object to the target object/point in 0-360
-- @return #string text Text in clock heading such as "4 O'CLOCK"
-- @usage Display the range and clock distance of a BTR in relation to REAPER 1-1's heading:
--
-- myUnit = UNIT:FindByName( "REAPER 1-1" )
-- myTarget = GROUP:FindByName( "BTR-1" )
--
-- coordUnit = myUnit:GetCoordinate()
-- coordTarget = myTarget:GetCoordinate()
--
-- hdgUnit = myUnit:GetHeading()
-- hdgTarget = coordUnit:HeadingTo( coordTarget )
-- distTarget = coordUnit:Get3DDistance( coordTarget )
--
-- clockString = UTILS.ClockHeadingString( hdgUnit, hdgTarget )
--
-- -- Will show this message to REAPER 1-1 in-game: Contact BTR at 3 o'clock for 1134m!
-- MESSAGE:New("Contact BTR at " .. clockString .. " for " .. distTarget .. "m!):ToUnit( myUnit )
function UTILS.ClockHeadingString(refHdg,tgtHdg)
local relativeAngle = tgtHdg - refHdg
if relativeAngle < 0 then
relativeAngle = relativeAngle + 360
end
local clockPos = math.ceil((relativeAngle % 360) / 30)
return clockPos.." o'clock"
end
--- Get a NATO abbreviated MGRS text for SRS use, optionally with prosody slow tag
-- @param #string Text The input string, e.g. "MGRS 4Q FJ 12345 67890"
-- @param #boolean Slow Optional - add slow tags
-- @return #string Output for (Slow) spelling in SRS TTS e.g. "MGRS;<prosody rate="slow">4;Quebec;Foxtrot;Juliett;1;2;3;4;5;6;7;8;niner;zero;</prosody>"
function UTILS.MGRSStringToSRSFriendly(Text,Slow)
local Text = string.gsub(Text,"MGRS ","")
Text = string.gsub(Text,"%s+","")
Text = string.gsub(Text,"([%a%d])","%1;") -- "0;5;1;"
Text = string.gsub(Text,"A","Alpha")
Text = string.gsub(Text,"B","Bravo")
Text = string.gsub(Text,"C","Charlie")
Text = string.gsub(Text,"D","Delta")
Text = string.gsub(Text,"E","Echo")
Text = string.gsub(Text,"F","Foxtrot")
Text = string.gsub(Text,"G","Golf")
Text = string.gsub(Text,"H","Hotel")
Text = string.gsub(Text,"I","India")
Text = string.gsub(Text,"J","Juliett")
Text = string.gsub(Text,"K","Kilo")
Text = string.gsub(Text,"L","Lima")
Text = string.gsub(Text,"M","Mike")
Text = string.gsub(Text,"N","November")
Text = string.gsub(Text,"O","Oscar")
Text = string.gsub(Text,"P","Papa")
Text = string.gsub(Text,"Q","Quebec")
Text = string.gsub(Text,"R","Romeo")
Text = string.gsub(Text,"S","Sierra")
Text = string.gsub(Text,"T","Tango")
Text = string.gsub(Text,"U","Uniform")
Text = string.gsub(Text,"V","Victor")
Text = string.gsub(Text,"W","Whiskey")
Text = string.gsub(Text,"X","Xray")
Text = string.gsub(Text,"Y","Yankee")
Text = string.gsub(Text,"Z","Zulu")
Text = string.gsub(Text,"0","zero")
Text = string.gsub(Text,"9","niner")
if Slow then
Text = '<prosody rate="slow">'..Text..'</prosody>'
end
Text = "MGRS;"..Text
return Text
end

File diff suppressed because it is too large Load Diff

View File

@@ -99,7 +99,7 @@
-- This method can also be used to **embed a function call when a certain waypoint has been reached**. -- This method can also be used to **embed a function call when a certain waypoint has been reached**.
-- See below the **Tasks at Waypoints** section. -- See below the **Tasks at Waypoints** section.
-- --
-- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Group/502-Route-at-waypoint-to-random-point) -- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Wrapper/Group/GRP-502%20-%20Route%20at%20waypoint%20to%20random%20point)
-- --
-- ## 2.5) Tasks at Waypoints -- ## 2.5) Tasks at Waypoints
-- --
@@ -2173,7 +2173,7 @@ do -- Patrol methods
local Waypoint = Waypoints[#Waypoints] local Waypoint = Waypoints[#Waypoints]
PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
PatrolGroup:Route( Waypoints, 2 ) -- Move after a random seconds to the Route. See the Route method for details. PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details.
end end
end end
@@ -3871,10 +3871,6 @@ end
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #table WayPoints If WayPoints is given, then use the route. -- @param #table WayPoints If WayPoints is given, then use the route.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
-- @usage Intended Workflow is:
-- mygroup:WayPointInitialize()
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
-- mygroup:WayPointExecute()
function CONTROLLABLE:WayPointInitialize( WayPoints ) function CONTROLLABLE:WayPointInitialize( WayPoints )
self:F( { WayPoints } ) self:F( { WayPoints } )
@@ -3906,15 +3902,9 @@ end
-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. -- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions.
-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. -- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
-- @usage Intended Workflow is:
-- mygroup:WayPointInitialize()
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
-- mygroup:WayPointExecute()
function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
self:F2( { WayPoint, WayPointIndex, WayPointFunction } ) self:F2( { WayPoint, WayPointIndex, WayPointFunction } )
if not self.WayPoints then
self:WayPointInitialize()
end
table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex ) table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex )
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPointFunction, arg ) self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPointFunction, arg )
return self return self
@@ -3927,10 +3917,6 @@ end
-- @param #number WayPoint The WayPoint from where to execute the mission. -- @param #number WayPoint The WayPoint from where to execute the mission.
-- @param #number WaitTime The amount seconds to wait before initiating the mission. -- @param #number WaitTime The amount seconds to wait before initiating the mission.
-- @return #CONTROLLABLE self -- @return #CONTROLLABLE self
-- @usage Intended Workflow is:
-- mygroup:WayPointInitialize()
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
-- mygroup:WayPointExecute()
function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime )
self:F( { WayPoint, WaitTime } ) self:F( { WayPoint, WaitTime } )
@@ -4116,74 +4102,6 @@ function CONTROLLABLE:SetOptionRadarUsingForContinousSearch()
return self return self
end end
--- [AIR] Set if the AI is reporting passing of waypoints
-- @param #CONTROLLABLE self
-- @param #boolean OnOff If true or nil, AI will report passing waypoints, if false, it will not.
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionWaypointPassReport(OnOff)
self:F2( { self.ControllableName } )
local onoff = (OnOff == nil or OnOff == true) and false or true
if self:IsAir() then
self:SetOption(AI.Option.Air.id.PROHIBIT_WP_PASS_REPORT,onoff)
end
return self
end
--- [AIR] Set the AI to not report anything over the radio - radio silence
-- @param #CONTROLLABLE self
-- @param #boolean OnOff If true or nil, radio is set to silence, if false radio silence is lifted.
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionRadioSilence(OnOff)
local onoff = (OnOff == true or OnOff == nil) and true or false
self:F2( { self.ControllableName } )
if self:IsAir() then
self:SetOption(AI.Option.Air.id.SILENCE,onoff)
end
return self
end
--- [AIR] Set the AI to report contact for certain types of objects.
-- @param #CONTROLLABLE self
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionRadioContact(Objects)
self:F2( { self.ControllableName } )
if not Objects then Objects = {"Air"} end
if type(Objects) ~= "table" then Objects = {Objects} end
if self:IsAir() then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_CONTACT,Objects)
end
return self
end
--- [AIR] Set the AI to report engaging certain types of objects.
-- @param #CONTROLLABLE self
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}, see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionRadioEngage(Objects)
self:F2( { self.ControllableName } )
if not Objects then Objects = {"Air"} end
if type(Objects) ~= "table" then Objects = {Objects} end
if self:IsAir() then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_ENGAGE,Objects)
end
return self
end
--- [AIR] Set the AI to report killing certain types of objects.
-- @param #CONTROLLABLE self
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}, see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetOptionRadioKill(Objects)
self:F2( { self.ControllableName } )
if not Objects then Objects = {"Air"} end
if type(Objects) ~= "table" then Objects = {Objects} end
if self:IsAir() then
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_KILL,Objects)
end
return self
end
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving. --- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @param #number speed Speed of the controllable, default 20 -- @param #number speed Speed of the controllable, default 20

View File

@@ -26,10 +26,6 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Group)
--
-- ===
--
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
@@ -359,15 +355,12 @@ 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
end end
self:T2(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
@@ -429,7 +422,7 @@ function GROUP:IsActive()
local DCSGroup = self:GetDCSObject() -- DCS#Group local DCSGroup = self:GetDCSObject() -- DCS#Group
if DCSGroup and DCSGroup:isExist() then if DCSGroup then
local unit = DCSGroup:getUnit(1) local unit = DCSGroup:getUnit(1)
if unit then if unit then
local GroupIsActive = unit:isActive() local GroupIsActive = unit:isActive()
@@ -1139,9 +1132,8 @@ function GROUP:GetAverageVec3()
-- Average. -- Average.
local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3 local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3
return Vec3 return Vec3
else
return self:GetVec3()
end end
return nil
end end
--- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission. --- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission.
@@ -1174,28 +1166,24 @@ function GROUP:GetAverageCoordinate()
local Heading = self:GetHeading() local Heading = self:GetHeading()
coord.Heading = Heading coord.Heading = Heading
return coord return coord
else
local coord = self:GetCoordinate()
if coord then
return coord
else else
BASE:E( { "Cannot GetAverageCoordinate", Group = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetAverageCoordinate", Group = self, Alive = self:IsAlive() } )
return nil return nil
end end
end end
end
--- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission. --- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self
-- @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 and FirstUnit:IsAlive() then if FirstUnit then
local FirstUnitCoordinate = FirstUnit:GetCoordinate() local FirstUnitCoordinate = FirstUnit:GetCoordinate()
@@ -1207,24 +1195,6 @@ function GROUP:GetCoordinate()
end end
end end
-- no luck, try the API way
local DCSGroup = Group.getByName(self.GroupName)
if DCSGroup then
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
end
BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } ) BASE:E( { "Cannot GetCoordinate", Group = self, Alive = self:IsAlive() } )
end end
@@ -1778,14 +1748,10 @@ end
--- Returns the group template from the global _DATABASE object (an instance of @{Core.Database#DATABASE}). --- Returns the group template from the global _DATABASE object (an instance of @{Core.Database#DATABASE}).
-- @param #GROUP self -- @param #GROUP self
-- @return #table Template table. -- @return #table
function GROUP:GetTemplate() function GROUP:GetTemplate()
local GroupName = self:GetName() local GroupName = self:GetName()
local template=_DATABASE:GetGroupTemplate( GroupName ) return UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
if template then
return UTILS.DeepCopy( template )
end
return nil
end end
--- Returns the group template route.points[] (the waypoints) from the global _DATABASE object (an instance of @{Core.Database#DATABASE}). --- Returns the group template route.points[] (the waypoints) from the global _DATABASE object (an instance of @{Core.Database#DATABASE}).
@@ -1858,7 +1824,6 @@ end
--- Randomize the positions of the units of the respawned group within the @{Core.Zone}. --- Randomize the positions of the units of the respawned group within the @{Core.Zone}.
-- When a Respawn happens, the units of the group will be placed at random positions within the Zone (selected). -- When a Respawn happens, the units of the group will be placed at random positions within the Zone (selected).
-- NOTE: InitRandomizePositionZone will not ensure, that every unit is placed within the zone!
-- @param #GROUP self -- @param #GROUP self
-- @param #boolean PositionZone true will randomize the positions within the Zone. -- @param #boolean PositionZone true will randomize the positions within the Zone.
-- @return #GROUP self -- @return #GROUP self
@@ -2809,14 +2774,6 @@ end
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled. -- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
-- @return #GROUP self -- @return #GROUP self
function GROUP:SetCommandInvisible(switch) function GROUP:SetCommandInvisible(switch)
return self:CommandSetInvisible(switch)
end
--- Switch on/off invisible flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
-- @return #GROUP self
function GROUP:CommandSetInvisible(switch)
self:F2( self.GroupName ) self:F2( self.GroupName )
if switch==nil then if switch==nil then
switch=false switch=false
@@ -2831,14 +2788,6 @@ end
-- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled. -- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled.
-- @return #GROUP self -- @return #GROUP self
function GROUP:SetCommandImmortal(switch) function GROUP:SetCommandImmortal(switch)
return self:CommandSetImmortal(switch)
end
--- Switch on/off immortal flag for the group.
-- @param #GROUP self
-- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled.
-- @return #GROUP self
function GROUP:CommandSetImmortal(switch)
self:F2( self.GroupName ) self:F2( self.GroupName )
if switch==nil then if switch==nil then
switch=false switch=false

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.Json2Lua(Json) function NET.Lua2Json(Json)
return net.json2lua(Json) return net.json2lua(Json)
end end

View File

@@ -123,36 +123,16 @@ end
--- Check if SCENERY Object is alive. --- Check if SCENERY Object is alive.
--@param #SCENERY self --@param #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100).
--@return #number life --@return #number life
function SCENERY:IsAlive(Threshold) function SCENERY:IsAlive()
if not Threshold then
return self:GetLife() >= 1 and true or false return self:GetLife() >= 1 and true or false
else
return self:GetRelativeLife() > Threshold and true or false
end
end end
--- Check if SCENERY Object is dead. --- Check if SCENERY Object is dead.
--@param #SCENERY self --@param #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100).
--@return #number life --@return #number life
function SCENERY:IsDead(Threshold) function SCENERY:IsDead()
if not Threshold then
return self:GetLife() < 1 and true or false return self:GetLife() < 1 and true or false
else
return self:GetRelativeLife() <= Threshold and true or false
end
end
--- Get SCENERY relative life in percent, e.g. 75.
--@param #SCENERY self
--@return #number rlife
function SCENERY:GetRelativeLife()
local life = self:GetLife()
local life0 = self:GetLife0()
local rlife = math.floor((life/life0)*100)
return rlife
end end
--- Get the threat level of a SCENERY object. Always 0 as scenery does not pose a threat to anyone. --- Get the threat level of a SCENERY object. Always 0 as scenery does not pose a threat to anyone.

View File

@@ -12,8 +12,7 @@
-- @image Wrapper_Static.JPG -- @image Wrapper_Static.JPG
--- --- @type STATIC
-- @type STATIC
-- @extends Wrapper.Positionable#POSITIONABLE -- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle Static objects. --- Wrapper class to handle Static objects.
@@ -237,7 +236,7 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
end end
--- Respawn the @{Wrapper.Static} at the same location with the same properties. --- Respawn the @{Wrapper.Unit} at the same location with the same properties.
-- This is useful to respawn a cargo after it has been destroyed. -- This is useful to respawn a cargo after it has been destroyed.
-- @param #STATIC self -- @param #STATIC self
-- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently. -- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently.
@@ -281,52 +280,3 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
return self return self
end end
--- Find the first(!) STATIC matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #STATIC self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #STATIC The STATIC.
-- @usage
-- -- Find a static with a partial static name
-- local grp = STATIC:FindByMatching( "Apple" )
-- -- will return e.g. a static named "Apple-1-1"
--
-- -- using a pattern
-- local grp = STATIC:FindByMatching( ".%d.%d$" )
-- -- will return the first static found ending in "-1-1" to "-9-9", but not e.g. "-10-1"
function STATIC:FindByMatching( Pattern )
local GroupFound = nil
for name,static in pairs(_DATABASE.STATICS) do
if string.match(name, Pattern ) then
GroupFound = static
break
end
end
return GroupFound
end
--- Find all STATIC objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #STATIC self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #table Groups Table of matching #STATIC objects found
-- @usage
-- -- Find all static with a partial static name
-- local grptable = STATIC:FindAllByMatching( "Apple" )
-- -- will return all statics with "Apple" in the name
--
-- -- using a pattern
-- local grp = STATIC:FindAllByMatching( ".%d.%d$" )
-- -- will return the all statics found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10"
function STATIC:FindAllByMatching( Pattern )
local GroupsFound = {}
for name,static in pairs(_DATABASE.STATICS) do
if string.match(name, Pattern ) then
GroupsFound[#GroupsFound+1] = static
end
end
return GroupsFound
end

View File

@@ -8,7 +8,7 @@
-- --
-- ## Example Missions: -- ## Example Missions:
-- --
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage). -- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Storage).
-- --
-- === -- ===
-- --

View File

@@ -1244,9 +1244,7 @@ function UNIT:GetThreatLevel()
if Attributes["Fighters"] then ThreatLevel = 10 if Attributes["Fighters"] then ThreatLevel = 10
elseif Attributes["Multirole fighters"] then ThreatLevel = 9 elseif Attributes["Multirole fighters"] then ThreatLevel = 9
elseif Attributes["Interceptors"] then ThreatLevel = 9
elseif Attributes["Battleplanes"] then ThreatLevel = 8 elseif Attributes["Battleplanes"] then ThreatLevel = 8
elseif Attributes["Battle airplanes"] then ThreatLevel = 8
elseif Attributes["Attack helicopters"] then ThreatLevel = 7 elseif Attributes["Attack helicopters"] then ThreatLevel = 7
elseif Attributes["Strategic bombers"] then ThreatLevel = 6 elseif Attributes["Strategic bombers"] then ThreatLevel = 6
elseif Attributes["Bombers"] then ThreatLevel = 5 elseif Attributes["Bombers"] then ThreatLevel = 5

View File

@@ -14,7 +14,7 @@
-- --
-- ## Additional Material: -- ## Additional Material:
-- --
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Weapon) -- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Weapon)
-- * **YouTube videos:** None -- * **YouTube videos:** None
-- * **Guides:** None -- * **Guides:** None
-- --
@@ -40,7 +40,6 @@
-- @field #number coalition Coalition ID. -- @field #number coalition Coalition ID.
-- @field #number country Country ID. -- @field #number country Country ID.
-- @field DCS#Desc desc Descriptor table. -- @field DCS#Desc desc Descriptor table.
-- @field DCS#Desc guidance Missile guidance descriptor.
-- @field DCS#Unit launcher Launcher DCS unit. -- @field DCS#Unit launcher Launcher DCS unit.
-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit. -- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit.
-- @field #string launcherName Name of launcher unit. -- @field #string launcherName Name of launcher unit.
@@ -197,9 +196,6 @@ function WEAPON:New(WeaponObject)
if self:IsMissile() and self.desc.missileCategory then if self:IsMissile() and self.desc.missileCategory then
self.categoryMissile=self.desc.missileCategory self.categoryMissile=self.desc.missileCategory
if self.desc.guidance then
self.guidance = self.desc.guidance
end
end end
-- Get type name. -- Get type name.
@@ -671,26 +667,6 @@ function WEAPON:IsTorpedo()
return self.category==Weapon.Category.TORPEDO return self.category==Weapon.Category.TORPEDO
end end
--- Check if weapon is a Fox One missile (Radar Semi-Active).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox One.
function WEAPON:IsFoxOne()
return self.guidance==Weapon.GuidanceType.RADAR_SEMI_ACTIVE
end
--- Check if weapon is a Fox Two missile (IR guided).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox Two.
function WEAPON:IsFoxTwo()
return self.guidance==Weapon.GuidanceType.IR
end
--- Check if weapon is a Fox Three missile (Radar Active).
-- @param #WEAPON self
-- @return #boolean If `true`, is a Fox Three.
function WEAPON:IsFoxThree()
return self.guidance==Weapon.GuidanceType.RADAR_ACTIVE
end
--- Destroy the weapon object. --- Destroy the weapon object.
-- @param #WEAPON self -- @param #WEAPON self

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType"> <launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/lua54/lua54.exe}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="C:\ProgramData\chocolatey\lib\lua51\tools\lua5.1.exe"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup/Moose_Create.lua&quot; &quot;D&quot; &quot;LOCAL&quot; &quot;${workspace_loc:/Moose_Framework/Moose Development/Moose}&quot; &quot;${workspace_loc:/Moose_Framework/Moose Setup}&quot;&#13;&#10;&quot;${workspace_loc:/Moose_Framework/MOOSE_INCLUDE\Moose_Include_Dynamic}&quot; 1"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup\Moose_Create.lua&quot; &quot;D&quot; &quot;LOCAL&quot; &quot;${resource_loc:/MOOSE/Moose Development/Moose}&quot; &quot;${resource_loc:/MOOSE/Moose Setup}&quot;&#13;&#10;${resource_loc:/MOOSE_INCLUDE/Moose_Include_Dynamic}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/MOOSE}"/>
</launchConfiguration> </launchConfiguration>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType"> <launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/Moose_Framework/lua54/lua54.exe}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="C:\ProgramData\chocolatey\lib\lua51\tools\lua5.1.exe"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup\Moose_Create.lua&quot; &quot;S&quot; &quot;LOCAL&quot; &quot;${workspace_loc:/Moose_Framework/Moose Development/Moose}&quot; &quot;${workspace_loc:/Moose_Framework/Moose Setup}&quot;&#13;&#10;&quot;${workspace_loc:/Moose_Framework/MOOSE_INCLUDE\Moose_Include_Static}&quot; 1"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="&quot;Moose Setup\Moose_Create.lua&quot; &quot;S&quot; &quot;LOCAL&quot; &quot;${resource_loc:/MOOSE/Moose Development/Moose}&quot; &quot;${resource_loc:/MOOSE/Moose Setup}&quot;&#13;&#10;${resource_loc:/MOOSE_INCLUDE/Moose_Include_Static}"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/Moose_Framework}"/> <stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${workspace_loc:/MOOSE}"/>
</launchConfiguration> </launchConfiguration>

View File

@@ -29,12 +29,11 @@ Core/SpawnStatic.lua
Core/Timer.lua Core/Timer.lua
Core/Goal.lua Core/Goal.lua
Core/Spot.lua Core/Spot.lua
Core/TextAndSound.lua
Core/Condition.lua
Core/Pathline.lua
Core/ClientMenu.lua
Core/Astar.lua
Core/MarkerOps_Base.lua Core/MarkerOps_Base.lua
Core/Astar.lua
Core/Condition.lua
Core/TextAndSound.lua
Core/Pathline.lua
Wrapper/Object.lua Wrapper/Object.lua
Wrapper/Identifiable.lua Wrapper/Identifiable.lua
@@ -79,44 +78,13 @@ Functional/Warehouse.lua
Functional/Fox.lua Functional/Fox.lua
Functional/Mantis.lua Functional/Mantis.lua
Functional/Shorad.lua Functional/Shorad.lua
Functional/Autolase.lua
Functional/AICSAR.lua
Functional/AmmoTruck.lua
Functional/ZoneGoalCargo.lua
Functional/Tiresias.lua
Functional/Stratego.lua
Ops/Airboss.lua Ops/Airboss.lua
Ops/RecoveryTanker.lua Ops/RecoveryTanker.lua
Ops/RescueHelo.lua Ops/RescueHelo.lua
Ops/ATIS.lua Ops/ATIS.lua
Ops/Auftrag.lua
Ops/OpsGroup.lua
Ops/FlightGroup.lua
Ops/NavyGroup.lua
Ops/Cohort.lua
Ops/Squadron.lua
Ops/Platoon.lua
Ops/Flotilla.lua
Ops/Legion.lua
Ops/AirWing.lua
Ops/Brigade.lua
Ops/Fleet.lua
Ops/Intelligence.lua
Ops/Commander.lua
Ops/Chief.lua
Ops/CSAR.lua
Ops/CTLD.lua Ops/CTLD.lua
Ops/Awacs.lua Ops/CSAR.lua
Ops/Operation.lua
Ops/FlightControl.lua
Ops/PlayerTask.lua
Ops/PlayerRecce.lua
Ops/EasyGCICAP.lua
Ops/OpsZone.lua
Ops/ArmyGroup.lua
Ops/OpsTransport.lua
Ops/Target.lua
AI/AI_Balancer.lua AI/AI_Balancer.lua
AI/AI_Air.lua AI/AI_Air.lua

View File

@@ -4,12 +4,6 @@ parent: Advanced
nav_order: 01 nav_order: 01
--- ---
# Concepts
{: .no_toc }
1. Table of contents
{:toc}
If you want to get deeper into Moose, you will encounter a few terms and If you want to get deeper into Moose, you will encounter a few terms and
concepts that we will explain here. You will need them for the later pages. concepts that we will explain here. You will need them for the later pages.
@@ -26,21 +20,21 @@ files on [GitHub] with a browser. But using [Git] will ease up the steps to keep
the Moose version on your hard disk up to date. the Moose version on your hard disk up to date.
You will need to interact with [GitHub]. At least to download the Moose files. You will need to interact with [GitHub]. At least to download the Moose files.
For non-developers the page can be confusing. Take your time and read this For non developers the page can be confusing. Take your time and read this
documentation. We are not able to explain every single detail on using [GitHub] documentation. We are not able to explain every single detail on using [GitHub]
and [Git]. Especially because it is changing really quick and this documentation and [Git]. Especially because it is changing really quick and this documentaion
will not. So try to use the help system of [GitHub] or find some videos on will not. So try to use the help system of [GitHub] or find some videos on
[YouTube]. If you get stuck ask for help in the [Moose Discord]. [YouTube]. If you get stuck ask for help in the [Moose Discord].
Moose uses more than one repository on [GitHub] which doesn't exactly make it Moose uses more then one repository on [GitHub] which doesn't exactly make it
any clearer. A list can be found on the [reposities] page. any clearer. A list can be found on the [reposities] page.
# Branches: master & develop # Branches: master & develop
As already explained in the [overview] two branches are used: As already explained in the [overview] two branches are used:
- Branch [master]: Stable release branch. - [master]: Stable release branch.
- Branch [develop]: Newest development with more OPS classes. - [develop]: Newest development with more OPS classes.
As a starter it is okay to begin your journey with the `master` branch. As a starter it is okay to begin your journey with the `master` branch.
If you are interested in some newer classes you need to use the `develop` If you are interested in some newer classes you need to use the `develop`
@@ -48,8 +42,8 @@ branch. The later one is also very stable, but it's missing more detailed
documentation and example missions for some of the new OPS classes. documentation and example missions for some of the new OPS classes.
You can switch between these branches with a drop down in the upper left corner You can switch between these branches with a drop down in the upper left corner
of the [GitHub] repository page. The list of branches is long. So it is a best of th [GitHub] repository page. The list of branches is long. So it is a best
practice to save a bookmark in your browser with the links above. practise to save a bookmark in your browser with the links above.
Both branches are available on most of the different repositories. But because Both branches are available on most of the different repositories. But because
of a limitation of [GitHub pages], we had to split the documentation in two of a limitation of [GitHub pages], we had to split the documentation in two
different repositories: different repositories:
@@ -61,7 +55,7 @@ different repositories:
Moose consists of more than 140 individual files with the file extension `.lua`. Moose consists of more than 140 individual files with the file extension `.lua`.
They are places in a [directory tree], which makes it more organized and its They are places in a [directory tree], which makes it more organized and its
semantic is pre-defined for [IntelliSense] to work. semantic is pre-defined for IntelliSense to work.
On every change which is pushed to [GitHub] a build job will combine all of On every change which is pushed to [GitHub] a build job will combine all of
these files to a single file called `Moose.lua`. In a second step all these files to a single file called `Moose.lua`. In a second step all
@@ -69,12 +63,12 @@ comments will be removed to decrease the file size and the result will be saved
as `Moose_.lua`. These both files are created for users of Moose to include in as `Moose_.lua`. These both files are created for users of Moose to include in
your missions. your missions.
The individual `.lua` files are used by the Moose developers and power users. The individual `.lua` files are used by the Mosse developers and power users.
It is complicated to use them, but in combination with an IDE and a debugger it It is complicated to use them, but in combination with an IDE and a debugger it
is very useful to analyze even complex problems or write new additions to the is very usefull to analyse even complex problems or write new additions to the
Moose framework. Moose framework.
# Static loading # Static loading vs. dynamic loading
If you add a script file with a `DO SCRIPT FILE` trigger, like we described in If you add a script file with a `DO SCRIPT FILE` trigger, like we described in
[Create your own Hello world], the script file will be copied into the mission [Create your own Hello world], the script file will be copied into the mission
@@ -84,159 +78,25 @@ with another file ending.
If you change the script file after adding it to the mission, the changes are If you change the script file after adding it to the mission, the changes are
not available on mission start. You have to re-add the script after each change. not available on mission start. You have to re-add the script after each change.
This can be very annoying and often leads to forgetting to add the change again. This can be very annoying and often leads to forgetting to add the change again.
Then you wonder why the mission does not deliver the desired result. Then you wonder why the script does not deliver the desired result.
But when the mission is finished you can upload it to your dedicated DCS server But when the mission is finished you can upload it to your dedicated DCS server
or give it to a friend and it should run without problems. This way of embedding or give it to a friend and it should run without problems. This way of embedding
the scripts do we call `static loading` and the resulting mission is very the scripts do we call `static loading` and the resulting mission is very
portable. portable.
# Dynamic loading of mission scripts The other way on loading scripts is by using `DO SCRIPT`. This time the mission
The other way of loading scripts is by using `DO SCRIPT`. This time the mission
editor don't show a file browse button. Instead you see a (very small) text editor don't show a file browse button. Instead you see a (very small) text
field to enter the code directly into it. It is only useful for very small field to enter the code directly into it. It is only usefull for very small
script snippets. But we can use it to load a file from your hard drive like script snippets. But we can use it to load a file from our hard drive like this:
this:
```lua ```lua
dofile('C:/MyScripts/hello-world.lua') aaa
dofile('C:\\MyScripts\\hello-world.lua')
dofile([[C:\MyScripts\hello-world.lua]])
```
So all lines above do the same. In [Lua] you need to specify the path with
slashes, escape backslashes or use double square brackets around the string.
Double square brackets are usefull, because you can copy paste the path
without any modification.
If you upload a mission with this code, you need to create the folder
`C:\MyScripts\` on the server file system and upload the newest version of
`hello-world.lua`, too. The same applies, if you give the mission to a friend.
This makes the mission less portable, but on the other hand the mission uses the
file on the hard disk, without the need to add it to the mission again.
All you need to do is save the file and restart the mission.
The following can be used to increase portability:
```lua
dofile(lfs.writedir() .. '/Missions/hello-world.lua')
```
The function `lfs.writedir()` will return your [Saved Games folder].
So you place the scripts in the subfolder Missions. This way the folder
structure is already available on all target systems. But you need to ensure
mission and script are both in sync to avoid problems. If you changed both and
upload only one of them to your server, you may get trouble.
There is another method you may find useful to dynamically load scripts:
```lua
assert(loadfile('C:/MyScripts/hello-world.lua'))()
assert(loadfile('C:\\MyScripts\\hello-world.lua'))()
assert(loadfile([[C:\MyScripts\hello-world.lua]]))()
```
It is a little bit harder to read and write because of all these different
brackets. Especially the one on line 3. But it is a little safer than `dofile`.
Because of readability I prefer to use `dofile`.
# Dynamic loading of Moose
Of course you can use the same method to load Moose. This way you can place one
Moose file in your [Saved Games folder], which is used by multiple missions.
If you want to update Moose you just need to replace the file and all missions
will use the new version. But I prefer to add Moose by a `DO SCRIPT FILE`
trigger so I can add and test the new version for each mission step by step.
But we added two different ways to load the Moose source files automatically.
This is useful for Moose developers and it is a requirement to use a debugger.
This will be explained later in the [Debugger Guide].
# Automatic dynamic loading
With the code below you can have the advantages of both approaches.
- Copy the code into your mission script at the beginning.
- Save the mission script into the folder Missions in your [Saved Games folder].
- Change script filename in line 2 to match to your script.
- [De-Sanitize] your `MissionScripting.lua`.
Now the mission will use the script on your hard drive instead of the script
embedded in th MIZ file, as long as it is available. So you can chnge the
script, save it and restart the mission, without the need to readd it after each
change.
If you reach a stable state in your script development and want to upload the
mission to your server or give it to a friend, then just add the script again
like in the static method and save the mission.
{: .important }
> Do not forget to readd the script, prior uploading or sharing the mission,
> or it will run with an outdated version of your script and may fail if the
> objects in the mission don't match to this old version.
```lua
-- Use script file from hard disk instead of the one included in the .miz file
if lfs and io then
MissionScript = lfs.writedir() .. '/Missions/hello-world-autodyn.lua'
-- Check if the running skript is from temp directory to avoid an endless loop
if string.find( debug.getinfo(1).source, lfs.tempdir() ) then
local f=io.open(MissionScript,"r")
if f~=nil then
io.close(f)
env.info( '*** LOAD MISSION SCRIPT FROM HARD DISK *** ' )
dofile(MissionScript)
do return end
end
end
else
env.error( '*** LOAD MISSION SCRIPT FROM HARD DISK FAILED (Desanitize lfs and io)*** ' )
end
--
-- Simple example mission to show the very basics of MOOSE
--
MESSAGE:New( "Hello World! This messages is printed by MOOSE!", 35, "INFO" ):ToAll():ToLog()
``` ```
# IDE vs. Notepad++ # IDE vs. Notepad++
As a beginner you should start with a good text editor, which supports syntax # What is a debugger (good for)
highlighting of [Lua] code. This must not be [Notepad++]. It can be any other
powerful editor of your choice. Do yourself a favor and don't use the Windows
editor.
If you are a developer of [Lua] or another programming language, then your are
most likely familiar with an IDE (Integrated Develop Environment).
Otherwise you should know, that an IDE may help you with code completion,
Refactoring, Autocorrection, Formatting Source Code, showing documentation
as popup on mouse hover over keywords and Debugging.
There are different IDEs available. And not all IDEs support all features.
The three most important for Moose are:
- [Eclipse LDT]
- [Visual Studio Code]
- [PyCharm] (or [IntelliJ IDEA])
Eclipse has the best support for hover documentation and [IntelliSense] with
Moose. The Inventor of Moose (FlightControl) did an amazing job by adding an
integration to Eclipse LDT (a special version for Lua).
Unfortunately Eclipse LDT is not maintained any longer (last release 2018).
And the debugger doesn't work anymore, since an update of DCS.
In Visual Studio Code the support of Lua can be added by an addon.
The debugger works with Moose and DCS, but showing the LuaDoc and [IntelliSense]
is very limited.
PyCharm supports Lua also with an addon. The debugger works with Moose and DCS,
but showing the LuaDoc and [IntelliSense] is very limited.
It is up to you to choose the IDE according to your taste. Guides on how to
setup Moose with different IDEs and Debuggers are provided later in this
documentation.
[Git]: https://en.wikipedia.org/wiki/Git [Git]: https://en.wikipedia.org/wiki/Git
[GitHub]: https://github.com/ [GitHub]: https://github.com/
@@ -250,14 +110,3 @@ documentation.
[MOOSE_DOCS]: https://flightcontrol-master.github.io/MOOSE_DOCS/ [MOOSE_DOCS]: https://flightcontrol-master.github.io/MOOSE_DOCS/
[MOOSE_DOCS_DEVELOP]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/ [MOOSE_DOCS_DEVELOP]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/
[directory tree]: https://github.com/FlightControl-Master/MOOSE/tree/master/Moose%20Development/Moose [directory tree]: https://github.com/FlightControl-Master/MOOSE/tree/master/Moose%20Development/Moose
[Saved Games folder]: ../beginner/tipps-and-tricks.md#find-the-saved-games-folder
[Lua]: https://www.lua.org/
[Create your own Hello world]: ../beginner/hello-world-build.md
[Debugger Guide]: debugger.md
[IntelliSense]: https://en.wikipedia.org/wiki/IntelliSense
[De-Sanitize]: desanitize-dcs.md
[Notepad++]: https://notepad-plus-plus.org/downloads/
[Eclipse LDT]: https://projects.eclipse.org/projects/tools.ldt
[Visual Studio Code]: https://code.visualstudio.com/
[PyCharm]: https://www.jetbrains.com/pycharm/
[IntelliJ IDEA]: https://www.jetbrains.com/idea/

View File

@@ -1,8 +0,0 @@
---
title: Debugger
parent: Advanced
nav_order: 100
---
{: .warning }
> THIS DOCUMENT IS STILL WORK IN PROGRESS!

View File

@@ -35,24 +35,16 @@ Please remember when posting a question:
- Before posting anything follow the [troubleshooting steps]. - Before posting anything follow the [troubleshooting steps].
- **Read your logs**. - **Read your logs**.
### Formulate a good description
A post should contain the following: A post should contain the following:
- A description what you expected to happen and what actually happened. 1. A describtion what you expected to happen and what actually happened.
- Do not use vague words this stuff is hard to help with! Be specific. - Do not use vague words this stuff is hard to help with! Be specific.
- Describe what happens instead. 2. Describe what happens instead.
- The less detail you offer, the less chance you can be helped. - The less detail you offer, the less chance you can be helped.
- Don't say it doesn't work. Or is it broken. Say what it actually does. - Dont say it doesnt work. Or is it broken. Say what it actually does.
### Format your code 3. Post your code in Discord as formatted code:
The easier your code is to read, the more likely you are to get a helpful answer. If your code is hard to read, some
people who could help you may not even bother to read your code. Syntax Highlighting makes the code much clearer and
easier to understand. Therefore:
- Post your code in Discord as formatted code:
- Wrap a single line of code in backticks \` like this: - Wrap a single line of code in backticks \` like this:
@@ -62,31 +54,14 @@ easier to understand. Therefore:
![discord-multi-line-code.png](../images/beginner/discord-multi-line-code.png) ![discord-multi-line-code.png](../images/beginner/discord-multi-line-code.png)
### Do not post a screenshot of your code - Post your log lines with the error or warning messages. Format them like this:
Your code is easy to read on a screenshot if you are using a good text editor or IDE, but if someone discovers an error ![discord-fomat-logs.png](../images/beginner/discord-fomat-logs.png)
in your code and wants to post a corrected version, they will have to type out the entire code. This could lead to them
not helping you because it's too much work for them.
### Post your log
If the error message in the `dcs.log` does not tell you anything, then post it in the Discord.
- Post the important log lines with the error or warning messages. Format them like this:
![discord-format-logs.png](../images/beginner/discord-format-logs.png)
### Send your mission when requested
Please don't just send your mission file. You have to manually extract the script from the file.
It is better to send your script code and log lines beforehand.
If this does not help, you may be asked to send your mission.
- Some complex problems need the mission (.miz file) also. - Some complex problems need the mission (.miz file) also.
- But post your mission only when requested. - But post your mission only when requested.
- Try to simplify your mission if it is complex! - Try to simplify your mission if it is complex!
- Try to avoid or delete MODs, because could prevent people from helping you.
There are people in the Discord and in the forum, who spend their free time to There are people in the Discord and in the forum, who spend their free time to
help you. <br /> help you. <br />

Some files were not shown because too many files have changed in this diff Show More