diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index db74f8ab0..d1b13a31c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: @@ -43,7 +43,7 @@ jobs: working-directory: docs/ - name: Setup Pages id: pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v4 - name: Build with Jekyll # Outputs to the './_site' directory by default run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}" @@ -52,7 +52,7 @@ jobs: working-directory: docs/ - name: Upload artifact # Automatically uploads an artifact from the './_site' directory by default - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: docs/_site/ @@ -66,13 +66,13 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 check: runs-on: ubuntu-latest needs: deploy steps: - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 - 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 diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index ce3675189..6ab927288 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -1144,6 +1144,19 @@ function BASE:TraceClassMethod( Class, Method ) self:I( "Tracing method " .. Method .. " of class " .. Class ) 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 + --- Trace a function call. This function is private. -- @param #BASE self -- @param Arguments A #table or any field. @@ -1168,7 +1181,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline end - env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) + env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, BASE:_Serialize(Arguments) ) ) end end end @@ -1242,7 +1255,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline end - env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) ) + env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) end end end @@ -1314,7 +1327,7 @@ function BASE:E( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) ) end end @@ -1341,39 +1354,8 @@ function BASE:I( Arguments ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) ) else - env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) ) + env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) ) 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 diff --git a/Moose Development/Moose/Core/Beacon.lua b/Moose Development/Moose/Core/Beacon.lua index c3d22b823..7cc9434d6 100644 --- a/Moose Development/Moose/Core/Beacon.lua +++ b/Moose Development/Moose/Core/Beacon.lua @@ -8,6 +8,10 @@ -- -- === -- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon) +-- +-- === +-- -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- -- @module Core.Beacon @@ -286,6 +290,7 @@ end -- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) self:F({TACANChannel, Message, Bearing, BeaconDuration}) + self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.") local IsValid = true diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 14b99fc76..cbfa62555 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -37,6 +37,8 @@ -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. -- @field #table CLIENTS Clients. -- @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 --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. @@ -93,6 +95,8 @@ DATABASE = { OPSZONES = {}, PATHLINES = {}, STORAGES = {}, + STNS={}, + SADL={}, } local _DATABASECoalition = @@ -928,7 +932,7 @@ function DATABASE:Spawn( SpawnTemplate ) SpawnTemplate.CountryID = nil SpawnTemplate.CategoryID = nil - self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) + self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name ) self:T3( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) @@ -1029,10 +1033,31 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate 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 end - + -- Debug info. self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, @@ -1043,6 +1068,80 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category ) 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) + 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) + 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. -- @param #DATABASE self -- @param #string GroupName Group name. diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index d3a105631..5908c032b 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1378,6 +1378,7 @@ function EVENT:onEvent( Event ) Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) Event.MarkText=Event.text Event.MarkCoalition=Event.coalition + Event.IniCoalition=Event.coalition Event.MarkGroupID = Event.groupID end diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index b39250c8c..3bc950416 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -17,7 +17,7 @@ -- ### Author: **Applevangelist** -- -- Date: 5 May 2021 --- Last Update: Feb 2023 +-- Last Update: Mar 2023 -- -- === --- @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.1", + version = "0.1.3", debug = false, Casesensitive = true, } @@ -114,6 +114,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. + -- @param #number MarkerID Id of this marker + -- @param #number CoalitionNumber Coalition of the marker creator --- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged @@ -124,7 +126,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. - -- @param #number idx DCS Marker ID + -- @param #number MarkerID Id of this marker + -- @param #number CoalitionNumber Coalition of the marker creator --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted @@ -133,7 +136,7 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Event The Event called -- @param #string To The To state - --- "Stop" trigger. Used to stop the function an unhandle events + --- "Stop" trigger. Used to stop the function an unhandle events -- @function [parent=#MARKEROPS_BASE] Stop end @@ -155,29 +158,30 @@ function MARKEROPS_BASE:OnEventMark(Event) local text = tostring(Event.text) local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll() end + local coalition = Event.MarkCoalition -- decision if Event.id==world.event.S_EVENT_MARK_ADDED then - self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos}) + self:T({event="S_EVENT_MARK_ADDED", carrier=Event.IniGroupName, vec3=Event.pos}) -- Handle event local Eventtext = tostring(Event.text) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkAdded(Eventtext,matchtable,coord) + self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition) end end elseif Event.id==world.event.S_EVENT_MARK_CHANGE then - self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos}) + self:T({event="S_EVENT_MARK_CHANGE", carrier=Event.IniGroupName, vec3=Event.pos}) -- Handle event. local Eventtext = tostring(Event.text) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord,Event.idx) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then - self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos}) + self:T({event="S_EVENT_MARK_REMOVED", carrier=Event.IniGroupName, vec3=Event.pos}) -- Hande event. local Eventtext = tostring(Event.text) if Eventtext~=nil then @@ -230,8 +234,10 @@ end -- @param #string To The To state -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text + -- @param #number MarkerID Id of this marker + -- @param #number CoalitionNumber Coalition of the marker creator -- @param Core.Point#COORDINATE Coord Coordinate of the marker. -function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord) +function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) end @@ -242,8 +248,10 @@ end -- @param #string To The To state -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text + -- @param #number MarkerID Id of this marker + -- @param #number CoalitionNumber Coalition of the marker creator -- @param Core.Point#COORDINATE Coord Coordinate of the marker. -function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord) +function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber) self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()}) end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 0045ee7fa..af0dfe661 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1097,6 +1097,7 @@ do GroupPrefixes = nil, Zones = nil, Functions = nil, + Alive = nil, }, FilterMeta = { Coalitions = { @@ -1470,7 +1471,7 @@ do end - --- Builds a set of groups that are only active. + --- Builds a set of groups that are active, ie in the mission but not yet activated (false) or actived (true). -- Only the groups that are active will be included within the set. -- @param #SET_GROUP self -- @param #boolean Active (Optional) Include only active groups to the set. @@ -1495,6 +1496,14 @@ do self.Filter.Active = Active return self 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. -- @param #SET_GROUP self @@ -1993,7 +2002,16 @@ do function SET_GROUP:IsIncludeObject( MGroup ) self:F2( MGroup ) 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 local MGroupActive = false self:F( { Active = self.Filter.Active } ) @@ -2997,7 +3015,7 @@ do -- SET_UNIT local velocity = self:GetVelocity() or 0 Coordinate:SetHeading( heading ) Coordinate:SetVelocity( velocity ) - self:I(UTILS.PrintTableToLog(Coordinate)) + self:T(UTILS.PrintTableToLog(Coordinate)) end return Coordinate @@ -4521,7 +4539,7 @@ do -- SET_CLIENT if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then -- CA Slot entered local ObjectName, Object = self:AddInDatabase( Event ) - self:I( ObjectName, UTILS.PrintTableToLog(Object) ) + self:T( ObjectName, UTILS.PrintTableToLog(Object) ) if Object and self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) end diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index c7095dc8f..19af529b0 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -199,6 +199,22 @@ -- -- * @{#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. +-- +-- ### 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 -- @@ -520,7 +536,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr end if SpawnTemplate then - 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.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.SpawnTemplatePrefix = SpawnTemplatePrefix self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix self.SpawnTemplate.name = SpawnTemplatePrefix @@ -724,7 +740,7 @@ end -- @param #number Country Country id as number or enumerator: -- -- * @{DCS#country.id.RUSSIA} --- * @{DCS#county.id.USA} +-- * @{DCS#country.id.USA} -- -- @return #SPAWN self function SPAWN:InitCountry( Country ) @@ -780,6 +796,82 @@ function SPAWN:InitSkill( Skill ) return self 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. -- @param #SPAWN self -- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. @@ -1699,6 +1791,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) end self.SpawnGroups[self.SpawnIndex].Spawned = true + self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor = self.SpawnTemplatePrefix return self.SpawnGroups[self.SpawnIndex].Group else -- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) @@ -3303,7 +3396,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 end end end - + if self.SpawnInitKeepUnitNames == false then for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) @@ -3311,9 +3404,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 end else for UnitID = 1, #SpawnTemplate.units do - local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) - self:T( { UnitPrefix, Rest } ) - + local SpawnInitKeepUnitIFF = false + 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" ) + 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].unitId = nil end @@ -3395,34 +3496,56 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex end end + -- Speed + if self.InitSpeed then + SpawnTemplate.units[UnitID].speed = self.InitSpeed + end -- Link16 local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft if AddProps then if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then - -- 4 digit octal with leading 0 - if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then - local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 - local decimal = UTILS.OctalToDecimal(octal)+UnitID-1 - SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",UTILS.DecimalToOctal(decimal)) - else -- ED bug - chars in here - 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) + if self.SpawnInitSTN then + 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 + local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 + local num = UTILS.OctalToDecimal(octal) + if _DATABASE.STNS[num] ~= nil or UnitID > 1 then -- STN taken or next unit + 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 + local OSTN = _DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name) + SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN) + end end end -- A10CII if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then - -- 3 digit octal with leading 0 - if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then - local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN - local decimal = UTILS.OctalToDecimal(octal)+UnitID-1 - SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",UTILS.DecimalToOctal(decimal)) - else -- ED bug - chars in here - 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) + -- 4 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 + local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN + local num = UTILS.OctalToDecimal(octal) + 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 + local OSTN = _DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name) + SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN) + end end end -- VoiceCallsignNumber diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 77543f5fc..f6a082ddf 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -1,36 +1,36 @@ --- **Core** - Spawn statics. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Spawn new statics from a static already defined in the mission editor. -- * Spawn new statics from a given template. -- * Spawn new statics from a given type. -- * Spawn with a custom heading and location. -- * Spawn within a zone. -- * Spawn statics linked to units, .e.g on aircraft carriers. --- --- === --- --- # Demo Missions --- --- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Core/SpawnStatic) -- --- -- === --- +-- +-- # Demo Missions +-- +-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic) +-- +-- +-- === +-- -- # YouTube Channel --- +-- -- ## No videos yet! --- +-- -- === --- +-- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Core.SpawnStatic -- @image Core_Spawnstatic.JPG @@ -58,37 +58,37 @@ --- Allows to spawn dynamically new @{Wrapper.Static}s into your mission. --- --- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), +-- +-- Through creating a copy of an existing static object template as defined in the Mission Editor (ME), SPAWNSTATIC can retireve the properties of the defined static object template (like type, category etc), -- and "copy" these properties to create a new static object and place it at the desired coordinate. --- --- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method. +-- +-- New spawned @{Wrapper.Static}s get **the same name** as the name of the template Static, or gets the given name when a new name is provided at the Spawn method. -- By default, spawned @{Wrapper.Static}s will follow a naming convention at run-time: --- +-- -- * Spawned @{Wrapper.Static}s will have the name _StaticName_#_nnn_, where _StaticName_ is the name of the **Template Static**, and _nnn_ is a **counter from 0 to 99999**. --- +-- -- # SPAWNSTATIC Constructors --- +-- -- Firstly, we need to create a SPAWNSTATIC object that will be used to spawn new statics into the mission. There are three ways to do this. --- +-- -- ## Use another Static --- +-- -- A new SPAWNSTATIC object can be created using another static by the @{#SPAWNSTATIC.NewFromStatic}() function. All parameters such as position, heading, country will be initialized -- from the static. --- +-- -- ## From a Template --- +-- -- A SPAWNSTATIC object can also be created from a template table using the @{#SPAWNSTATIC.NewFromTemplate}(SpawnTemplate, CountryID) function. All parameters are taken from the template. --- +-- -- ## From a Type --- +-- -- A very basic method is to create a SPAWNSTATIC object by just giving the type of the static. All parameters must be initialized from the InitXYZ functions described below. Otherwise default values -- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map. --- +-- -- # Setting Parameters --- +-- -- Parameters such as the spawn position, heading, country etc. can be set via :Init*XYZ* functions. Note that these functions must be given before the actual spawn command! --- +-- -- * @{#SPAWNSTATIC.InitCoordinate}(Coordinate) Sets the coordinate where the static is spawned. Statics are always spawnd on the ground. -- * @{#SPAWNSTATIC.InitHeading}(Heading) sets the orientation of the static. -- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this. @@ -99,17 +99,17 @@ -- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier. -- -- # Spawning the Statics --- +-- -- Once the SPAWNSTATIC object is created and parameters are initialized, the spawn command can be given. There are different methods where some can be used to directly set parameters -- such as position and heading. --- +-- -- * @{#SPAWNSTATIC.Spawn}(Heading, NewName) spawns the static with the set parameters. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromZone}(Zone, Heading, NewName) spawns the static at the center of a @{Core.Zone}. Optionally, heading and name can be given. The name **must be unique**! --- +-- -- @field #SPAWNSTATIC SPAWNSTATIC --- +-- SPAWNSTATIC = { ClassName = "SPAWNSTATIC", SpawnIndex = 0, @@ -139,9 +139,9 @@ SPAWNSTATIC = { function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - + local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) - + if TemplateStatic then self.SpawnTemplatePrefix = SpawnTemplateName self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) @@ -166,11 +166,11 @@ end function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID) local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC - + self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate) self.SpawnTemplatePrefix = SpawnTemplate.name self.CountryID = CountryID or country.id.USA - + return self end @@ -189,7 +189,7 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID) self.InitStaticCategory=StaticCategory self.CountryID=CountryID or country.id.USA self.SpawnTemplatePrefix=self.InitStaticType - + self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) self.InitStaticHeading=0 @@ -291,7 +291,7 @@ function SPAWNSTATIC:InitCountry(CountryID) return self end ---- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc. +--- Initialize name prefix statics get. This will be appended by "#0001", "#0002" etc. -- @param #SPAWNSTATIC self -- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name. -- @return #SPAWNSTATIC self @@ -327,13 +327,13 @@ function SPAWNSTATIC:Spawn(Heading, NewName) if Heading then self.InitStaticHeading=Heading end - + if NewName then self.InitStaticName=NewName end return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) - + end --- Creates a new @{Wrapper.Static} from a POINT_VEC2. @@ -347,7 +347,7 @@ function SPAWNSTATIC:SpawnFromPointVec2(PointVec2, Heading, NewName) local vec2={x=PointVec2:GetX(), y=PointVec2:GetY()} local Coordinate=COORDINATE:NewFromVec2(vec2) - + return self:SpawnFromCoordinate(Coordinate, Heading, NewName) end @@ -362,11 +362,11 @@ function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName) -- Set up coordinate. self.InitStaticCoordinate=Coordinate - + if Heading then self.InitStaticHeading=Heading end - + if NewName then self.InitStaticName=NewName end @@ -385,7 +385,7 @@ function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName) -- Spawn the new static at the center of the zone. local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) - + return Static end @@ -399,45 +399,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) Template=Template or {} local CountryID=CountryID or self.CountryID - + if self.InitStaticType then Template.type=self.InitStaticType end - + if self.InitStaticCategory then Template.category=self.InitStaticCategory end - - if self.InitStaticCoordinate then - Template.x = self.InitStaticCoordinate.x + + if self.InitStaticCoordinate then + Template.x = self.InitStaticCoordinate.x Template.y = self.InitStaticCoordinate.z - Template.alt = self.InitStaticCoordinate.y + Template.alt = self.InitStaticCoordinate.y end - + if self.InitStaticHeading then - Template.heading = math.rad(self.InitStaticHeading) + Template.heading = math.rad(self.InitStaticHeading) end if self.InitStaticShape then Template.shape_name=self.InitStaticShape end - + if self.InitStaticLivery then Template.livery_id=self.InitStaticLivery end - + if self.InitStaticDead~=nil then Template.dead=self.InitStaticDead end - + if self.InitStaticCargo~=nil then Template.canCargo=self.InitStaticCargo end - + if self.InitStaticCargoMass~=nil then Template.mass=self.InitStaticCargoMass end - + if self.InitLinkUnit then Template.linkUnit=self.InitLinkUnit:GetID() Template.linkOffset=true @@ -446,45 +446,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) Template.offsets.x=self.InitOffsetX Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 end - + if self.InitFarp then Template.heliport_callsign_id = self.InitFarpCallsignID Template.heliport_frequency = self.InitFarpFreq Template.heliport_modulation = self.InitFarpModu Template.unitId=nil end - + -- Increase spawn index counter. self.SpawnIndex = self.SpawnIndex + 1 - + -- Name of the spawned static. Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) -- Add and register the new static. local mystatic=_DATABASE:AddStatic(Template.name) - + -- Debug output. self:T(Template) - + -- Add static to the game. local Static=nil --DCS#StaticObject - + if self.InitFarp then - - local TemplateGroup={} + + local TemplateGroup={} TemplateGroup.units={} TemplateGroup.units[1]=Template - + TemplateGroup.visible=true TemplateGroup.hidden=false TemplateGroup.x=Template.x TemplateGroup.y=Template.y TemplateGroup.name=Template.name - self:T("Spawning FARP") + self:T("Spawning FARP") self:T({Template=Template}) self:T({TemplateGroup=TemplateGroup}) - + -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) @@ -499,10 +499,10 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) world.onEvent(Event) else - self:T("Spawning Static") - self:T2({Template=Template}) + self:T("Spawning Static") + self:T2({Template=Template}) Static=coalition.addStaticObject(CountryID, Template) end - + return mystatic end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 3317df260..d2432703a 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -46,6 +46,10 @@ -- -- === -- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Zone) +-- +-- === +-- -- ### Author: **FlightControl** -- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit** -- @@ -326,14 +330,14 @@ function ZONE_BASE:GetRandomVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. +--- 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. -- @param #ZONE_BASE self -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. function ZONE_BASE:GetRandomPointVec2() return nil end ---- Define a random @{Core.Point#POINT_VEC3} within the zone. +--- 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. -- @param #ZONE_BASE self -- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. function ZONE_BASE:GetRandomPointVec3() @@ -899,7 +903,8 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Point = {} local Vec2 = self:GetVec2() - + local countryID = CountryID or country.id.USA + Points = Points and Points or 360 local Angle @@ -910,7 +915,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - local CountryName = _DATABASE.COUNTRY_NAME[CountryID] + local CountryName = _DATABASE.COUNTRY_NAME[countryID] local Tire = { ["country"] = CountryName, @@ -925,7 +930,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) ["heading"] = 0, } -- end of ["group"] - local Group = coalition.addStaticObject( CountryID, Tire ) + local Group = coalition.addStaticObject( countryID, Tire ) if UnBound and UnBound == true then Group:destroy() end @@ -1175,7 +1180,7 @@ function ZONE_RADIUS:RemoveJunk() return n end ---- Count the number of different coalitions inside the zone. +--- Get a table of scanned units. -- @param #ZONE_RADIUS self -- @return #table Table of DCS units and DCS statics inside the zone. function ZONE_RADIUS:GetScannedUnits() @@ -1210,7 +1215,7 @@ function ZONE_RADIUS:GetScannedSetUnit() return SetUnit end ---- Get a set of scanned units. +--- Get a set of scanned groups. -- @param #ZONE_RADIUS self -- @return Core.Set#SET_GROUP Set of groups. function ZONE_RADIUS:GetScannedSetGroup() @@ -1510,7 +1515,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes) return point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. +--- 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. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. @@ -1541,7 +1546,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) end ---- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. +--- 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. -- @param #ZONE_RADIUS self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. @@ -1985,7 +1990,7 @@ function ZONE_GROUP:GetRandomVec2() return Point end ---- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. +--- 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. -- @param #ZONE_GROUP self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. @@ -2829,7 +2834,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2() end end ---- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. +--- 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. -- @param #ZONE_POLYGON_BASE self -- @return @{Core.Point#POINT_VEC2} function ZONE_POLYGON_BASE:GetRandomPointVec2() @@ -2842,7 +2847,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2() return PointVec2 end ---- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. +--- 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. -- @param #ZONE_POLYGON_BASE self -- @return @{Core.Point#POINT_VEC3} function ZONE_POLYGON_BASE:GetRandomPointVec3() @@ -3830,18 +3835,18 @@ function ZONE_OVAL:GetRandomVec2() return {x=rx, y=ry} end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. +--- 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. -- @param #ZONE_OVAL self -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. function ZONE_OVAL:GetRandomPointVec2() return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) end ---- Define a random @{Core.Point#POINT_VEC2} within the zone. +--- 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. -- @param #ZONE_OVAL self -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. function ZONE_OVAL:GetRandomPointVec3() - return POINT_VEC2:NewFromVec3(self:GetRandomVec2()) + return POINT_VEC3:NewFromVec3(self:GetRandomVec2()) end --- Draw the zone on the F10 map. @@ -3981,7 +3986,7 @@ do -- ZONE_AIRBASE return ZoneVec2 end - --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. + --- 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. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 0c9c81b67..5ca6d4e51 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -1,14 +1,14 @@ --- **Functional** - Yet Another Missile Trainer. --- --- +-- +-- -- Practice to evade missiles without being destroyed. --- +-- -- -- ## Main Features: --- +-- -- * Handles air-to-air and surface-to-air missiles. -- * Define your own training zones on the map. Players in this zone will be protected. --- * Define launch zones. Only missiles launched in these zones are tracked. +-- * Define launch zones. Only missiles launched in these zones are tracked. -- * Define protected AI groups. -- * F10 radio menu to adjust settings for each player. -- * Alert on missile launch (optional). @@ -16,7 +16,11 @@ -- * Adaptive update of missile-to-player distance. -- * Finite State Machine (FSM) implementation. -- * Easy to use. See examples below. --- +-- +-- === +-- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Functional/FOX) +-- -- === -- -- ### Author: **funkyfranky** @@ -47,7 +51,7 @@ -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. -- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec. -- @field #number dt01 Time step [sec] for missile position updates if distance to target > 1 km and < 5 km. Default 0.1 sec. --- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec. +-- @field #number dt00 Time step [sec] for missile position updates if distance to target < 1 km. Default 0.01 sec. -- @extends Core.Fsm#FSM --- Fox 3! @@ -57,66 +61,66 @@ -- ![Banner Image](..\Presentations\FOX\FOX_Main.png) -- -- # The FOX Concept --- +-- -- As you probably know [Fox](https://en.wikipedia.org/wiki/Fox_\(code_word\)) is a NATO brevity code for launching air-to-air munition. Therefore, the class name is not 100% accurate as this -- script handles air-to-air but also surface-to-air missiles. --- +-- -- # Basic Script --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Training Zones --- +-- -- Players are only protected if they are inside one of the training zones. --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add training zones. -- fox:AddSafeZone(ZONE:New("Training Zone Alpha")) -- fox:AddSafeZone(ZONE:New("Training Zone Bravo")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Launch Zones --- +-- -- Missile launches are only monitored if the shooter is inside the defined launch zone. --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add training zones. -- fox:AddLaunchZone(ZONE:New("Launch Zone SA-10 Krim")) -- fox:AddLaunchZone(ZONE:New("Training Zone Bravo")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Protected AI Groups --- +-- -- Define AI protected groups. These groups cannot be harmed by missiles. --- +-- -- ## Add Individual Groups --- +-- -- -- Create a new missile trainer object. -- fox=FOX:New() --- +-- -- -- Add single protected group(s). -- fox:AddProtectedGroup(GROUP:FindByName("A-10 Protected")) -- fox:AddProtectedGroup(GROUP:FindByName("Yak-40")) --- +-- -- -- Start missile trainer. -- fox:Start() --- +-- -- # Notes --- +-- -- The script needs to be running before you enter an airplane slot. If FOX is not available to you, go back to observers and then join a slot again. --- +-- -- @field #FOX FOX = { ClassName = "FOX", @@ -218,17 +222,17 @@ function FOX:New() -- Inherit everthing from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #FOX - + -- Defaults: self:SetDefaultMissileDestruction(true) self:SetDefaultLaunchAlerts(true) self:SetDefaultLaunchMarks(true) - + -- Explosion/destruction defaults. self:SetExplosionDistance() self:SetExplosionDistanceBigMissiles() self:SetExplosionPower() - + -- Start State. self:SetStartState("Stopped") @@ -350,7 +354,7 @@ function FOX:New() -- @param #string To To state. -- @param #FOX.PlayerData player Player data. - + return self end @@ -368,16 +372,16 @@ function FOX:onafterStart(From, Event, To) -- Handle events: self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Shot) - + if self.Debug then self:HandleEvent(EVENTS.Hit) end - + if self.Debug then self:TraceClass(self.ClassName) self:TraceLevel(2) end - + self:__Status(-20) end @@ -395,7 +399,7 @@ function FOX:onafterStop(From, Event, To) -- Handle events: self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Shot) - + if self.Debug then self:UnhandleEvent(EVENTS.Hit) end @@ -437,18 +441,18 @@ function FOX:SetProtectedGroupSet(groupset) return self end ---- Add a group to the protected set. +--- Add a group to the protected set. Works only with AI! -- @param #FOX self -- @param Wrapper.Group#GROUP group Protected group. -- @return #FOX self function FOX:AddProtectedGroup(group) - + if not self.protectedset then self.protectedset=SET_GROUP:New() end - + self.protectedset:AddGroup(group) - + return self end @@ -483,7 +487,7 @@ end function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass) self.explosiondist2=distance or 500 - + self.bigmissilemass=explosivemass or 50 return self @@ -609,18 +613,18 @@ function FOX:onafterStatus(From, Event, To) -- Get FSM state. local fsmstate=self:GetState() - + local time=timer.getAbsTime() local clock=UTILS.SecondsToClock(time) - + -- Status. if self.verbose>=1 then self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) end - + -- Check missile status. self:_CheckMissileStatus() - + -- Check player status. self:_CheckPlayers() @@ -635,39 +639,39 @@ function FOX:_CheckPlayers() for playername,_playersettings in pairs(self.players) do local playersettings=_playersettings --#FOX.PlayerData - + local unitname=playersettings.unitname local unit=UNIT:FindByName(unitname) - + if unit and unit:IsAlive() then - + local coord=unit:GetCoordinate() - + local issafe=self:_CheckCoordSafe(coord) - - + + if issafe then - + ----------------------------- -- Player INSIDE Safe Zone -- ----------------------------- - + if not playersettings.inzone then self:EnterSafeZone(playersettings) playersettings.inzone=true end - + else - + ------------------------------ -- Player OUTSIDE Safe Zone -- - ------------------------------ - + ------------------------------ + if playersettings.inzone==true then self:ExitSafeZone(playersettings) playersettings.inzone=false end - + end end end @@ -686,7 +690,7 @@ function FOX:_RemoveMissile(missile) table.remove(self.missiles, i) return end - end + end end end @@ -699,7 +703,7 @@ function FOX:_CheckMissileStatus() local inactive={} for i,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData - + local targetname="unkown" if missile.targetUnit then targetname=missile.targetUnit:GetName() @@ -712,14 +716,14 @@ function FOX:_CheckMissileStatus() local mtype=missile.missileType local dtype=missile.missileType local range=UTILS.MetersToNM(missile.missileRange) - + if not active then table.insert(inactive,i) end local heading=self:_GetWeapongHeading(missile.weapon) - + text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName) - + end if #self.missiles==0 then text=text.." none" @@ -728,7 +732,7 @@ function FOX:_CheckMissileStatus() self:I(self.lid..text) end - -- Remove inactive missiles. + -- Remove inactive missiles. for i=#self.missiles,1,-1 do local missile=self.missiles[i] --#FOX.MissileData if missile and not missile.active then @@ -747,31 +751,31 @@ function FOX:_IsProtected(targetunit) if not self.protectedset then return false end - + if targetunit and targetunit:IsAlive() then -- Get Group. local targetgroup=targetunit:GetGroup() - + if targetgroup then local targetname=targetgroup:GetName() - + for _,_group in pairs(self.protectedset:GetSet()) do local group=_group --Wrapper.Group#GROUP - + if group then local groupname=group:GetName() - + -- Target belongs to a protected set. if targetname==groupname then return true end end - + end end end - + return false end @@ -784,22 +788,22 @@ function FOX._FuncTrack(weapon, self, missile) -- Missile coordinate. local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos) - + -- Missile velocity in m/s. local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity()) - + -- Update missile target if necessary. self:GetMissileTarget(missile) - + -- Target unit of the missile. - local target=nil --Wrapper.Unit#UNIT - + local target=nil --Wrapper.Unit#UNIT + if missile.targetUnit then - + ----------------------------------- -- Missile has a specific target -- ----------------------------------- - + if missile.targetPlayer then -- Target is a player. if missile.targetPlayer.destroy==true then @@ -811,13 +815,13 @@ function FOX._FuncTrack(weapon, self, missile) target=missile.targetUnit end end - + else - + ------------------------------------ -- Missile has NO specific target -- - ------------------------------------ - + ------------------------------------ + -- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not. -- That would trigger the distance check right after missile launch if things to wrong. -- @@ -825,165 +829,165 @@ function FOX._FuncTrack(weapon, self, missile) -- * Time check: enable this check after X seconds after missile was fired. What is X? -- * Coalition check. But would not work in training situations where blue on blue is valid! -- * At least enable it for surface-to-air missiles. - + local function _GetTarget(_unit) local unit=_unit --Wrapper.Unit#UNIT - + -- Player position. local playerCoord=unit:GetCoordinate() - - -- Distance. + + -- Distance. local dist=missileCoord:Get3DDistance(playerCoord) - + -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. if dist<=self.explosiondist then return unit - end + end end - + -- Distance to closest player. local mindist=nil - + -- Loop over players. for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + -- Check that player was not the one who launched the missile. if player.unitname~=missile.shooterName then - + -- Player position. local playerCoord=player.unit:GetCoordinate() - - -- Distance. + + -- Distance. local dist=missileCoord:Get3DDistance(playerCoord) - + -- Distance from shooter to player. local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) - + -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. if (mindist==nil or dist=self.bigmissilemass end - + -- If missile is 150 m from target ==> destroy missile if in safe zone. if destroymissile and self:_CheckCoordSafe(targetVec3) then - + -- Destroy missile. - self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", + self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance)) weapon:Destroy() - + -- Missile is not active any more. missile.active=false - + -- Debug smoke. if self.Debug then - missileCoord:SmokeRed() + missileCoord:SmokeRed() end - + -- Create event. self:MissileDestroyed(missile) - + -- Little explosion for the visual effect. if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then missileCoord:Explosion(self.explosionpower) end - + -- Target was a player. if missile.targetPlayer then - + -- Message to target. local text=string.format("Destroying missile. %s", self:_DeadText()) MESSAGE:New(text, 10):ToGroup(target:GetGroup()) - + -- Increase dead counter. missile.targetPlayer.dead=missile.targetPlayer.dead+1 end -- We could disable the tracking here but then the impact function would not be called. --weapon.tracking=false - + else - + -- Time step. - local dt=1.0 + local dt=1.0 if distance>50000 then -- > 50 km dt=self.dt50 --=5.0 @@ -1000,17 +1004,17 @@ function FOX._FuncTrack(weapon, self, missile) -- < 1 km dt=self.dt00 --0.01 end - + -- Set time step. weapon:SetTimeStepTrack(dt) end - + else - + -- No current target. self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName)) weapon:SetTimeStepTrack(0.1) - + end end @@ -1021,25 +1025,25 @@ end -- @param #FOX.MissileData missile Fired missile. function FOX._FuncImpact(weapon, self, missile) - if missile.targetPlayer then - + if missile.targetPlayer then + -- Get human player. local player=missile.targetPlayer - + -- Check for player and distance < 10 km. if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then local text=string.format("Missile defeated. Well done, %s!", player.name) MESSAGE:New(text, 10):ToClient(player.client) - + -- Increase defeated counter. player.defeated=player.defeated+1 end - + end - + -- Missile is not active any more. - missile.active=false - + missile.active=false + --Terminate the timer. self:T(FOX.lid..string.format("Terminating missile track timer.")) weapon.tracking=false @@ -1058,59 +1062,59 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName) self:I(FOX.lid..text) MESSAGE:New(text, 10):ToAllIf(self.Debug) - + -- Loop over players. for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + -- Player position. local playerUnit=player.unit - + -- Check that player is alive and of the opposite coalition. if playerUnit and playerUnit:IsAlive() and player.coalition~=missile.shooterCoalition then - + -- Player missile distance. local distance=playerUnit:GetCoordinate():Get3DDistance(missile.shotCoord) - + -- Player bearing to missile. local bearing=playerUnit:GetCoordinate():HeadingTo(missile.shotCoord) - + -- Alert that missile has been launched. if player.launchalert then - + -- Alert directly targeted players or players that are within missile max range. if (missile.targetPlayer and player.unitname==missile.targetPlayer.unitname) or (distance Target=%s, fuse dist=%s, explosive=%s", tostring(missile.shooterName), tostring(missile.missileType), tostring(missile.missileName), tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive))) - + -- Only track if target was a player or target is protected. Saw the 9M311 missiles have no target! if missile.targetPlayer or self:_IsProtected(missile.targetUnit) or missile.targetName=="unknown" then - + -- Add missile table. table.insert(self.missiles, missile) - + -- Trigger MissileLaunch event. self:__MissileLaunch(0.1, missile) - + end - + end --if _track - + end --- FOX event handler for event hit. @@ -1340,7 +1344,7 @@ end -- @param Core.Event#EVENTDATA EventData function FOX:OnEventHit(EventData) self:T({eventhit = EventData}) - + -- Nil checks. if EventData.Weapon==nil then return @@ -1351,10 +1355,10 @@ function FOX:OnEventHit(EventData) if EventData.TgtUnit==nil then return end - + local weapon=EventData.Weapon local weaponname=weapon:getName() - + for i,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData if missile.missileName==weaponname then @@ -1375,51 +1379,51 @@ end -- @param #string _unitName Name of player unit. function FOX:_AddF10Commands(_unitName) self:F(_unitName) - + -- Get player unit and name. local _unit, playername = self:_GetPlayerUnitAndName(_unitName) - + -- Check for player unit. if _unit and playername then -- Get group and ID. local group=_unit:GetGroup() local gid=group:GetID() - + if group and gid then - + if not self.menuadded[gid] then - + -- Enable switch so we don't do this twice. self.menuadded[gid]=true - + -- Set menu root path. local _rootPath=nil if FOX.MenuF10Root then ------------------------ -- MISSON LEVEL MENUE -- - ------------------------ - + ------------------------ + -- F10/FOX/... _rootPath=FOX.MenuF10Root - + else ------------------------ -- GROUP LEVEL MENUES -- ------------------------ - + -- Main F10 menu: F10/FOX/ if FOX.MenuF10[gid]==nil then FOX.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FOX") end - + -- F10/FOX/... _rootPath=FOX.MenuF10[gid] - + end - - - -------------------------------- + + + -------------------------------- -- F10/F FOX/F1 Help -------------------------------- --local _helpPath=missionCommands.addSubMenuForGroup(gid, "Help", _rootPath) @@ -1430,12 +1434,12 @@ function FOX:_AddF10Commands(_unitName) ------------------------- -- F10/F FOX/ ------------------------- - + missionCommands.addCommandForGroup(gid, "Destroy Missiles On/Off", _rootPath, self._ToggleDestroyMissiles, self, _unitName) -- F1 missionCommands.addCommandForGroup(gid, "Launch Alerts On/Off", _rootPath, self._ToggleLaunchAlert, self, _unitName) -- F2 missionCommands.addCommandForGroup(gid, "Mark Launch On/Off", _rootPath, self._ToggleLaunchMark, self, _unitName) -- F3 missionCommands.addCommandForGroup(gid, "My Status", _rootPath, self._MyStatus, self, _unitName) -- F4 - + end else self:E(self.lid..string.format("ERROR: Could not find group or group ID in AddF10Menu() function. Unit name: %s.", _unitName or "unknown")) @@ -1452,23 +1456,23 @@ end -- @param #string _unitname Name of the player unit. function FOX:_MyStatus(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + local m,mtext=self:_GetTargetMissiles(playerData.name) - + local text=string.format("Status of player %s:\n", playerData.name) local safe=self:_CheckCoordSafe(playerData.unit:GetCoordinate()) - + text=text..string.format("Destroy missiles? %s\n", tostring(playerData.destroy)) text=text..string.format("Launch alert? %s\n", tostring(playerData.launchalert)) text=text..string.format("Launch marks? %s\n", tostring(playerData.marklaunch)) @@ -1476,9 +1480,9 @@ function FOX:_MyStatus(_unitname) text=text..string.format("Missiles defeated: %d\n", playerData.defeated) text=text..string.format("Missiles destroyed: %d\n", playerData.dead) text=text..string.format("Me target: %d\n%s", m, mtext) - + MESSAGE:New(text, 10, nil, true):ToClient(playerData.client) - + end end end @@ -1494,12 +1498,12 @@ function FOX:_GetTargetMissiles(playername) local n=0 for _,_missile in pairs(self.missiles) do local missile=_missile --#FOX.MissileData - + if missile.targetPlayer and missile.targetPlayer.name==playername then n=n+1 text=text..string.format("Type %s: active %s\n", missile.missileType, tostring(missile.active)) end - + end return n,text @@ -1510,21 +1514,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleLaunchAlert(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.launchalert=not playerData.launchalert - + -- Inform player. local text="" if playerData.launchalert==true then @@ -1533,7 +1537,7 @@ function FOX:_ToggleLaunchAlert(_unitname) text=string.format("%s, missile launch alerts are now DISABLED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1543,21 +1547,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleLaunchMark(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.marklaunch=not playerData.marklaunch - + -- Inform player. local text="" if playerData.marklaunch==true then @@ -1566,7 +1570,7 @@ function FOX:_ToggleLaunchMark(_unitname) text=string.format("%s, missile launch marks are now DISABLED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1577,21 +1581,21 @@ end -- @param #string _unitname Name of the player unit. function FOX:_ToggleDestroyMissiles(_unitname) self:F2(_unitname) - + -- Get player unit and player name. local unit, playername = self:_GetPlayerUnitAndName(_unitname) - + -- Check if we have a player. if unit and playername then - - -- Player data. + + -- Player data. local playerData=self.players[playername] --#FOX.PlayerData - + if playerData then - + -- Invert state. playerData.destroy=not playerData.destroy - + -- Inform player. local text="" if playerData.destroy==true then @@ -1600,7 +1604,7 @@ function FOX:_ToggleDestroyMissiles(_unitname) text=string.format("%s, incoming missiles will NOT be DESTROYED.", playerData.name) end MESSAGE:New(text, 5):ToClient(playerData.client) - + end end end @@ -1622,9 +1626,9 @@ function FOX:_DeadText() texts[4]="Well, I guess that was it!" texts[5]="Bye, bye!" texts[6]="Cheers buddy, was nice knowing you!" - + local r=math.random(#texts) - + return texts[r] end @@ -1637,9 +1641,9 @@ function FOX:_CheckCoordSafe(coord) -- No safe zones defined ==> Everything is safe. if #self.safezones==0 then - return true + return true end - + -- Loop over all zones. for _,_zone in pairs(self.safezones) do local zone=_zone --Core.Zone#ZONE @@ -1662,9 +1666,9 @@ function FOX:_CheckCoordLaunch(coord) -- No safe zones defined ==> Everything is safe. if #self.launchzones==0 then - return true + return true end - + -- Loop over all zones. for _,_zone in pairs(self.launchzones) do local zone=_zone --Core.Zone#ZONE @@ -1679,50 +1683,50 @@ function FOX:_CheckCoordLaunch(coord) return false end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param DCS#Weapon weapon The weapon. -- @return #number Heading of weapon in degrees or -1. function FOX:_GetWeapongHeading(weapon) if weapon and weapon:isExist() then - + local wp=weapon:getPosition() - + local wph = math.atan2(wp.x.z, wp.x.x) - + if wph < 0 then wph=wph+2*math.pi end - + wph=math.deg(wph) - + return wph end return -1 end ---- Tell player notching headings. +--- Tell player notching headings. -- @param #FOX self -- @param #FOX.PlayerData playerData Player data. -- @param DCS#Weapon weapon The weapon. function FOX:_SayNotchingHeadings(playerData, weapon) if playerData and playerData.unit and playerData.unit:IsAlive() then - + local nr, nl=self:_GetNotchingHeadings(weapon) - + if nr and nl then - local text=string.format("Notching heading %03d° or %03d°", nr, nl) + local text=string.format("Notching heading %03d° or %03d°", nr, nl) MESSAGE:New(text, 5, "FOX"):ToClient(playerData.client) end - + end end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param DCS#Weapon weapon The weapon. -- @return #number Notching heading right, i.e. missile heading +90°. @@ -1730,22 +1734,22 @@ end function FOX:_GetNotchingHeadings(weapon) if weapon then - + local hdg=self:_GetWeapongHeading(weapon) - + local hdg1=hdg+90 if hdg1>360 then hdg1=hdg1-360 end - + local hdg2=hdg-90 if hdg2<0 then hdg2=hdg2+360 end - + return hdg1, hdg2 - end - + end + return nil, nil end @@ -1755,14 +1759,14 @@ end -- @return #FOX.PlayerData Player data. function FOX:_GetPlayerFromUnitname(unitName) - for _,_player in pairs(self.players) do + for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + if player.unitname==unitName then return player end end - + return nil end @@ -1777,20 +1781,20 @@ function FOX:_GetPlayerFromUnit(unit) -- Name of the unit local unitname=unit:GetName() - for _,_player in pairs(self.players) do + for _,_player in pairs(self.players) do local player=_player --#FOX.PlayerData - + if player.unitname==unitname then return player end end end - + return nil end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FOX self -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player or nil. @@ -1799,31 +1803,31 @@ function FOX:_GetPlayerUnitAndName(_unitName) self:F2(_unitName) if _unitName ~= nil then - + -- Get DCS unit from its name. local DCSunit=Unit.getByName(_unitName) - + if DCSunit then - + -- Get player name if any. local playername=DCSunit:getPlayerName() - + -- Unit object. local unit=UNIT:Find(DCSunit) - + -- Debug. self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) - + -- Check if enverything is there. if DCSunit and unit and playername then self:T(self.lid..string.format("Found DCS unit %s with player %s.", tostring(_unitName), tostring(playername))) return unit, playername end - + end - + end - + -- Return nil if we could not find a player. return nil,nil end diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b8c7218cf..15d23ba84 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -78,7 +78,8 @@ -- ### Authors: **FlightControl** -- -- ### Contributions: --- +-- +-- * **Applevangelist**: Additional functionality, fixes. -- * **Wingthor (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **Whisper**: Testing and Advice. @@ -116,11 +117,13 @@ -- 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.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s. --- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}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}. -- -- local Scoring = SCORING:New( "Scoring File" ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- 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. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. @@ -226,7 +229,7 @@ SCORING = { ClassID = 0, Players = {}, AutoSave = true, - version = "1.17.1" + version = "1.18.2" } local _SCORINGCoalition = { @@ -428,6 +431,31 @@ function SCORING:AddScoreGroup( ScoreGroup, Score ) return self 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. -- 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. @@ -1030,7 +1058,7 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT - -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + -- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() -- if this fails for some reason, set a good default value @@ -1141,7 +1169,7 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT - -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + -- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() -- if this fails for some reason, set a good default value @@ -1288,17 +1316,17 @@ function SCORING:_EventOnDeadOrCrash( Event ) TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 - self:OnKillPvP(Player, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) + --self:OnKillPvP(PlayerName, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player - self:OnKillPvP(Player, TargetPlayerName, true) + self:OnKillPvP(PlayerName, TargetPlayerName, true) MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else - self:OnKillPvE(Player, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) + self:OnKillPvE(PlayerName, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty) MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) @@ -1326,14 +1354,14 @@ function SCORING:_EventOnDeadOrCrash( Event ) else Player.PlayerKills = 1 end - self:OnKillPvP(Player, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) + self:OnKillPvP(PlayerName, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else - self:OnKillPvE(Player, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) + self:OnKillPvE(PlayerName, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore) MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) @@ -1935,23 +1963,23 @@ end --- Handles the event when one player kill another player -- @param #SCORING self --- @param #PLAYER Player the ataching player --- @param #string TargetPlayerName the name of the killed player --- @param #bool IsTeamKill true if this kill was a team kill --- @param #number TargetThreatLevel Thread level of the target --- @param #number PlayerThreatLevelThread level of the player +-- @param #string PlayerName The attacking player +-- @param #string TargetPlayerName The name of the killed player +-- @param #boolean IsTeamKill true if this kill was a team kill +-- @param #number TargetThreatLevel Threat level of the target +-- @param #number PlayerThreatLevel Threat level of the player -- @param #number Score The score based on both threat levels -function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) +function SCORING:OnKillPvP(PlayerName, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) end --- Handles the event when one player kill another player -- @param #SCORING self --- @param #PLAYER Player the ataching player +-- @param #string PlayerName The attacking player -- @param #string TargetUnitName the name of the killed unit --- @param #bool IsTeamKill true if this kill was a team kill --- @param #number TargetThreatLevel Thread level of the target --- @param #number PlayerThreatLevelThread level of the player +-- @param #boolean IsTeamKill true if this kill was a team kill +-- @param #number TargetThreatLevel Threat level of the target +-- @param #number PlayerThreatLevel Threat level of the player -- @param #number Score The score based on both threat levels -function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) +function SCORING:OnKillPvE(PlayerName, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) -end \ No newline at end of file +end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 7b1958fbb..c50e2d310 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -320,9 +320,6 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG end 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 _targetgroup = nil local _targetgroupname = "none" diff --git a/Moose Development/Moose/Functional/Stratego.lua b/Moose Development/Moose/Functional/Stratego.lua index 3c48cb7a0..2655500f8 100644 --- a/Moose Development/Moose/Functional/Stratego.lua +++ b/Moose Development/Moose/Functional/Stratego.lua @@ -41,6 +41,7 @@ -- @field #boolean usebudget -- @field #number CaptureUnits -- @field #number CaptureThreatlevel +-- @field #boolean ExcludeShips -- @extends Core.Base#BASE -- @extends Core.Fsm#FSM @@ -176,7 +177,7 @@ STRATEGO = { debug = false, drawzone = false, markzone = false, - version = "0.2.4", + version = "0.2.5", portweight = 3, POIweight = 1, maxrunways = 3, @@ -195,6 +196,7 @@ STRATEGO = { usebudget = false, CaptureUnits = 3, CaptureThreatlevel = 1, + ExcludeShips = true, } --- @@ -256,6 +258,7 @@ function STRATEGO:New(Name,Coalition,MaxDist) self.maxdist = MaxDist or 150 -- km self.disttable = {} self.routexists = {} + self.ExcludeShips = true self.lid = string.format("STRATEGO %s %s | ",self.name,self.version) @@ -427,6 +430,7 @@ function STRATEGO:AnalyseBases() self.bases:ForEach( function(afb) local ab = afb -- Wrapper.Airbase#AIRBASE + if self.ExcludeShips and ab:IsShip() then return end local abname = ab:GetName() local runways = ab:GetRunways() local numrwys = #runways @@ -438,14 +442,14 @@ function STRATEGO:AnalyseBases() local coa = ab:GetCoalition() if coa == nil then return end -- Spawned FARPS issue - these have no tangible data coa = coa+1 - local abtype = "AIRBASE" + local abtype = STRATEGO.Type.AIRBASE if ab:IsShip() then numrwys = 1 - abtype = "SHIP" + abtype = STRATEGO.Type.SHIP end if ab:IsHelipad() then numrwys = 1 - abtype = "FARP" + abtype = STRATEGO.Type.FARP end local coord = ab:GetCoordinate() if debug then @@ -481,10 +485,10 @@ function STRATEGO:UpdateNodeCoalitions() local newtable = {} for _id,_data in pairs(self.airbasetable) do local data = _data -- #STRATEGO.Data - if data.type == "AIRBASE" or data.type == "FARP" then - data.coalition = AIRBASE:FindByName(data.name):GetCoalition() + if data.type == STRATEGO.Type.AIRBASE or data.type == STRATEGO.Type.FARP or data.type == STRATEGO.Type.SHIP then + data.coalition = AIRBASE:FindByName(data.name):GetCoalition() or 0 else - data.coalition = data.opszone:GetOwner() + data.coalition = data.opszone:GetOwner() or 0 end newtable[_id] = _data end @@ -937,11 +941,13 @@ function STRATEGO:FindClosestConsolidationTarget(Startpoint,BaseWeight) local cname = self.easynames[tname] local targetweight = self.airbasetable[cname].baseweight coa = self.airbasetable[cname].coalition + --self:T("Start -> End: "..startpoint.." -> "..cname) if (dist < shortest) and (coa ~= self.coalition) and (BaseWeight >= targetweight) then + self:T("Found Consolidation Target: "..cname) shortest = dist target = cname weight = self.airbasetable[cname].weight - coa = self.airbasetable[cname].coalition + coa = coa end end end @@ -974,8 +980,9 @@ function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight) local coa = self.airbasetable[cname].coalition local tweight = self.airbasetable[cname].baseweight local ttweight = self.airbasetable[cname].weight - self:T("Start -> End: "..startpoint.." -> "..cname) + --self:T("Start -> End: "..startpoint.." -> "..cname) if (dist < shortest) and (coa ~= self.coalition) and (tweight >= Weight) then + self:T("Found Strategic Target: "..cname) shortest = dist target = cname weight = self.airbasetable[cname].weight @@ -996,38 +1003,31 @@ function STRATEGO:FindStrategicTargets() local data = _data -- #STRATEGO.Data if data.coalition == self.coalition then local dist, name, points, coa = self:FindClosestStrategicTarget(data.name,data.weight) - if coa == coalition.side.NEUTRAL and points ~= 0 then - local fpoints = points + self.NeutralBenefit - local tries = 1 - while targets[fpoints] or tries < 100 do - fpoints = points + (self.NeutralBenefit+math.random(1,100)) - tries = tries + 1 - end - targets[fpoints] = { - name = name, - dist = dist, - points = fpoints, - coalition = coa, - coalitionname = UTILS.GetCoalitionName(coa), - coordinate = self.airbasetable[name].coord, - } + if points > 0 then + self:T({dist=dist, name=name, points=points, coa=coa}) end - local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE - if coa == enemycoa and points ~= 0 then - local fpoints = points - local tries = 1 - while targets[fpoints] or tries < 100 do - fpoints = points + (math.random(1,100)) - tries = tries + 1 + if points ~= 0 then + local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE + self:T("Enemycoa = "..enemycoa) + if coa == coalition.side.NEUTRAL then + local tdata = {} + tdata.name = name + tdata.dist = dist + tdata.points = points + self.NeutralBenefit + tdata.coalition = coa + tdata.coalitionname = UTILS.GetCoalitionName(coa) + tdata.coordinate = self.airbasetable[name].coord + table.insert(targets,tdata) + else + local tdata = {} + tdata.name = name + tdata.dist = dist + tdata.points = points + tdata.coalition = coa + tdata.coalitionname = UTILS.GetCoalitionName(coa) + tdata.coordinate = self.airbasetable[name].coord + table.insert(targets,tdata) end - targets[fpoints] = { - name = name, - dist = dist, - points = fpoints, - coalition = coa, - coalitionname = UTILS.GetCoalitionName(coa), - coordinate = self.airbasetable[name].coord, - } end end end @@ -1044,38 +1044,31 @@ function STRATEGO:FindConsolidationTargets() local data = _data -- #STRATEGO.Data if data.coalition == self.coalition then local dist, name, points, coa = self:FindClosestConsolidationTarget(data.name,self.maxrunways-1) - if coa == coalition.side.NEUTRAL and points ~= 0 then - local fpoints = points + self.NeutralBenefit - local tries = 1 - while targets[fpoints] or tries < 100 do - fpoints = points - (self.NeutralBenefit+math.random(1,100)) - tries = tries + 1 - end - targets[fpoints] = { - name = name, - dist = dist, - points = fpoints, - coalition = coa, - coalitionname = UTILS.GetCoalitionName(coa), - coordinate = self.airbasetable[name].coord, - } + if points > 0 then + self:T({dist=dist, name=name, points=points, coa=coa}) end - local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE - if coa == enemycoa and points ~= 0 then - local fpoints = points - local tries = 1 - while targets[fpoints] or tries < 100 do - fpoints = points - (math.random(1,100)) - tries = tries + 1 + if points ~= 0 then + local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE + self:T("Enemycoa = "..enemycoa) + if coa == coalition.side.NEUTRAL then + local tdata = {} + tdata.name = name + tdata.dist = dist + tdata.points = points + self.NeutralBenefit + tdata.coalition = coa + tdata.coalitionname = UTILS.GetCoalitionName(coa) + tdata.coordinate = self.airbasetable[name].coord + table.insert(targets,tdata) + else + local tdata = {} + tdata.name = name + tdata.dist = dist + tdata.points = points + tdata.coalition = coa + tdata.coalitionname = UTILS.GetCoalitionName(coa) + tdata.coordinate = self.airbasetable[name].coord + table.insert(targets,tdata) end - targets[fpoints] = { - name = name, - dist = dist, - points = fpoints, - coalition = coa, - coalitionname = UTILS.GetCoalitionName(coa), - coordinate = self.airbasetable[name].coord, - } end end end @@ -1245,13 +1238,15 @@ end -- @return #table Target Table with #STRATEGO.Target data or nil if none found. function STRATEGO:FindAffordableStrategicTarget() self:T(self.lid.."FindAffordableStrategicTarget") - local targets = self:FindStrategicTargets() -- #table of #STRATEGO.Target + local Stargets = self:FindStrategicTargets() -- #table of #STRATEGO.Target + --UTILS.PrintTableToLog(Stargets,1) local budget = self.Budget --local leftover = self.Budget - local target = nil -- #STRATEGO.Target + local ftarget = nil -- #STRATEGO.Target local Targets = {} - for _,_data in pairs(targets) do + for _,_data in pairs(Stargets) do local data = _data -- #STRATEGO.Target + self:T("Considering Strategic Target "..data.name) --if data.points <= budget and budget-data.points < leftover then if data.points <= budget then --leftover = budget-data.points @@ -1259,14 +1254,18 @@ function STRATEGO:FindAffordableStrategicTarget() self:T(self.lid.."Affordable strategic target: "..data.name) end end - if not targets then + if #Targets == 0 then self:T(self.lid.."No suitable target found!") return nil end - target = Targets[math.random(1,#Targets)] - if target then - self:T(self.lid.."Final affordable strategic target: "..target.name) - return target + if #Targets > 1 then + ftarget = Targets[math.random(1,#Targets)] + else + ftarget = Targets[1] + end + if ftarget then + self:T(self.lid.."Final affordable strategic target: "..ftarget.name) + return ftarget else return nil end @@ -1277,13 +1276,15 @@ end -- @return #table Target Table with #STRATEGO.Target data or nil if none found. function STRATEGO:FindAffordableConsolidationTarget() self:T(self.lid.."FindAffordableConsolidationTarget") - local targets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target + local Ctargets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target + --UTILS.PrintTableToLog(Ctargets,1) local budget = self.Budget --local leftover = self.Budget - local target = nil -- #STRATEGO.Target + local ftarget = nil -- #STRATEGO.Target local Targets = {} - for _,_data in pairs(targets) do + for _,_data in pairs(Ctargets) do local data = _data -- #STRATEGO.Target + self:T("Considering Consolidation Target "..data.name) --if data.points <= budget and budget-data.points < leftover then if data.points <= budget then --leftover = budget-data.points @@ -1291,14 +1292,18 @@ function STRATEGO:FindAffordableConsolidationTarget() self:T(self.lid.."Affordable consolidation target: "..data.name) end end - if not targets then + if #Targets == 0 then self:T(self.lid.."No suitable target found!") return nil end - target = Targets[math.random(1,#Targets)] - if target then - self:T(self.lid.."Final affordable consolidation target: "..target.name) - return target + if #Targets > 1 then + ftarget = Targets[math.random(1,#Targets)] + else + ftarget = Targets[1] + end + if ftarget then + self:T(self.lid.."Final affordable consolidation target: "..ftarget.name) + return ftarget else return nil end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 1d2adfbd2..745bf817c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -9755,7 +9755,7 @@ function AIRBOSS:_Groove( playerData ) local glideslopeError = groovedata.GSE local AoA = groovedata.AoA - if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then + if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 and playerData.unit:IsInZone( self:_GetZoneLineup() )) then -- Start time in groove playerData.TIG0 = timer.getTime() diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index bf760dad9..9978f55e5 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1,5 +1,5 @@ --- **Ops** - Enhanced Ground Group. --- +-- -- ## Main Features: -- -- * Patrol waypoints *ad infinitum* @@ -21,7 +21,7 @@ -- === -- -- ### Author: **funkyfranky** --- +-- -- == -- @module Ops.ArmyGroup -- @image OPS_ArmyGroup.png @@ -44,11 +44,11 @@ --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge -- -- === --- +-- -- # The ARMYGROUP Concept --- +-- -- This class enhances ground groups. --- +-- -- @field #ARMYGROUP ARMYGROUP = { ClassName = "ARMYGROUP", @@ -74,7 +74,7 @@ ARMYGROUP.version="1.0.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Suppression of fire. +-- TODO: Suppression of fire. -- TODO: Check if group is mobile. -- TODO: F10 menu. -- DONE: Retreat. @@ -89,20 +89,20 @@ ARMYGROUP.version="1.0.1" -- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #ARMYGROUP self function ARMYGROUP:New(group) - + -- First check if we already have an OPS group for this group. local og=_DATABASE:GetOpsGroup(group) if og then og:I(og.lid..string.format("WARNING: OPS group already exists in data base!")) return og end - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #ARMYGROUP - + -- Set some string id for output to DCS.log file. self.lid=string.format("ARMYGROUP %s | ", self.groupname) - + -- Defaults self:SetDefaultROE() self:SetDefaultAlarmstate() @@ -116,20 +116,20 @@ function ARMYGROUP:New(group) -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Cruise along the given route of waypoints. - + self:AddTransition("*", "RTZ", "Returning") -- Group is returning to (home) zone. self:AddTransition("Holding", "Returned", "Returned") -- Group is returned to (home) zone, e.g. when unloaded from carrier. self:AddTransition("Returning", "Returned", "Returned") -- Group is returned to (home) zone. - + self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. - + self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. - + self:AddTransition("*", "Suppressed", "*") -- Group is suppressed self:AddTransition("*", "Unsuppressed", "*") -- Group is unsuppressed. - + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state @@ -138,7 +138,7 @@ function ARMYGROUP:New(group) self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled. self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed. self:AddTransition("*", "Rearmed", "Cruising") -- Group was rearmed. - + ------------------------ --- Pseudo Functions --- ------------------------ @@ -163,7 +163,7 @@ function ARMYGROUP:New(group) -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots until next waypoint is reached. - -- @param #number Formation Formation. + -- @param #number Formation Formation. --- Triggers the FSM event "FullStop". @@ -261,7 +261,7 @@ function ARMYGROUP:New(group) -- @function [parent=#ARMYGROUP] __Retreat -- @param #ARMYGROUP self -- @param Core.Zone#ZONE_BASE Zone (Optional) Zone where to retreat. Default is the closest retreat zone. - -- @param #number Formation (Optional) Formation of the group. + -- @param #number Formation (Optional) Formation of the group. -- @param #number delay Delay in seconds. --- On after "Retreat" event. @@ -395,29 +395,29 @@ function ARMYGROUP:New(group) -- Init waypoints. self:_InitWaypoints() - + -- Initialize the group. self:_InitGroup() - + -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) - self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.Hit, self.OnEventHit) - + -- Start the status monitoring. self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) - + -- Start queue update timer. - self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) - + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) + -- Start check zone timer. self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30) - + -- Add OPSGROUP to _DATABASE. _DATABASE:AddOpsGroup(self) - - return self + + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -492,19 +492,19 @@ end function ARMYGROUP:AddTaskBarrage(Clock, Heading, Alpha, Altitude, Radius, Nshots, WeaponType, Prio) Heading=Heading or 0 - + Alpha=Alpha or 60 - + Altitude=Altitude or 100 - + local distance=Altitude/math.tan(math.rad(Alpha)) - + local a=self:GetVec2() - + local vec2=UTILS.Vec2Translate(a, distance, Heading) - + --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("Fire At Point",ReadOnly,Text) - + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, vec2, Radius, Nshots, WeaponType, Altitude) local task=self:AddTask(DCStask, Clock, nil, Prio) @@ -596,28 +596,28 @@ end -- @param #number Tmax (Optional) Maximum time a group will be suppressed. Default is 25 seconds. -- @return #ARMYGROUP self function ARMYGROUP:SetSuppressionOn(Tave, Tmin, Tmax) - + -- Activate suppression. self.suppressionOn=true -- Minimum suppression time is input or default 5 sec (but at least 1 second). self.TsuppressMin=Tmin or 1 self.TsuppressMin=math.max(self.TsuppressMin, 1) - + -- Maximum suppression time is input or default but at least Tmin. self.TsuppressMax=Tmax or 15 self.TsuppressMax=math.max(self.TsuppressMax, self.TsuppressMin) - + -- Expected suppression time is input or default but at leat Tmin and at most Tmax. self.TsuppressAve=Tave or 10 self.TsuppressAve=math.max(self.TsuppressMin) self.TsuppressAve=math.min(self.TsuppressMax) - + -- Debug Info self:T(self.lid..string.format("Set ave suppression time to %d seconds.", self.TsuppressAve)) self:T(self.lid..string.format("Set min suppression time to %d seconds.", self.TsuppressMin)) self:T(self.lid..string.format("Set max suppression time to %d seconds.", self.TsuppressMax)) - + return self end @@ -656,15 +656,15 @@ end -- @return #boolean If true, group is on a combat ready. function ARMYGROUP:IsCombatReady() local combatready=true - + if self:IsRearming() or self:IsRetreating() or self:IsOutOfAmmo() or self:IsEngaging() or self:IsDead() or self:IsStopped() or self:IsInUtero() then combatready=false end - + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsLoaded() or self:IsCargo() or self:IsCarrier() then combatready=false end - + return combatready end @@ -679,37 +679,37 @@ function ARMYGROUP:Status() -- FSM state. local fsmstate=self:GetState() - + -- Is group alive? local alive=self:IsAlive() -- Check that group EXISTS and is ACTIVE. if alive then - -- Update position etc. + -- Update position etc. self:_UpdatePosition() - + -- Check if group has detected any units. self:_CheckDetectedUnits() - + -- Check ammo status. self:_CheckAmmoStatus() - + -- Check damage of elements and group. self:_CheckDamage() -- Check if group got stuck. self:_CheckStuck() - + -- Update engagement. if self:IsEngaging() then self:_UpdateEngageTarget() end - + -- Check if group is waiting. if self:IsWaiting() then if self.Twaiting and self.dTwait then - if timer.getAbsTime()>self.Twaiting+self.dTwait then + if timer.getAbsTime()>self.Twaiting+self.dTwait then self.Twaiting=nil self.dTwait=nil if self:_CountPausedMissions()>0 then @@ -720,46 +720,46 @@ function ARMYGROUP:Status() end end end - - + + -- Get current mission (if any). local mission=self:GetMissionCurrent() - + -- If mission, check if DCS task needs to be updated. if mission and mission.updateDCSTask then - + if mission.type==AUFTRAG.Type.CAPTUREZONE then - + -- Get task. local Task=mission:GetGroupWaypointTask(self) - + -- Update task: Engage or get new zone. if mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING or mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.STARTED then self:_UpdateTask(Task, mission) end - + end - - end + + end else -- Check damage of elements and group. - self:_CheckDamage() + self:_CheckDamage() end - + -- Check that group EXISTS. if alive~=nil then - + if self.verbose>=1 then -- Number of elements. local nelem=self:CountElements() local Nelem=#self.elements - + -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() - + -- ROE and Alarm State. local roe=self:GetROE() or -1 local als=self:GetAlarmstate() or -1 @@ -771,43 +771,43 @@ function ARMYGROUP:Status() local wpuidNext=self:GetWaypointUIDFromIndex(wpidxNext) or 0 local wpN=#self.waypoints or 0 local wpF=tostring(self.passedfinalwp) - + -- Speed. local speed=UTILS.MpsToKnots(self.velocity or 0) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) - + -- Altitude. local alt=self.position and self.position.y or 0 - + -- Heading in degrees. - local hdg=self.heading or 0 - + local hdg=self.heading or 0 + -- TODO: GetFormation function. local formation=self.option.Formation or "unknown" - + -- Life points. local life=self.life or 0 - - -- Total ammo. + + -- Total ammo. local ammo=self:GetAmmoTot().Total - + -- Detected units. - local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" - + local ndetected=self.detectionOn and tostring(self.detectedunits:Count()) or "Off" + -- Get cargo weight. local cargo=0 for _,_element in pairs(self.elements) do local element=_element --Ops.OpsGroup#OPSGROUP.Element cargo=cargo+element.weightCargo end - + -- Info text. local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) [%s] | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", fsmstate, nelem, Nelem, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, formation, hdg, ammo, ndetected, cargo) self:I(self.lid..text) - + end - + else -- Info text. @@ -815,13 +815,13 @@ function ARMYGROUP:Status() local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) self:I(self.lid..text) end - + end --- -- Elements --- - + if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do @@ -831,7 +831,7 @@ function ARMYGROUP:Status() local status=element.status local unit=element.unit local life,life0=self:GetLifePoints(element) - + local life0=element.life0 -- Get ammo. @@ -861,12 +861,12 @@ function ARMYGROUP:Status() end end - - + + --- -- Cargo --- - + self:_CheckCargoTransport() @@ -916,7 +916,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Weight = %.1f kg\n", self:GetWeightTotal()) text=text..string.format("Cargo bay = %.1f kg\n", self:GetFreeCargobay()) - text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) + text=text..string.format("Has EPLRS = %s\n", tostring(self.isEPLRS)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) @@ -929,47 +929,47 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update position. self:_UpdatePosition() - + -- Not dead or destroyed yet. self.isDead=false - self.isDestroyed=false + self.isDestroyed=false if self.isAI then - + -- Set default ROE. self:SwitchROE(self.option.ROE) - + -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) - + -- Set emission. self:SwitchEmission(self.option.Emission) - + -- Set default EPLRS. self:SwitchEPLRS(self.option.EPLRS) -- Set default Invisible. - self:SwitchInvisible(self.option.Invisible) + self:SwitchInvisible(self.option.Invisible) -- Set default Immortal. self:SwitchImmortal(self.option.Immortal) - + -- Set TACAN to default. self:_SwitchTACAN() - + -- Turn on the radio. if self.radioDefault then self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) else self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, true) end - + -- Formation if not self.option.Formation then -- Will be set in update route. --self.option.Formation=self.optionDefault.Formation end - + -- Number of waypoints. local Nwp=#self.waypoints @@ -984,7 +984,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) end end - + end --- On before "UpdateRoute" event. @@ -1019,9 +1019,9 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) return false elseif self:IsEngaging() then self:T(self.lid.."Update route allowed. Group is engaging!") - return true + return true end - + -- Check for a current task. if self.taskcurrent>0 then @@ -1040,7 +1040,7 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) self:T2(self.lid.."Allowing update route for Task: Relocate Cohort") elseif task.dcstask.id==AUFTRAG.SpecialTask.REARMING then -- For relocate - self:T2(self.lid.."Allowing update route for Task: Rearming") + self:T2(self.lid.."Allowing update route for Task: Rearming") else local taskname=task and task.description or "No description" self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname))) @@ -1069,8 +1069,8 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Try again? if trepeat then self:__UpdateRoute(trepeat, n) - end - + end + return allowed end @@ -1087,7 +1087,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) - + -- Max index. N=N or #self.waypoints N=math.min(N, #self.waypoints) @@ -1095,22 +1095,22 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Debug info. local text=string.format("Update route state=%s: n=%s, N=%s, Speed=%s, Formation=%s", self:GetState(), tostring(n), tostring(N), tostring(Speed), tostring(Formation)) self:T(self.lid..text) - + -- Waypoints including addtional wp onroad. local waypoints={} - + -- Next waypoint. local wp=self.waypoints[n] --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Current position. local coordinate=self:GetCoordinate() - + -- Road coordinate. local coordRoad=coordinate:GetClosestPointToRoad() - + -- Road distance. local roaddist=coordinate:Get2DDistance(coordRoad) - + -- Formation at the current position. local formation0=wp.action if formation0==ENUMS.Formation.Vehicle.OnRoad then @@ -1123,108 +1123,108 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) formation0=ENUMS.Formation.Vehicle.OnRoad end end - + -- Debug --env.info(self.lid.."FF formation0="..tostring(formation0)) -- Current point. local current=coordinate:WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) table.insert(waypoints, 1, current) - + -- Check if route consists of more than one waypoint (otherwise we have no previous waypoint) if N-n>0 then - + -- Loop over waypoints. for j=n, N do - + -- Index of previous waypoint. local i=j-1 - + -- If we go to the first waypoint j=1 ==> i=0, so we take the last waypoint passed. E.g. when adinfinitum and passed final waypoint. if i==0 then i=self.currentwp end - + -- Next waypoint. We create a copy because we need to modify it. local wp=UTILS.DeepCopy(self.waypoints[j]) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Previous waypoint. Index is i and not i-1 because we added the current position. local wp0=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Debug if false and self.attribute==GROUP.Attribute.GROUND_APC then local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s", i, wp.action, wp0.action) env.info(text) end - + -- Speed. if Speed then wp.speed=UTILS.KnotsToMps(tonumber(Speed)) else -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if wp.speed<0.1 then + if wp.speed<0.1 then wp.speed=UTILS.KmphToMps(self.speedCruise) end end - + -- Formation. if self.formationPerma then wp.action=self.formationPerma - elseif Formation then + elseif Formation then wp.action=Formation end - + -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then - + -- Add "On Road" waypoint in between. local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Debug --wp0.roadcoord:MarkToAll(self.lid.." Added road wp near "..tostring(wproad.action)) - + -- Insert road waypoint. table.insert(waypoints, wproad) end - + -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then - + -- The real waypoint is actually off road. wp.action=ENUMS.Formation.Vehicle.OffRoad - + -- Add "On Road" waypoint in between. local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Debug --wp.roadcoord:MarkToAll(self.lid.." Added road wp far "..tostring(wproad.action)) - + -- Insert road waypoint. table.insert(waypoints, wproad) end - + -- Debug --wp.coordinate:MarkToAll(self.lid.." Added wp actual"..tostring(wp.action)) - + -- Add waypoint. table.insert(waypoints, wp) end - + else --- -- This is the case, where we have only one WP left. -- Could be because we had only one WP and did a detour (temp waypoint, which was deleted). - --- + --- -- Next waypoint. local wp=UTILS.DeepCopy(self.waypoints[n]) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Speed. if wp.speed<0.1 then wp.speed=UTILS.KmphToMps(self.speedCruise) end - + -- Formation. local formation=wp.action if self.formationPerma then @@ -1232,94 +1232,94 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) elseif Formation then formation=Formation end - + -- Debug --env.info(self.lid..string.format("FF Formation %s", formation)) - + -- Add road waypoint. if formation==ENUMS.Formation.Vehicle.OnRoad then - + if roaddist>10 then - + -- Add "On Road" waypoint in between. local wproad=coordRoad:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Debug --coordRoad:MarkToAll(self.lid.." Added road wp near "..tostring(wp.action)) - + -- Insert road waypoint. table.insert(waypoints, wproad) - + end - + if wp.roaddist>10 then - + -- Add "On Road" waypoint in between. local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint -- Debug --wp.roadcoord:MarkToAll(self.lid.." Added road wp far "..tostring(wp.action)) - + -- Insert road waypoint. table.insert(waypoints, wproad) - + end - + end - + -- Waypoint set set to on-road but lies off-road. We set it to off-road. the on-road wp has been inserted. if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then wp.action=ENUMS.Formation.Vehicle.OffRoad end - + -- Debug --wp.coordinate:MarkToAll(self.lid.." Added coord "..tostring(wp.action)) - + -- Add actual waypoint. table.insert(waypoints, wp) - + end - + -- First (next wp). local wp=waypoints[1] --Ops.OpsGroup#OPSGROUP.Waypoint - + -- Current set formation. self.option.Formation=wp.action - + -- Current set speed in m/s. self.speedWp=wp.speed - + -- Debug output. if self.verbose>=10 then --or self.attribute==GROUP.Attribute.GROUND_APC then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint - + local text=string.format("WP #%d UID=%d Formation=%s: Speed=%d m/s, Alt=%d m, Type=%s", i, wp.uid and wp.uid or -1, wp.action, wp.speed, wp.alt, wp.type) - + local coord=COORDINATE:NewFromWaypoint(wp):MarkToAll(text) self:I(text) - + end end if self:IsEngaging() or not self.passedfinalwp then - + -- Debug info. - self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", + self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", self.currentwp, n, #waypoints, #self.waypoints, UTILS.MpsToKnots(self.speedWp), tostring(self.option.Formation))) - + -- Route group to all defined waypoints remaining. self:Route(waypoints) - + else --- -- Passed final WP ==> Full Stop --- - + self:T(self.lid..string.format("WARNING: Passed final WP when UpdateRoute() ==> Full Stop!")) - self:FullStop() - + self:FullStop() + end end @@ -1335,17 +1335,17 @@ end function ARMYGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed, Formation) local n=self:GetWaypointIndex(UID) - + if n then - + -- Speed to waypoint. Speed=Speed or self:GetSpeedToWaypoint(n) - + -- Update the route. self:__UpdateRoute(-0.01, n, nil, Speed, Formation) - + end - + end --- On after "Detour" event. @@ -1364,17 +1364,17 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, if wp.detour then self:RemoveWaypointByID(wp.uid) end - end + end -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - + -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() - + -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, Speed, uid, Formation, true) - + -- Set if we want to resume route after reaching the detour waypoint. if ResumeRoute then wp.detour=1 @@ -1391,17 +1391,17 @@ end -- @param #string To To state. function ARMYGROUP:onafterOutOfAmmo(From, Event, To) self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) - + -- Get current task. local task=self:GetTaskCurrent() - + if task then if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) self:TaskCancel(task) end - end - + end + -- Fist, check if we want to rearm once out-of-ammo. --TODO: IsMobile() check if self.rearmOnOutOfAmmo then @@ -1413,20 +1413,20 @@ function ARMYGROUP:onafterOutOfAmmo(From, Event, To) return end end - + -- Second, check if we want to retreat once out of ammo. if self.retreatOnOutOfAmmo then self:T(self.lid.."Retreat on out of ammo") self:__Retreat(-1) return end - + -- Third, check if we want to RTZ once out of ammo (unless we have a rearming mission in the queue). if self.rtzOnOutOfAmmo and not self:IsMissionTypeInQueue(AUFTRAG.Type.REARMING) then self:T(self.lid.."RTZ on out of ammo") self:__RTZ(-1) end - + end @@ -1462,7 +1462,7 @@ function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation) dt=-0.1 allowed=false end - + -- Check if coordinate is provided. if allowed and not Coordinate then local truck=self:FindNearestAmmoSupply() @@ -1471,14 +1471,14 @@ function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation) end return false end - + -- Try again... if dt then self:T(self.lid..string.format("Trying Rearm again in %.2f sec", dt)) self:__Rearm(dt, Coordinate, Formation) allowed=false end - + return allowed end @@ -1496,10 +1496,10 @@ function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation) -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() - + -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) - + -- Set if we want to resume route after reaching the detour waypoint. wp.detour=0 @@ -1512,21 +1512,21 @@ end -- @param #string To To state. function ARMYGROUP:onafterRearmed(From, Event, To) self:T(self.lid.."Group rearmed") - + -- Get Current mission. local mission=self:GetMissionCurrent() - + -- Check if this is a rearming mission. if mission and mission.type==AUFTRAG.Type.REARMING then - + -- Rearmed ==> Mission Done! This also checks if the group is done. self:MissionDone(mission) - + else - + -- Check group done. self:_CheckGroupDone(1) - + end end @@ -1543,7 +1543,7 @@ function ARMYGROUP:onbeforeRTZ(From, Event, To, Zone, Formation) -- Zone. local zone=Zone or self.homezone - + if zone then if (not self.isMobile) and (not self:IsInZone(zone)) then @@ -1551,11 +1551,11 @@ function ARMYGROUP:onbeforeRTZ(From, Event, To, Zone, Formation) self:__RTZ(-1, Zone, Formation) return false end - + else return false end - + return true end @@ -1568,35 +1568,35 @@ end -- @param #number Formation Formation of the group. function ARMYGROUP:onafterRTZ(From, Event, To, Zone, Formation) self:T2(self.lid.."onafterRTZ") - + -- Zone. local zone=Zone or self.homezone - + -- Cancel all missions in the queue. self:CancelAllMissions() - + if zone then - + if self:IsInZone(zone) then self:Returned() else - + -- Debug info. - self:T(self.lid..string.format("RTZ to Zone %s", zone:GetName())) - + self:T(self.lid..string.format("RTZ to Zone %s", zone:GetName())) + local Coordinate=zone:GetRandomCoordinate() -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() - + -- Add waypoint after current. local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true) - + -- Set if we want to resume route after reaching the detour waypoint. wp.detour=0 - + end - + else self:T(self.lid.."ERROR: No RTZ zone given!") end @@ -1612,11 +1612,11 @@ function ARMYGROUP:onafterReturned(From, Event, To) -- Debug info. self:T(self.lid..string.format("Group returned")) - + if self.legion then -- Debug info. self:T(self.lid..string.format("Adding group back to warehouse stock")) - + -- Add asset back in 10 seconds. self.legion:__AddAsset(10, self.group, 1) end @@ -1632,13 +1632,13 @@ function ARMYGROUP:onafterRearming(From, Event, To) -- Get current position. local pos=self:GetCoordinate() - + -- Create a new waypoint. local wp=pos:WaypointGround(0) - + -- Create new route consisting of only this position ==> Stop! self:Route({wp}) - + end --- On before "Retreat" event. @@ -1651,29 +1651,29 @@ end function ARMYGROUP:onbeforeRetreat(From, Event, To, Zone, Formation) if not Zone then - + local a=self:GetVec2() - + local distmin=math.huge - local zonemin=nil + local zonemin=nil for _,_zone in pairs(self.retreatZones:GetSet()) do local zone=_zone --Core.Zone#ZONE_BASE - + local b=zone:GetVec2() - + local dist=UTILS.VecDist2D(a, b) - + if dist Stop! self:Route({wp}) - + end --- On after "EngageTarget" event. @@ -1741,22 +1741,22 @@ function ARMYGROUP:onbeforeEngageTarget(From, Event, To, Target, Speed, Formatio local allowed=true local ammo=self:GetAmmoTot() - + if ammo.Total==0 then self:T(self.lid.."WARNING: Cannot engage TARGET because no ammo left!") return false end - + -- Get current mission. local mission=self:GetMissionCurrent() - + -- Pause current mission unless it uses the EngageTarget command. if mission and mission.type~=AUFTRAG.Type.GROUNDATTACK and mission.type~=AUFTRAG.Type.CAPTUREZONE then self:T(self.lid.."Engage command but have current mission ==> Pausing mission!") self:PauseMission() dt=-0.1 allowed=false - end + end -- Try again... if dt then @@ -1785,33 +1785,33 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Formation else self.engage.Target=TARGET:New(Target) end - + -- Target coordinate. - self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) - + self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + -- Get a coordinate close to the target. local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95) -- Backup ROE and alarm state. self.engage.roe=self:GetROE() self.engage.alarmstate=self:GetAlarmstate() - + -- Switch ROE and alarm state. self:SwitchAlarmstate(ENUMS.AlarmState.Auto) self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() - + -- Set formation. self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee - + -- Set speed. self.engage.Speed=Speed - + -- Add waypoint after current. self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true) - + -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=1 @@ -1825,53 +1825,53 @@ function ARMYGROUP:_UpdateEngageTarget() -- Get current position vector. local vec3=self.engage.Target:GetVec3() - + if vec3 then - + -- Distance to last known position of target. local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) - + -- Check line of sight to target. local los=self:HasLoS(vec3) - + -- Check if target moved more than 100 meters or we do not have line of sight. if dist>100 or los==false then - + --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) - + -- Update new position. self.engage.Coordinate:UpdateFromVec3(vec3) - + -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() - + -- Remove current waypoint self:RemoveWaypointByID(self.engage.Waypoint.uid) - + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) - + -- Add waypoint after current. self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true) - + -- Set if we want to resume route after reaching the detour waypoint. self.engage.Waypoint.detour=0 - + end - + else -- Could not get position of target (not alive any more?) ==> Disengage. self:T(self.lid.."Could not get position of target ==> Disengage!") self:Disengage() - + end - + else - + -- Target not alive any more ==> Disengage. self:T(self.lid.."Target not ALIVE ==> Disengage!") self:Disengage() - + end end @@ -1887,19 +1887,19 @@ function ARMYGROUP:onafterDisengage(From, Event, To) -- Restore previous ROE and alarm state. self:SwitchROE(self.engage.roe) self:SwitchAlarmstate(self.engage.alarmstate) - + -- Get current task local task=self:GetTaskCurrent() - + -- Get if current task is ground attack. if task and task.dcstask.id==AUFTRAG.SpecialTask.GROUNDATTACK then self:T(self.lid.."Disengage with current task GROUNDATTACK ==> Task Done!") self:TaskDone(task) - end - + end + -- Remove current waypoint if self.engage.Waypoint then - self:RemoveWaypointByID(self.engage.Waypoint.uid) + self:RemoveWaypointByID(self.engage.Waypoint.uid) end -- Check group is done @@ -1928,10 +1928,10 @@ function ARMYGROUP:onafterFullStop(From, Event, To) -- Get current position. local pos=self:GetCoordinate() - + -- Create a new waypoint. local wp=pos:WaypointGround(0) - + -- Create new route consisting of only this position ==> Stop! self:Route({wp}) @@ -1949,7 +1949,7 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) -- Not waiting anymore. self.Twaiting=nil self.dTwait=nil - + -- Debug info. self:T(self.lid..string.format("Cruise ==> Update route in 0.01 sec (speed=%s, formation=%s)", tostring(Speed), tostring(Formation))) @@ -1966,11 +1966,11 @@ end -- @param Wrapper.Unit#UNIT Enemy Unit that hit the element or `nil`. function ARMYGROUP:onafterHit(From, Event, To, Enemy) self:T(self.lid..string.format("ArmyGroup hit by %s", Enemy and Enemy:GetName() or "unknown")) - + if self.suppressionOn then env.info(self.lid.."FF suppress") self:_Suppress() - end + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1989,7 +1989,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Debug info. self:T(self.lid..string.format("AddWaypoint Formation = %s", tostring(Formation))) - + -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) @@ -1998,8 +1998,8 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - - -- Formation. + + -- Formation. if not Formation then if self.formationPerma then Formation = self.formationPerma @@ -2013,16 +2013,16 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation end self:T2(self.lid..string.format("Formation set to = %s", tostring(Formation))) end - + -- Create a Ground waypoint. local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation) - + -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) - + -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) - + -- Get closest point to road. waypoint.roadcoord=coordinate:GetClosestPointToRoad(false) if waypoint.roadcoord then @@ -2030,15 +2030,15 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation else waypoint.roaddist=1000*1000 --1000 km. end - + -- Debug info. self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) - + -- Update route. if Updateroute==nil or Updateroute==true then self:__UpdateRoute(-0.01) end - + return waypoint end @@ -2051,85 +2051,133 @@ function ARMYGROUP:_InitGroup(Template, Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0) else + -- First check if group was already initialized. + if self.groupinitialized then + self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") + return + end + + -- Get template of group. + local template=Template or self:_GetTemplate() + + -- Ground are always AI. + self.isAI=true + + -- Is (template) group late activated. + self.isLateActivated=template.lateActivation + + -- Ground groups cannot be uncontrolled. + self.isUncontrolled=false + + -- Max speed in km/h. + self.speedMax=self.group:GetSpeedMax() + + -- Is group mobile? + if self.speedMax and self.speedMax>3.6 then + self.isMobile=true + else + self.isMobile=false + self.speedMax = 0 + end + + -- Cruise speed in km/h + self.speedCruise=self.speedMax*0.7 + + -- Group ammo. + self.ammo=self:GetAmmoTot() + + -- Radio parameters from template. + self.radio.On=false -- Radio is always OFF for ground. + self.radio.Freq=133 + self.radio.Modu=radio.modulation.AM + + -- Set default radio. + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) + + -- Get current formation from first waypoint. + self.option.Formation=template.route.points[1].action + + -- Set default formation to "on road". + self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad -- First check if group was already initialized. if self.groupinitialized then self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!") return end - + self:I(self.lid.."FF Initializing Group") - + -- Get template of group. local template=Template or self:_GetTemplate() - + -- Ground are always AI. self.isAI=true - + -- Is (template) group late activated. self.isLateActivated=template.lateActivation - + -- Ground groups cannot be uncontrolled. self.isUncontrolled=false - + -- Max speed in km/h. self.speedMax=self.group:GetSpeedMax() - + -- Is group mobile? if self.speedMax>3.6 then self.isMobile=true else self.isMobile=false end - + -- Cruise speed in km/h self.speedCruise=self.speedMax*0.7 - + -- Group ammo. self.ammo=self:GetAmmoTot() - + -- Radio parameters from template. self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 self.radio.Modu=radio.modulation.AM - + -- Set default radio. self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - + -- Get current formation from first waypoint. self.option.Formation=template.route.points[1].action - + -- Set default formation to "on road". self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad - + -- Default TACAN off. self:SetDefaultTACAN(nil, nil, nil, nil, true) self.tacan=UTILS.DeepCopy(self.tacanDefault) - + -- Units of the group. local units=self.group:GetUnits() - + -- DCS group. local dcsgroup=Group.getByName(self.groupname) local size0=dcsgroup:getInitialSize() local u=dcsgroup:getUnits() - + -- Quick check. if #units~=size0 then self:T(self.lid..string.format("ERROR: Got #units=%d but group consists of %d units! u=%d", #units, size0, #u)) end - + -- Add elemets. for _,unit in pairs(units) do local unitname=unit:GetName() self:_AddElementByName(unitname) end - - + + -- Init done. self.groupinitialized=true end - + return self end @@ -2146,32 +2194,32 @@ end function ARMYGROUP:SwitchFormation(Formation, Permanently, NoRouteUpdate) if self:IsAlive() or self:IsInUtero() then - + Formation=Formation or (self.optionDefault.Formation or "Off road") Permanently = Permanently or false - + if Permanently then self.formationPerma=Formation else self.formationPerma=nil - end - + end + -- Set current formation. self.option.Formation=Formation or "Off road" - + if self:IsInUtero() then self:T(self.lid..string.format("Will switch formation to %s (permanently=%s) when group is spawned", tostring(self.option.Formation), tostring(Permanently))) else - + -- Update route with the new formation. if NoRouteUpdate then else self:__UpdateRoute(-1, nil, nil, Formation) end - + -- Debug info. self:T(self.lid..string.format("Switching formation to %s (permanently=%s)", tostring(self.option.Formation), tostring(Permanently))) - + end end @@ -2195,19 +2243,19 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) -- Current positon. local coord=self:GetCoordinate() - + -- Get my coalition. local myCoalition=self:GetCoalition() -- Scanned units. local units=coord:ScanUnits(Radius) - -- Find closest + -- Find closest local dmin=math.huge local truck=nil --Wrapper.Unit#UNIT for _,_unit in pairs(units.Set) do local unit=_unit --Wrapper.Unit#UNIT - + -- Check coaliton and if unit can supply ammo. if unit:IsAlive() and unit:GetCoalition()==myCoalition and unit:IsAmmoSupply() and unit:GetVelocityKMH()<1 then @@ -2221,7 +2269,7 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) -- Debug message. self:T(self.lid..string.format("Ammo truck %s [%s] at dist=%d meters", unit:GetName(), unit:GetTypeName(), d)) end - + end end @@ -2238,44 +2286,44 @@ function ARMYGROUP:_Suppress() -- Current time. local Tnow=timer.getTime() - + -- Current ROE local currROE=self:GetROE() - - + + -- Get randomized time the unit is suppressed. local sigma=(self.TsuppressMax-self.TsuppressMin)/4 - -- Gaussian distribution. + -- Gaussian distribution. local Tsuppress=UTILS.RandomGaussian(self.TsuppressAve,sigma,self.TsuppressMin, self.TsuppressMax) - + -- Time at which the suppression is over. local renew=true if not self.TsuppressionOver then - + -- Group is not suppressed currently. self.TsuppressionOver=Tnow+Tsuppress - + -- Group will hold their weapons. self:SwitchROE(ENUMS.ROE.WeaponHold) - + -- Backup ROE. self.suppressionROE=currROE - + else -- Check if suppression is longer than current time. if Tsuppress+Tnow > self.TsuppressionOver then self.TsuppressionOver=Tnow+Tsuppress else renew=false - end + end end - + -- Recovery event will be called in Tsuppress seconds. if renew then self:__Unsuppressed(self.TsuppressionOver-Tnow) end - + -- Debug message. self:T(self.lid..string.format("Suppressed for %d sec", Tsuppress)) @@ -2291,17 +2339,17 @@ function ARMYGROUP:onbeforeUnsuppressed(From, Event, To) -- Current time. local Tnow=timer.getTime() - + -- Debug info self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver)) - + -- Recovery is only possible if enough time since the last hit has passed. if Tnow >= self.TsuppressionOver then return true else return false end - + end --- After "Recovered" event. Group has recovered and its ROE is set back to the "normal" unsuppressed state. Optionally the group is flared green. @@ -2310,20 +2358,20 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterUnsuppressed(From, Event, To) - + -- Debug message. local text=string.format("Group %s has recovered!", self:GetName()) MESSAGE:New(text, 10):ToAll() self:T(self.lid..text) - + -- Set ROE back to default. self:SwitchROE(self.suppressionROE) - + -- Flare unit green. if true then self.group:FlareGreen() end - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Awacs.lua b/Moose Development/Moose/Ops/Awacs.lua index 5df0f7f42..1fd51026b 100644 --- a/Moose Development/Moose/Ops/Awacs.lua +++ b/Moose Development/Moose/Ops/Awacs.lua @@ -508,7 +508,7 @@ do -- @field #AWACS AWACS = { ClassName = "AWACS", -- #string - version = "0.2.61", -- #string + version = "0.2.63", -- #string lid = "", -- #string coalition = coalition.side.BLUE, -- #number coalitiontxt = "blue", -- #string @@ -1384,7 +1384,7 @@ end -- Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- [User] Set the tactical information option, create 10 radio channels groups can subscribe and get Bogey Dope on a specific frequency automatically. +--- [User] Set the tactical information option, create 10 radio channels groups can subscribe and get Bogey Dope on a specific frequency automatically. You **need** to set up SRS first before using this! -- @param #AWACS self -- @param #number BaseFreq Base Frequency to use, defaults to 130. -- @param #number Increase Increase to use, defaults to 0.5, thus channels created are 130, 130.5, 131 .. etc. @@ -1394,6 +1394,10 @@ end -- @return #AWACS self function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number) self:T(self.lid.."SetTacticalRadios") + if not self.AwacsSRS then + MESSAGE:New("AWACS: Setup SRS in your code BEFORE trying to add tac radios please!",30,"ERROR",true):ToLog():ToAll() + return self + end self.TacticalMenu = true self.TacticalBaseFreq = BaseFreq or 130 self.TacticalIncrFreq = Increase or 0.5 @@ -1407,7 +1411,7 @@ function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number) self.TacticalFrequencies[freq] = freq end if self.AwacsSRS then - self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation) + self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation,self.Backend) self.TacticalSRS:SetCoalition(self.coalition) self.TacticalSRS:SetGender(self.Gender) self.TacticalSRS:SetCulture(self.Culture) @@ -2085,8 +2089,9 @@ end -- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest) -- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here. -- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here. +-- @param #string Backend (Optional) Your MSRS Backend if different from your config file settings, e.g. MSRS.Backend.SRSEXE or MSRS.Backend.GRPC -- @return #AWACS self -function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey) +function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend) self:T(self.lid.."SetSRS") self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.Gender = Gender or MSRS.gender or "male" @@ -2096,8 +2101,9 @@ function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey self.PathToGoogleKey = PathToGoogleKey self.AccessKey = AccessKey self.Volume = Volume or 1.0 - - self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation) + self.Backend = Backend or MSRS.backend + BASE:I({backend = self.Backend}) + self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend) self.AwacsSRS:SetCoalition(self.coalition) self.AwacsSRS:SetGender(self.Gender) self.AwacsSRS:SetCulture(self.Culture) @@ -2499,13 +2505,22 @@ function AWACS:_CheckMerges() local cpos = contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() local dist = ppos:Get2DDistance(cpos) local distnm = UTILS.Round(UTILS.MetersToNM(dist),0) - if (pilot.IsPlayer or self.debug) and distnm <= 5 and not contact.MergeCallDone then - local label = contact.EngagementTag or "" - if not contact.MergeCallDone or not string.find(label,pcallsign) then + if (pilot.IsPlayer or self.debug) and distnm <= 5 then --and ((not contact.MergeCallDone) or (timer.getTime() - contact.MergeCallDone > 30)) then + --local label = contact.EngagementTag or "" + --if not contact.MergeCallDone or not string.find(label,pcallsign) then self:T(self.lid.."Merged") self:_MergedCall(_id) - contact.MergeCallDone = true - end + --contact.MergeCallDone = true + --end + end + if (pilot.IsPlayer or self.debug) and distnm >5 and distnm <= self.ThreatDistance then + self:_ThreatRangeCall(_id,Contact) + end + if (pilot.IsPlayer or self.debug) and distnm > self.ThreatDistance and distnm <= self.MeldDistance then + self:_MeldRangeCall(_id,Contact) + end + if (pilot.IsPlayer or self.debug) and distnm > self.MeldDistance and distnm <= self.TacDistance then + self:_TACRangeCall(_id,Contact) end end ) @@ -3099,7 +3114,7 @@ function AWACS:_BogeyDope(Group,Tactical) local clean = self.gettext:GetEntry("CLEAN",self.locale) text = string.format(clean,self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) - self:_NewRadioEntry(text,textScreen,GID,Outcome,Outcome,true,false,true,Tactical) + self:_NewRadioEntry(text,text,GID,Outcome,Outcome,true,false,true,Tactical) else @@ -3141,9 +3156,13 @@ end function AWACS:_ShowAwacsInfo(Group) self:T(self.lid.."_ShowAwacsInfo") local report = REPORT:New("Info") + local STN = self.STN report:Add("====================") report:Add(string.format("AWACS %s",self.callsigntxt)) report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) + if STN then + report:Add(string.format("Link-16 STN: %s",STN)) + end report:Add(string.format("Bulls Alias: %s",self.AOName)) report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM())) report:Add("====================") @@ -5467,7 +5486,7 @@ function AWACS:_TACRangeCall(GID,Contact) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local contact = Contact.Contact -- Ops.Intel#INTEL.Contact local contacttag = Contact.TargetGroupNaming - if contact and not Contact.TACCallDone then + if contact then --and not Contact.TACCallDone then local position = contact.position -- Core.Point#COORDINATE if position then local distance = position:Get2DDistance(managedgroup.Group:GetCoordinate()) @@ -5477,6 +5496,15 @@ function AWACS:_TACRangeCall(GID,Contact) local text = string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING) + if GID and GID ~= 0 then + --local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then + local name = managedgroup.GroupName + if self.TacticalSubscribers[name] then + self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) + end + end + end end end return self @@ -5495,8 +5523,8 @@ function AWACS:_MeldRangeCall(GID,Contact) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local flightpos = managedgroup.Group:GetCoordinate() local contact = Contact.Contact -- Ops.Intel#INTEL.Contact - local contacttag = Contact.TargetGroupNaming - if contact and not Contact.MeldCallDone then + local contacttag = Contact.TargetGroupNaming or "Bogey" + if contact then --and not Contact.MeldCallDone then local position = contact.position -- Core.Point#COORDINATE if position then local BRATExt = "" @@ -5509,6 +5537,15 @@ function AWACS:_MeldRangeCall(GID,Contact) local text = string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING) + if GID and GID ~= 0 then + --local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then + local name = managedgroup.GroupName + if self.TacticalSubscribers[name] then + self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) + end + end + end end end return self @@ -5525,7 +5562,7 @@ function AWACS:_ThreatRangeCall(GID,Contact) local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local flightpos = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition local contact = Contact.Contact -- Ops.Intel#INTEL.Contact - local contacttag = Contact.TargetGroupNaming + local contacttag = Contact.TargetGroupNaming or "Bogey" if contact then local position = contact.position or contact.group:GetCoordinate() -- Core.Point#COORDINATE if position then @@ -5539,6 +5576,15 @@ function AWACS:_ThreatRangeCall(GID,Contact) local thrt = self.gettext:GetEntry("THREAT",self.locale) local text = string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt, thrt, BRATExt) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) + if GID and GID ~= 0 then + --local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup + if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then + local name = managedgroup.GroupName + if self.TacticalSubscribers[name] then + self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true) + end + end + end end end return self @@ -5953,6 +5999,10 @@ function AWACS:_CheckAwacsStatus() local awacs = nil -- Wrapper.Group#GROUP if self.AwacsFG then awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP + local unit = awacs:GetUnit(1) + if unit then + self.STN = tostring(unit:GetSTN()) + end end local monitoringdata = self.MonitoringData -- #AWACS.MonitoringData @@ -6632,7 +6682,7 @@ function AWACS:onafterCheckTacticalQueue(From,Event,To) end -- end while - if self:Is("Running") then + if not self:Is("Stopped") then self:__CheckTacticalQueue(-self.TacticalInterval) end return self diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 3242dc389..76e442df3 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -313,8 +313,8 @@ end -- -- local Path = FilePath or "C:\\Users\\\\Saved Games\\DCS\\Missions\\" -- example path -- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename --- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce() --- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename) +-- 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 @@ -324,7 +324,7 @@ end -- local Path = FilePath or "C:\\Users\\\\Saved Games\\DCS\\Missions\\" -- example path -- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename -- if UTILS.CheckFileExists(Path,BlueOpsFilename) then --- local loadback = UTILS.LoadSetOfGroups(Path,BlueOpsFilename,false) +-- 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 diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 5ecc2c4c8..f1d366ca4 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -290,10 +290,11 @@ CSAR.AircraftType["Bell-47"] = 2 CSAR.AircraftType["UH-60L"] = 10 CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2 +CSAR.AircraftType["MH-60R"] = 10 --- CSAR class version. -- @field #string version -CSAR.version="1.0.19" +CSAR.version="1.0.20" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 7adcf3afb..e571e5cd0 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -24,7 +24,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update December 2023 +-- Last Update February 2024 do @@ -44,6 +44,7 @@ do -- @field #number PerCrateMass Mass in kg. -- @field #number Stock Number of builds available, -1 for unlimited. -- @field #string Subcategory Sub-category name. +-- @field #boolean DontShowInMenu Show this item in menu or not. -- @extends Core.Base#BASE --- @@ -62,6 +63,7 @@ CTLD_CARGO = { PerCrateMass = 0, Stock = nil, Mark = nil, + DontShowInMenu = false, } --- Define cargo types. @@ -97,8 +99,9 @@ CTLD_CARGO = { -- @param #number PerCrateMass Mass in kg -- @param #number Stock Number of builds available, nil for unlimited -- @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). -- @return #CTLD_CARGO self - function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) + function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory,DontShowInMenu) -- Inherit everything from BASE class. local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) @@ -115,6 +118,7 @@ CTLD_CARGO = { self.Stock = Stock or nil --#number self.Mark = nil self.Subcategory = Subcategory or "Other" + self.DontShowInMenu = DontShowInMenu or false return self end @@ -1222,13 +1226,14 @@ CTLD.UnitTypeCapabilities = { ["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. ["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 ["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}, } --- CTLD class version. -- @field #string version -CTLD.version="1.0.45" +CTLD.version="1.0.48" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1433,7 +1438,7 @@ function CTLD:New(Coalition, Prefixes, Alias) --- Pseudo Functions --- ------------------------ - --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. + --- Triggers the FSM event "Start". Starts the CTLD. Initializes parameters and starts event handlers. -- @function [parent=#CTLD] Start -- @param #CTLD self @@ -3024,9 +3029,10 @@ end function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) local Positions = {} local template = _DATABASE:GetGroupTemplate(Template) - UTILS.PrintTableToLog(template) + --UTILS.PrintTableToLog(template) local numbertroops = #template.units - local newcenter = Coordinate:Translate(Radius,((Heading+270)%360)) + local slightshift = math.abs(math.random(0,200)/100) + local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270)%360)) for i=1,360,math.floor(360/numbertroops) do local phead = ((Heading+270+i)%360) local post = newcenter:Translate(Radius,phead) @@ -3038,7 +3044,7 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) } table.insert(Positions,p1t) end - UTILS.PrintTableToLog(Positions) + --UTILS.PrintTableToLog(Positions) return Positions end @@ -3700,14 +3706,20 @@ function CTLD:_RefreshF10Menus() for _,_entry in pairs(self.Cargo_Troops) do local entry = _entry -- #CTLD_CARGO local subcat = entry.Subcategory - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) + end end else for _,_entry in pairs(self.Cargo_Troops) do local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) + end end end local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() @@ -3728,33 +3740,45 @@ function CTLD:_RefreshF10Menus() for _,_entry in pairs(self.Cargo_Crates) do local entry = _entry -- #CTLD_CARGO local subcat = entry.Subcategory - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + end end for _,_entry in pairs(self.Cargo_Statics) do local entry = _entry -- #CTLD_CARGO local subcat = entry.Subcategory - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + end end else for _,_entry in pairs(self.Cargo_Crates) do local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end end for _,_entry in pairs(self.Cargo_Statics) do local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + local noshow = entry.DontShowInMenu + if not noshow then + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end end end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) - removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) + local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) if not self.nobuildmenu then local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 046d5ddc0..1c649dd85 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -140,7 +140,7 @@ COMMANDER = { --- COMMANDER class version. -- @field #string version -COMMANDER.version="0.1.3" +COMMANDER.version="0.1.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -675,7 +675,8 @@ function COMMANDER:AddCapZone(Zone, Altitude, Speed, Heading, Leg) patrolzone.zone=Zone patrolzone.altitude=Altitude or 12000 patrolzone.heading=Heading or 270 - patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude) + --patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude) + patrolzone.speed=Speed or 350 patrolzone.leg=Leg or 30 patrolzone.mission=nil --patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "CAP Zone"):ToCoalition(self:GetCoalition()) @@ -700,7 +701,8 @@ function COMMANDER:AddGciCapZone(Zone, Altitude, Speed, Heading, Leg) patrolzone.zone=Zone patrolzone.altitude=Altitude or 12000 patrolzone.heading=Heading or 270 - patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude) + --patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude) + patrolzone.speed=Speed or 350 patrolzone.leg=Leg or 30 patrolzone.mission=nil --patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "GCICAP Zone"):ToCoalition(self:GetCoalition()) @@ -745,7 +747,9 @@ function COMMANDER:AddAwacsZone(Zone, Altitude, Speed, Heading, Leg) awacszone.zone=Zone awacszone.altitude=Altitude or 12000 awacszone.heading=Heading or 270 - awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude) + --awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude) + awacszone.speed=Speed or 350 + awacszone.speed=Speed or 350 awacszone.leg=Leg or 30 awacszone.mission=nil --awacszone.marker=MARKER:New(awacszone.zone:GetCoordinate(), "AWACS Zone"):ToCoalition(self:GetCoalition()) @@ -791,7 +795,8 @@ function COMMANDER:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSyst tankerzone.zone=Zone tankerzone.altitude=Altitude or 12000 tankerzone.heading=Heading or 270 - tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, tankerzone.altitude) + --tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, tankerzone.altitude) -- speed translation to alt will be done by AUFTRAG anyhow + tankerzone.speed = Speed or 350 tankerzone.leg=Leg or 30 tankerzone.refuelsystem=RefuelSystem tankerzone.mission=nil diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1d40e3383..8a0095954 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3799,10 +3799,11 @@ function FLIGHTGROUP:_InitGroup(Template) self.speedMax=group:GetSpeedMax() -- Is group mobile? - if self.speedMax>3.6 then + if self.speedMax and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false + self.speedMax = 0 end -- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings. diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index 9c5861800..69c620dd1 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -3190,14 +3190,14 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu elseif (currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then score=score+25 elseif currmission.type==AUFTRAG.Type.NOTHING then - score=score+25 + score=score+30 end end if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then -- TODO: need to check for missions that do not require ammo like transport, recon, awacs, tanker etc. - -- We better take a fresh asset. Sometimes spawned assets to something else, which is difficult to check. + -- We better take a fresh asset. Sometimes spawned assets do something else, which is difficult to check. score=score-10 else -- Combat mission. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 929225ee6..0ed715fc9 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1800,10 +1800,11 @@ function NAVYGROUP:_InitGroup(Template) self.speedMax=self.group:GetSpeedMax() -- Is group mobile? - if self.speedMax>3.6 then + if self.speedMax and self.speedMax>3.6 then self.isMobile=true else self.isMobile=false + self.speedMax = 0 end -- Cruise speed: 70% of max speed. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d6d4ce6d5..35f8a76a8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -508,7 +508,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="1.0.0" +OPSGROUP.version="1.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -535,7 +535,7 @@ OPSGROUP.version="1.0.0" -- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @return #OPSGROUP self function OPSGROUP:New(group) - + -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #OPSGROUP @@ -554,10 +554,15 @@ function OPSGROUP:New(group) -- Check if group exists. if self.group then if not self:IsExist() then - self:T(self.lid.."ERROR: GROUP does not exist! Returning nil") + self:E(self.lid.."ERROR: GROUP does not exist! Returning nil") return nil end end + + if UTILS.IsInstanceOf(group,"OPSGROUP") then + self:E(self.lid.."ERROR: GROUP is already an OPSGROUP: "..tostring(self.groupname).."!") + return group + end -- Set the template. self:_SetTemplate() @@ -591,33 +596,34 @@ function OPSGROUP:New(group) if units then local masterunit=units[1] --Wrapper.Unit#UNIT - - -- Get Descriptors. - self.descriptors=masterunit:GetDesc() - - -- Set type name. - self.actype=masterunit:GetTypeName() - - -- Is this a submarine. - self.isSubmarine=masterunit:HasAttribute("Submarines") - - -- Has this a datalink? - self.isEPLRS=masterunit:HasAttribute("Datalink") - - if self:IsFlightgroup() then - - self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 - - self.ceiling=self.descriptors.Hmax - - self.tankertype=select(2, masterunit:IsTanker()) - self.refueltype=select(2, masterunit:IsRefuelable()) - - --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) - --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) - + + if unit then + -- Get Descriptors. + self.descriptors=masterunit:GetDesc() + + -- Set type name. + self.actype=masterunit:GetTypeName() + + -- Is this a submarine. + self.isSubmarine=masterunit:HasAttribute("Submarines") + + -- Has this a datalink? + self.isEPLRS=masterunit:HasAttribute("Datalink") + + if self:IsFlightgroup() then + + self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000 + + self.ceiling=self.descriptors.Hmax + + self.tankertype=select(2, masterunit:IsTanker()) + self.refueltype=select(2, masterunit:IsRefuelable()) + + --env.info("DCS Unit BOOM_AND_RECEPTACLE="..tostring(Unit.RefuelingSystem.BOOM_AND_RECEPTACLE)) + --env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE)) + + end end - end -- Init set of detected units. diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index 643355ed8..e0c7f8c60 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -21,7 +21,7 @@ -- === -- @module Ops.PlayerTask -- @image OPS_PlayerTask.jpg --- @date Last Update Jan 2024 +-- @date Last Update Feb 2024 do @@ -411,6 +411,15 @@ function PLAYERTASK:IsDone() return IsDone end +--- [User] Check if PLAYERTASK has clients assigned to it. +-- @param #PLAYERTASK self +-- @return #boolean hasclients +function PLAYERTASK:HasClients() + self:T(self.lid.."HasClients?") + local hasclients = self:CountClients() > 0 and true or false + return hasclients +end + --- [User] Get client names assigned as table of #strings -- @param #PLAYERTASK self -- @return #table clients @@ -1552,7 +1561,7 @@ PLAYERTASKCONTROLLER.Messages = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASKCONTROLLER.version="0.1.64" +PLAYERTASKCONTROLLER.version="0.1.65" --- Create and run a new TASKCONTROLLER instance. -- @param #PLAYERTASKCONTROLLER self @@ -3173,7 +3182,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client) local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale) local taskname = string.format(tname,task.Type,task.PlayerTaskNr) local ttstaskname = string.format(ttsname,task.TTSType,task.PlayerTaskNr) - local Coordinate = task.Target:GetCoordinate() + local Coordinate = task.Target:GetCoordinate() or COORDINATE:New(0,0,0) local CoordText = "" local CoordTextLLDM = nil if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then diff --git a/Moose Development/Moose/Sound/Radio.lua b/Moose Development/Moose/Sound/Radio.lua index 002e6ddaf..68acf2a5a 100644 --- a/Moose Development/Moose/Sound/Radio.lua +++ b/Moose Development/Moose/Sound/Radio.lua @@ -30,6 +30,10 @@ -- -- === -- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/Radio) +-- +-- === +-- -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- -- @module Sound.Radio diff --git a/Moose Development/Moose/Sound/SRS.lua b/Moose Development/Moose/Sound/SRS.lua index 9d075dfd0..0d3315dcc 100644 --- a/Moose Development/Moose/Sound/SRS.lua +++ b/Moose Development/Moose/Sound/SRS.lua @@ -14,7 +14,7 @@ -- -- === -- --- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Sound/MSRS). +-- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/MSRS). -- -- === -- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 259f6d223..6bf7e1fc9 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -444,10 +444,11 @@ end --- Print a table to log in a nice format -- @param #table table The table to print -- @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 -function UTILS.PrintTableToLog(table, indent) +function UTILS.PrintTableToLog(table, indent, noprint) local text = "\n" - if not table then + if not table or type(table) ~= "table" then env.warning("No table passed!") return nil end @@ -455,11 +456,16 @@ function UTILS.PrintTableToLog(table, indent) for k, v in pairs(table) do if string.find(k," ") then k='"'..k..'"'end if type(v) == "table" then - env.info(string.rep(" ", indent) .. tostring(k) .. " = {") + if not noprint then + env.info(string.rep(" ", indent) .. tostring(k) .. " = {") + end text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n" text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n" - env.info(string.rep(" ", indent) .. "},") + if not noprint then + env.info(string.rep(" ", indent) .. "},") + end text = text .. string.rep(" ", indent) .. "},\n" + elseif type(v) == "function" then else local value if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then @@ -467,7 +473,9 @@ function UTILS.PrintTableToLog(table, indent) else value = '"'..tostring(v)..'"' end - env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n") + if not noprint then + env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n") + end text = text .. string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n" end end @@ -2229,6 +2237,11 @@ function UTILS.IsLoadingDoorOpen( unit_name ) return true -- no doors on this one ;) 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 + return false end -- nil @@ -3717,3 +3730,116 @@ end function UTILS.OctalToDecimal(Number) return tonumber(Number,8) 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 diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 6f1ecd91a..be41aa6c5 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -79,314 +79,320 @@ AIRBASE = { -- -- Airbases of the Caucasus map: -- --- * AIRBASE.Caucasus.Gelendzhik --- * AIRBASE.Caucasus.Krasnodar_Pashkovsky --- * AIRBASE.Caucasus.Sukhumi_Babushara --- * AIRBASE.Caucasus.Gudauta --- * AIRBASE.Caucasus.Batumi --- * AIRBASE.Caucasus.Senaki_Kolkhi --- * AIRBASE.Caucasus.Kobuleti --- * AIRBASE.Caucasus.Kutaisi --- * AIRBASE.Caucasus.Tbilisi_Lochini --- * AIRBASE.Caucasus.Soganlug --- * AIRBASE.Caucasus.Vaziani --- * AIRBASE.Caucasus.Anapa_Vityazevo --- * AIRBASE.Caucasus.Krasnodar_Center --- * AIRBASE.Caucasus.Novorossiysk --- * AIRBASE.Caucasus.Krymsk --- * AIRBASE.Caucasus.Maykop_Khanskaya --- * AIRBASE.Caucasus.Sochi_Adler --- * AIRBASE.Caucasus.Mineralnye_Vody --- * AIRBASE.Caucasus.Nalchik --- * AIRBASE.Caucasus.Mozdok --- * AIRBASE.Caucasus.Beslan +-- * AIRBASE.Caucasus.Anapa_Vityazevo +-- * AIRBASE.Caucasus.Batumi +-- * AIRBASE.Caucasus.Beslan +-- * AIRBASE.Caucasus.Gelendzhik +-- * AIRBASE.Caucasus.Gudauta +-- * AIRBASE.Caucasus.Kobuleti +-- * AIRBASE.Caucasus.Krasnodar_Center +-- * AIRBASE.Caucasus.Krasnodar_Pashkovsky +-- * AIRBASE.Caucasus.Krymsk +-- * AIRBASE.Caucasus.Kutaisi +-- * AIRBASE.Caucasus.Maykop_Khanskaya +-- * AIRBASE.Caucasus.Mineralnye_Vody +-- * AIRBASE.Caucasus.Mozdok +-- * AIRBASE.Caucasus.Nalchik +-- * AIRBASE.Caucasus.Novorossiysk +-- * AIRBASE.Caucasus.Senaki_Kolkhi +-- * AIRBASE.Caucasus.Sochi_Adler +-- * AIRBASE.Caucasus.Soganlug +-- * AIRBASE.Caucasus.Sukhumi_Babushara +-- * AIRBASE.Caucasus.Tbilisi_Lochini +-- * AIRBASE.Caucasus.Vaziani -- -- @field Caucasus AIRBASE.Caucasus = { - ["Gelendzhik"] = "Gelendzhik", - ["Krasnodar_Pashkovsky"] = "Krasnodar-Pashkovsky", - ["Sukhumi_Babushara"] = "Sukhumi-Babushara", - ["Gudauta"] = "Gudauta", - ["Batumi"] = "Batumi", - ["Senaki_Kolkhi"] = "Senaki-Kolkhi", - ["Kobuleti"] = "Kobuleti", - ["Kutaisi"] = "Kutaisi", - ["Tbilisi_Lochini"] = "Tbilisi-Lochini", - ["Soganlug"] = "Soganlug", - ["Vaziani"] = "Vaziani", ["Anapa_Vityazevo"] = "Anapa-Vityazevo", - ["Krasnodar_Center"] = "Krasnodar-Center", - ["Novorossiysk"] = "Novorossiysk", - ["Krymsk"] = "Krymsk", - ["Maykop_Khanskaya"] = "Maykop-Khanskaya", - ["Sochi_Adler"] = "Sochi-Adler", - ["Mineralnye_Vody"] = "Mineralnye Vody", - ["Nalchik"] = "Nalchik", - ["Mozdok"] = "Mozdok", + ["Batumi"] = "Batumi", ["Beslan"] = "Beslan", + ["Gelendzhik"] = "Gelendzhik", + ["Gudauta"] = "Gudauta", + ["Kobuleti"] = "Kobuleti", + ["Krasnodar_Center"] = "Krasnodar-Center", + ["Krasnodar_Pashkovsky"] = "Krasnodar-Pashkovsky", + ["Krymsk"] = "Krymsk", + ["Kutaisi"] = "Kutaisi", + ["Maykop_Khanskaya"] = "Maykop-Khanskaya", + ["Mineralnye_Vody"] = "Mineralnye Vody", + ["Mozdok"] = "Mozdok", + ["Nalchik"] = "Nalchik", + ["Novorossiysk"] = "Novorossiysk", + ["Senaki_Kolkhi"] = "Senaki-Kolkhi", + ["Sochi_Adler"] = "Sochi-Adler", + ["Soganlug"] = "Soganlug", + ["Sukhumi_Babushara"] = "Sukhumi-Babushara", + ["Tbilisi_Lochini"] = "Tbilisi-Lochini", + ["Vaziani"] = "Vaziani", } --- Airbases of the Nevada map: -- --- * AIRBASE.Nevada.Creech_AFB --- * AIRBASE.Nevada.Groom_Lake_AFB --- * AIRBASE.Nevada.McCarran_International_Airport --- * AIRBASE.Nevada.Nellis_AFB --- * AIRBASE.Nevada.Beatty_Airport --- * AIRBASE.Nevada.Boulder_City_Airport --- * AIRBASE.Nevada.Echo_Bay --- * AIRBASE.Nevada.Henderson_Executive_Airport --- * AIRBASE.Nevada.Jean_Airport --- * AIRBASE.Nevada.Laughlin_Airport --- * AIRBASE.Nevada.Lincoln_County --- * AIRBASE.Nevada.Mesquite --- * AIRBASE.Nevada.Mina_Airport --- * AIRBASE.Nevada.North_Las_Vegas --- * AIRBASE.Nevada.Pahute_Mesa_Airstrip --- * AIRBASE.Nevada.Tonopah_Airport --- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield +-- * AIRBASE.Nevada.Beatty +-- * AIRBASE.Nevada.Boulder_City +-- * AIRBASE.Nevada.Creech +-- * AIRBASE.Nevada.Echo_Bay +-- * AIRBASE.Nevada.Groom_Lake +-- * AIRBASE.Nevada.Henderson_Executive +-- * AIRBASE.Nevada.Jean +-- * AIRBASE.Nevada.Laughlin +-- * AIRBASE.Nevada.Lincoln_County +-- * AIRBASE.Nevada.McCarran_International +-- * AIRBASE.Nevada.Mesquite +-- * AIRBASE.Nevada.Mina +-- * AIRBASE.Nevada.Nellis +-- * AIRBASE.Nevada.North_Las_Vegas +-- * AIRBASE.Nevada.Pahute_Mesa +-- * AIRBASE.Nevada.Tonopah +-- * AIRBASE.Nevada.Tonopah_Test_Range -- -- @field Nevada AIRBASE.Nevada = { - ["Creech_AFB"] = "Creech", - ["Groom_Lake_AFB"] = "Groom Lake", - ["McCarran_International_Airport"] = "McCarran International", - ["Nellis_AFB"] = "Nellis", - ["Beatty_Airport"] = "Beatty", - ["Boulder_City_Airport"] = "Boulder City", + ["Beatty"] = "Beatty", + ["Boulder_City"] = "Boulder City", + ["Creech"] = "Creech", ["Echo_Bay"] = "Echo Bay", - ["Henderson_Executive_Airport"] = "Henderson Executive", - ["Jean_Airport"] = "Jean", - ["Laughlin_Airport"] = "Laughlin", + ["Groom_Lake"] = "Groom Lake", + ["Henderson_Executive"] = "Henderson Executive", + ["Jean"] = "Jean", + ["Laughlin"] = "Laughlin", ["Lincoln_County"] = "Lincoln County", + ["McCarran_International"] = "McCarran International", ["Mesquite"] = "Mesquite", - ["Mina_Airport"] = "Mina", + ["Mina"] = "Mina", + ["Nellis"] = "Nellis", ["North_Las_Vegas"] = "North Las Vegas", - ["Pahute_Mesa_Airstrip"] = "Pahute Mesa", - ["Tonopah_Airport"] = "Tonopah", - ["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range", + ["Pahute_Mesa"] = "Pahute Mesa", + ["Tonopah"] = "Tonopah", + ["Tonopah_Test_Range"] = "Tonopah Test Range", } --- Airbases of the Normandy map: -- --- * AIRBASE.Normandy.Saint_Pierre_du_Mont --- * AIRBASE.Normandy.Lignerolles --- * AIRBASE.Normandy.Cretteville --- * AIRBASE.Normandy.Maupertus --- * AIRBASE.Normandy.Brucheville --- * AIRBASE.Normandy.Meautis --- * AIRBASE.Normandy.Cricqueville_en_Bessin --- * AIRBASE.Normandy.Lessay --- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer --- * AIRBASE.Normandy.Biniville --- * AIRBASE.Normandy.Cardonville --- * AIRBASE.Normandy.Deux_Jumeaux --- * AIRBASE.Normandy.Chippelle --- * AIRBASE.Normandy.Beuzeville --- * AIRBASE.Normandy.Azeville --- * AIRBASE.Normandy.Picauville --- * AIRBASE.Normandy.Le_Molay --- * AIRBASE.Normandy.Longues_sur_Mer --- * AIRBASE.Normandy.Carpiquet --- * AIRBASE.Normandy.Bazenville --- * AIRBASE.Normandy.Sainte_Croix_sur_Mer --- * AIRBASE.Normandy.Beny_sur_Mer --- * AIRBASE.Normandy.Rucqueville --- * AIRBASE.Normandy.Sommervieu --- * AIRBASE.Normandy.Lantheuil --- * AIRBASE.Normandy.Evreux --- * AIRBASE.Normandy.Chailey --- * AIRBASE.Normandy.Needs_Oar_Point --- * AIRBASE.Normandy.Funtington --- * AIRBASE.Normandy.Tangmere --- * AIRBASE.Normandy.Ford --- * AIRBASE.Normandy.Argentan --- * AIRBASE.Normandy.Goulet --- * AIRBASE.Normandy.Barville --- * AIRBASE.Normandy.Essay --- * AIRBASE.Normandy.Hauterive --- * AIRBASE.Normandy.Lymington --- * AIRBASE.Normandy.Vrigny --- * AIRBASE.Normandy.Odiham --- * AIRBASE.Normandy.Conches --- * AIRBASE.Normandy.West_Malling --- * AIRBASE.Normandy.Villacoublay --- * AIRBASE.Normandy.Kenley --- * AIRBASE.Normandy.Beauvais_Tille --- * AIRBASE.Normandy.Cormeilles_en_Vexin --- * AIRBASE.Normandy.Creil --- * AIRBASE.Normandy.Guyancourt --- * AIRBASE.Normandy.Lonrai --- * AIRBASE.Normandy.Dinan_Trelivan --- * AIRBASE.Normandy.Heathrow --- * AIRBASE.Normandy.Fecamp_Benouville --- * AIRBASE.Normandy.Farnborough --- * AIRBASE.Normandy.Friston --- * AIRBASE.Normandy.Deanland --- * AIRBASE.Normandy.Triqueville --- * AIRBASE.Normandy.Poix --- * AIRBASE.Normandy.Orly --- * AIRBASE.Normandy.Stoney_Cross --- * AIRBASE.Normandy.Amiens_Glisy --- * AIRBASE.Normandy.Ronai --- * AIRBASE.Normandy.Rouen_Boos --- * AIRBASE.Normandy.Deauville --- * AIRBASE.Normandy.Saint_Aubin --- * AIRBASE.Normandy.Flers --- * AIRBASE.Normandy.Avranches_Le_Val_Saint_Pere --- * AIRBASE.Normandy.Gravesend --- * AIRBASE.Normandy.Beaumont_le_Roger --- * AIRBASE.Normandy.Broglie --- * AIRBASE.Normandy.Bernay_Saint_Martin --- * AIRBASE.Normandy.Saint_Andre_de_lEure --- * AIRBASE.Normandy.Biggin_Hill --- * AIRBASE.Normandy.Manston --- * AIRBASE.Normandy.Detling --- * AIRBASE.Normandy.Lympne --- * AIRBASE.Normandy.Abbeville_Drucat --- * AIRBASE.Normandy.Merville_Calonne --- * AIRBASE.Normandy.Saint_Omer_Wizernes +-- * AIRBASE.Normandy.Abbeville_Drucat +-- * AIRBASE.Normandy.Amiens_Glisy +-- * AIRBASE.Normandy.Argentan +-- * AIRBASE.Normandy.Avranches_Le_Val_Saint_Pere +-- * AIRBASE.Normandy.Azeville +-- * AIRBASE.Normandy.Barville +-- * AIRBASE.Normandy.Bazenville +-- * AIRBASE.Normandy.Beaumont_le_Roger +-- * AIRBASE.Normandy.Beauvais_Tille +-- * AIRBASE.Normandy.Beny_sur_Mer +-- * AIRBASE.Normandy.Bernay_Saint_Martin +-- * AIRBASE.Normandy.Beuzeville +-- * AIRBASE.Normandy.Biggin_Hill +-- * AIRBASE.Normandy.Biniville +-- * AIRBASE.Normandy.Broglie +-- * AIRBASE.Normandy.Brucheville +-- * AIRBASE.Normandy.Cardonville +-- * AIRBASE.Normandy.Carpiquet +-- * AIRBASE.Normandy.Chailey +-- * AIRBASE.Normandy.Chippelle +-- * AIRBASE.Normandy.Conches +-- * AIRBASE.Normandy.Cormeilles_en_Vexin +-- * AIRBASE.Normandy.Creil +-- * AIRBASE.Normandy.Cretteville +-- * AIRBASE.Normandy.Cricqueville_en_Bessin +-- * AIRBASE.Normandy.Deanland +-- * AIRBASE.Normandy.Deauville +-- * AIRBASE.Normandy.Detling +-- * AIRBASE.Normandy.Deux_Jumeaux +-- * AIRBASE.Normandy.Dinan_Trelivan +-- * AIRBASE.Normandy.Dunkirk_Mardyck +-- * AIRBASE.Normandy.Essay +-- * AIRBASE.Normandy.Evreux +-- * AIRBASE.Normandy.Farnborough +-- * AIRBASE.Normandy.Fecamp_Benouville +-- * AIRBASE.Normandy.Flers +-- * AIRBASE.Normandy.Ford +-- * AIRBASE.Normandy.Friston +-- * AIRBASE.Normandy.Funtington +-- * AIRBASE.Normandy.Goulet +-- * AIRBASE.Normandy.Gravesend +-- * AIRBASE.Normandy.Guyancourt +-- * AIRBASE.Normandy.Hauterive +-- * AIRBASE.Normandy.Heathrow +-- * AIRBASE.Normandy.High_Halden +-- * AIRBASE.Normandy.Kenley +-- * AIRBASE.Normandy.Lantheuil +-- * AIRBASE.Normandy.Le_Molay +-- * AIRBASE.Normandy.Lessay +-- * AIRBASE.Normandy.Lignerolles +-- * AIRBASE.Normandy.Longues_sur_Mer +-- * AIRBASE.Normandy.Lonrai +-- * AIRBASE.Normandy.Lymington +-- * AIRBASE.Normandy.Lympne +-- * AIRBASE.Normandy.Manston +-- * AIRBASE.Normandy.Maupertus +-- * AIRBASE.Normandy.Meautis +-- * AIRBASE.Normandy.Merville_Calonne +-- * AIRBASE.Normandy.Needs_Oar_Point +-- * AIRBASE.Normandy.Odiham +-- * AIRBASE.Normandy.Orly +-- * AIRBASE.Normandy.Picauville +-- * AIRBASE.Normandy.Poix +-- * AIRBASE.Normandy.Ronai +-- * AIRBASE.Normandy.Rouen_Boos +-- * AIRBASE.Normandy.Rucqueville +-- * AIRBASE.Normandy.Saint_Andre_de_lEure +-- * AIRBASE.Normandy.Saint_Aubin +-- * AIRBASE.Normandy.Saint_Omer_Wizernes +-- * AIRBASE.Normandy.Saint_Pierre_du_Mont +-- * AIRBASE.Normandy.Sainte_Croix_sur_Mer +-- * AIRBASE.Normandy.Sainte_Laurent_sur_Mer +-- * AIRBASE.Normandy.Sommervieu +-- * AIRBASE.Normandy.Stoney_Cross +-- * AIRBASE.Normandy.Tangmere +-- * AIRBASE.Normandy.Triqueville +-- * AIRBASE.Normandy.Villacoublay +-- * AIRBASE.Normandy.Vrigny +-- * AIRBASE.Normandy.West_Malling -- -- @field Normandy AIRBASE.Normandy = { - ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", - ["Lignerolles"] = "Lignerolles", - ["Cretteville"] = "Cretteville", - ["Maupertus"] = "Maupertus", - ["Brucheville"] = "Brucheville", - ["Meautis"] = "Meautis", - ["Cricqueville_en_Bessin"] = "Cricqueville-en-Bessin", - ["Lessay"] = "Lessay", - ["Sainte_Laurent_sur_Mer"] = "Sainte-Laurent-sur-Mer", - ["Biniville"] = "Biniville", - ["Cardonville"] = "Cardonville", - ["Deux_Jumeaux"] = "Deux Jumeaux", - ["Chippelle"] = "Chippelle", - ["Beuzeville"] = "Beuzeville", - ["Azeville"] = "Azeville", - ["Picauville"] = "Picauville", - ["Le_Molay"] = "Le Molay", - ["Longues_sur_Mer"] = "Longues-sur-Mer", - ["Carpiquet"] = "Carpiquet", - ["Bazenville"] = "Bazenville", - ["Sainte_Croix_sur_Mer"] = "Sainte-Croix-sur-Mer", - ["Beny_sur_Mer"] = "Beny-sur-Mer", - ["Rucqueville"] = "Rucqueville", - ["Sommervieu"] = "Sommervieu", - ["Lantheuil"] = "Lantheuil", - ["Evreux"] = "Evreux", - ["Chailey"] = "Chailey", - ["Needs_Oar_Point"] = "Needs Oar Point", - ["Funtington"] = "Funtington", - ["Tangmere"] = "Tangmere", - ["Ford"] = "Ford", + ["Abbeville_Drucat"] = "Abbeville Drucat", + ["Amiens_Glisy"] = "Amiens-Glisy", ["Argentan"] = "Argentan", - ["Goulet"] = "Goulet", + ["Avranches_Le_Val_Saint_Pere"] = "Avranches Le Val-Saint-Pere", + ["Azeville"] = "Azeville", ["Barville"] = "Barville", - ["Essay"] = "Essay", - ["Hauterive"] = "Hauterive", - ["Lymington"] = "Lymington", - ["Vrigny"] = "Vrigny", - ["Odiham"] = "Odiham", - ["Conches"] = "Conches", - ["West_Malling"] = "West Malling", - ["Villacoublay"] = "Villacoublay", - ["Kenley"] = "Kenley", + ["Bazenville"] = "Bazenville", + ["Beaumont_le_Roger"] = "Beaumont-le-Roger", ["Beauvais_Tille"] = "Beauvais-Tille", + ["Beny_sur_Mer"] = "Beny-sur-Mer", + ["Bernay_Saint_Martin"] = "Bernay Saint Martin", + ["Beuzeville"] = "Beuzeville", + ["Biggin_Hill"] = "Biggin Hill", + ["Biniville"] = "Biniville", + ["Broglie"] = "Broglie", + ["Brucheville"] = "Brucheville", + ["Cardonville"] = "Cardonville", + ["Carpiquet"] = "Carpiquet", + ["Chailey"] = "Chailey", + ["Chippelle"] = "Chippelle", + ["Conches"] = "Conches", ["Cormeilles_en_Vexin"] = "Cormeilles-en-Vexin", ["Creil"] = "Creil", - ["Guyancourt"] = "Guyancourt", - ["Lonrai"] = "Lonrai", + ["Cretteville"] = "Cretteville", + ["Cricqueville_en_Bessin"] = "Cricqueville-en-Bessin", + ["Deanland"] = "Deanland", + ["Deauville"] = "Deauville", + ["Detling"] = "Detling", + ["Deux_Jumeaux"] = "Deux Jumeaux", ["Dinan_Trelivan"] = "Dinan-Trelivan", - ["Heathrow"] = "Heathrow", - ["Fecamp_Benouville"] = "Fecamp-Benouville", + ["Dunkirk_Mardyck"] = "Dunkirk-Mardyck", + ["Essay"] = "Essay", + ["Evreux"] = "Evreux", ["Farnborough"] = "Farnborough", + ["Fecamp_Benouville"] = "Fecamp-Benouville", + ["Flers"] = "Flers", + ["Ford"] = "Ford", ["Friston"] = "Friston", - ["Deanland "] = "Deanland ", - ["Triqueville"] = "Triqueville", - ["Poix"] = "Poix", + ["Funtington"] = "Funtington", + ["Goulet"] = "Goulet", + ["Gravesend"] = "Gravesend", + ["Guyancourt"] = "Guyancourt", + ["Hauterive"] = "Hauterive", + ["Heathrow"] = "Heathrow", + ["High_Halden"] = "High Halden", + ["Kenley"] = "Kenley", + ["Lantheuil"] = "Lantheuil", + ["Le_Molay"] = "Le Molay", + ["Lessay"] = "Lessay", + ["Lignerolles"] = "Lignerolles", + ["Longues_sur_Mer"] = "Longues-sur-Mer", + ["Lonrai"] = "Lonrai", + ["Lymington"] = "Lymington", + ["Lympne"] = "Lympne", + ["Manston"] = "Manston", + ["Maupertus"] = "Maupertus", + ["Meautis"] = "Meautis", + ["Merville_Calonne"] = "Merville Calonne", + ["Needs_Oar_Point"] = "Needs Oar Point", + ["Odiham"] = "Odiham", ["Orly"] = "Orly", - ["Stoney_Cross"] = "Stoney Cross", - ["Amiens_Glisy"] = "Amiens-Glisy", + ["Picauville"] = "Picauville", + ["Poix"] = "Poix", ["Ronai"] = "Ronai", ["Rouen_Boos"] = "Rouen-Boos", - ["Deauville"] = "Deauville", + ["Rucqueville"] = "Rucqueville", + ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", ["Saint_Aubin"] = "Saint-Aubin", - ["Flers"] = "Flers", - ["Avranches_Le_Val_Saint_Pere"] = "Avranches Le Val-Saint-Pere", - ["Gravesend"] = "Gravesend", - ["Beaumont_le_Roger"] = "Beaumont-le-Roger", - ["Broglie"] = "Broglie", - ["Bernay_Saint_Martin"] = "Bernay Saint Martin", - ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", - ["Biggin_Hill"] = "Biggin Hill", - ["Manston"] = "Manston", - ["Detling"] = "Detling", - ["Lympne"] = "Lympne", - ["Abbeville_Drucat"] = "Abbeville Drucat", - ["Merville_Calonne"] = "Merville Calonne", ["Saint_Omer_Wizernes"] = "Saint-Omer Wizernes", + ["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont", + ["Sainte_Croix_sur_Mer"] = "Sainte-Croix-sur-Mer", + ["Sainte_Laurent_sur_Mer"] = "Sainte-Laurent-sur-Mer", + ["Sommervieu"] = "Sommervieu", + ["Stoney_Cross"] = "Stoney Cross", + ["Tangmere"] = "Tangmere", + ["Triqueville"] = "Triqueville", + ["Villacoublay"] = "Villacoublay", + ["Vrigny"] = "Vrigny", + ["West_Malling"] = "West Malling", } --- Airbases of the Persion Gulf Map: -- --- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport --- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport --- * AIRBASE.PersianGulf.Al_Bateen_Airport --- * AIRBASE.PersianGulf.Al_Ain_International_Airport --- * AIRBASE.PersianGulf.Al_Dhafra_AB +-- * AIRBASE.PersianGulf.Abu_Dhabi_Intl +-- * AIRBASE.PersianGulf.Abu_Musa_Island +-- * AIRBASE.PersianGulf.Al_Ain_Intl +-- * AIRBASE.PersianGulf.Al_Bateen +-- * AIRBASE.PersianGulf.Al_Dhafra_AFB -- * AIRBASE.PersianGulf.Al_Maktoum_Intl --- * AIRBASE.PersianGulf.Al_Minhad_AB --- * AIRBASE.PersianGulf.Bandar_e_Jask_airfield +-- * AIRBASE.PersianGulf.Al_Minhad_AFB -- * AIRBASE.PersianGulf.Bandar_Abbas_Intl -- * AIRBASE.PersianGulf.Bandar_Lengeh +-- * AIRBASE.PersianGulf.Bandar_e_Jask -- * AIRBASE.PersianGulf.Dubai_Intl -- * AIRBASE.PersianGulf.Fujairah_Intl -- * AIRBASE.PersianGulf.Havadarya --- * AIRBASE.PersianGulf.Jiroft_Airport --- * AIRBASE.PersianGulf.Kerman_Airport +-- * AIRBASE.PersianGulf.Jiroft +-- * AIRBASE.PersianGulf.Kerman -- * AIRBASE.PersianGulf.Khasab --- * AIRBASE.PersianGulf.Kish_International_Airport --- * AIRBASE.PersianGulf.Lar_Airbase --- * AIRBASE.PersianGulf.Lavan_Island_Airport --- * AIRBASE.PersianGulf.Liwa_Airbase +-- * AIRBASE.PersianGulf.Kish_Intl +-- * AIRBASE.PersianGulf.Lar +-- * AIRBASE.PersianGulf.Lavan_Island +-- * AIRBASE.PersianGulf.Liwa_AFB -- * AIRBASE.PersianGulf.Qeshm_Island --- * AIRBASE.PersianGulf.Ras_Al_Khaimah --- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport +-- * AIRBASE.PersianGulf.Quasoura_airport +-- * AIRBASE.PersianGulf.Ras_Al_Khaimah_Intl +-- * AIRBASE.PersianGulf.Sas_Al_Nakheel -- * AIRBASE.PersianGulf.Sharjah_Intl --- * AIRBASE.PersianGulf.Shiraz_International_Airport +-- * AIRBASE.PersianGulf.Shiraz_Intl -- * AIRBASE.PersianGulf.Sir_Abu_Nuayr -- * AIRBASE.PersianGulf.Sirri_Island -- * AIRBASE.PersianGulf.Tunb_Island_AFB -- * AIRBASE.PersianGulf.Tunb_Kochak --- +-- -- @field PersianGulf AIRBASE.PersianGulf = { - ["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl", - ["Abu_Musa_Island_Airport"] = "Abu Musa Island", - ["Al_Ain_International_Airport"] = "Al Ain Intl", - ["Al_Bateen_Airport"] = "Al-Bateen", - ["Al_Dhafra_AB"] = "Al Dhafra AFB", + ["Abu_Dhabi_Intl"] = "Abu Dhabi Intl", + ["Abu_Musa_Island"] = "Abu Musa Island", + ["Al_Ain_Intl"] = "Al Ain Intl", + ["Al_Bateen"] = "Al-Bateen", + ["Al_Dhafra_AFB"] = "Al Dhafra AFB", ["Al_Maktoum_Intl"] = "Al Maktoum Intl", - ["Al_Minhad_AB"] = "Al Minhad AFB", + ["Al_Minhad_AFB"] = "Al Minhad AFB", ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Lengeh"] = "Bandar Lengeh", - ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask", + ["Bandar_e_Jask"] = "Bandar-e-Jask", ["Dubai_Intl"] = "Dubai Intl", ["Fujairah_Intl"] = "Fujairah Intl", ["Havadarya"] = "Havadarya", - ["Jiroft_Airport"] = "Jiroft", - ["Kerman_Airport"] = "Kerman", + ["Jiroft"] = "Jiroft", + ["Kerman"] = "Kerman", ["Khasab"] = "Khasab", - ["Kish_International_Airport"] = "Kish Intl", - ["Lar_Airbase"] = "Lar", - ["Lavan_Island_Airport"] = "Lavan Island", - ["Liwa_Airbase"] = "Liwa AFB", + ["Kish_Intl"] = "Kish Intl", + ["Lar"] = "Lar", + ["Lavan_Island"] = "Lavan Island", + ["Liwa_AFB"] = "Liwa AFB", ["Qeshm_Island"] = "Qeshm Island", - ["Ras_Al_Khaimah"] = "Ras Al Khaimah Intl", - ["Sas_Al_Nakheel_Airport"] = "Sas Al Nakheel", + ["Quasoura_airport"] = "Quasoura_airport", + ["Ras_Al_Khaimah_Intl"] = "Ras Al Khaimah Intl", + ["Sas_Al_Nakheel"] = "Sas Al Nakheel", ["Sharjah_Intl"] = "Sharjah Intl", - ["Shiraz_International_Airport"] = "Shiraz Intl", + ["Shiraz_Intl"] = "Shiraz Intl", ["Sir_Abu_Nuayr"] = "Sir Abu Nuayr", ["Sirri_Island"] = "Sirri Island", ["Tunb_Island_AFB"] = "Tunb Island AFB", @@ -396,311 +402,322 @@ AIRBASE.PersianGulf = { --- Airbases of The Channel Map: -- -- * AIRBASE.TheChannel.Abbeville_Drucat +-- * AIRBASE.TheChannel.Biggin_Hill +-- * AIRBASE.TheChannel.Detling +-- * AIRBASE.TheChannel.Dunkirk_Mardyck +-- * AIRBASE.TheChannel.Eastchurch +-- * AIRBASE.TheChannel.Hawkinge +-- * AIRBASE.TheChannel.Headcorn +-- * AIRBASE.TheChannel.High_Halden +-- * AIRBASE.TheChannel.Lympne +-- * AIRBASE.TheChannel.Manston -- * AIRBASE.TheChannel.Merville_Calonne -- * AIRBASE.TheChannel.Saint_Omer_Longuenesse --- * AIRBASE.TheChannel.Dunkirk_Mardyck --- * AIRBASE.TheChannel.Manston --- * AIRBASE.TheChannel.Hawkinge --- * AIRBASE.TheChannel.Lympne --- * AIRBASE.TheChannel.Detling --- * AIRBASE.TheChannel.High_Halden --- * AIRBASE.TheChannel.Biggin_Hill --- * AIRBASE.TheChannel.Eastchurch --- * AIRBASE.TheChannel.Headcorn --- +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", + ["Biggin_Hill"] = "Biggin Hill", + ["Detling"] = "Detling", + ["Dunkirk_Mardyck"] = "Dunkirk Mardyck", + ["Eastchurch"] = "Eastchurch", + ["Hawkinge"] = "Hawkinge", + ["Headcorn"] = "Headcorn", + ["High_Halden"] = "High Halden", + ["Lympne"] = "Lympne", + ["Manston"] = "Manston", ["Merville_Calonne"] = "Merville Calonne", ["Saint_Omer_Longuenesse"] = "Saint Omer Longuenesse", - ["Dunkirk_Mardyck"] = "Dunkirk Mardyck", - ["Manston"] = "Manston", - ["Hawkinge"] = "Hawkinge", - ["Lympne"] = "Lympne", - ["Detling"] = "Detling", - ["High_Halden"] = "High Halden", - ["Biggin_Hill"] = "Biggin Hill", - ["Eastchurch"] = "Eastchurch", - ["Headcorn"] = "Headcorn", } --- Airbases of the Syria map: -- --- * AIRBASE.Syria.Kuweires --- * AIRBASE.Syria.Marj_Ruhayyil --- * AIRBASE.Syria.Kiryat_Shmona --- * AIRBASE.Syria.Marj_as_Sultan_North --- * AIRBASE.Syria.Eyn_Shemer --- * AIRBASE.Syria.Incirlik --- * AIRBASE.Syria.Damascus --- * AIRBASE.Syria.Bassel_Al_Assad --- * AIRBASE.Syria.Rosh_Pina --- * AIRBASE.Syria.Aleppo --- * AIRBASE.Syria.Al_Qusayr --- * AIRBASE.Syria.Wujah_Al_Hajar --- * AIRBASE.Syria.Al_Dumayr --- * AIRBASE.Syria.Gazipasa --- * AIRBASE.Syria.Hatay --- * AIRBASE.Syria.Nicosia [Deactivated by ED as of June/2023] --- * AIRBASE.Syria.Pinarbashi --- * AIRBASE.Syria.Paphos --- * AIRBASE.Syria.Kingsfield --- * AIRBASE.Syria.Thalah --- * AIRBASE.Syria.Haifa --- * AIRBASE.Syria.Khalkhalah --- * AIRBASE.Syria.Megiddo --- * AIRBASE.Syria.Lakatamia --- * AIRBASE.Syria.Rayak --- * AIRBASE.Syria.Larnaca --- * AIRBASE.Syria.Mezzeh --- * AIRBASE.Syria.Gecitkale --- * AIRBASE.Syria.Akrotiri --- * AIRBASE.Syria.Naqoura --- * AIRBASE.Syria.Gaziantep --- * AIRBASE.Syria.Sayqal --- * AIRBASE.Syria.Tiyas --- * AIRBASE.Syria.Shayrat --- * AIRBASE.Syria.Taftanaz --- * AIRBASE.Syria.H4 --- * AIRBASE.Syria.King_Hussein_Air_College --- * AIRBASE.Syria.Rene_Mouawad --- * AIRBASE.Syria.Jirah --- * AIRBASE.Syria.Ramat_David --- * AIRBASE.Syria.Qabr_as_Sitt --- * AIRBASE.Syria.Minakh --- * AIRBASE.Syria.Adana_Sakirpasa --- * AIRBASE.Syria.Palmyra --- * AIRBASE.Syria.Hama --- * AIRBASE.Syria.Ercan --- * AIRBASE.Syria.Marj_as_Sultan_South --- * AIRBASE.Syria.Tabqa --- * AIRBASE.Syria.Beirut_Rafic_Hariri --- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.Abu_al_Duhur +-- * AIRBASE.Syria.Adana_Sakirpasa +-- * AIRBASE.Syria.Akrotiri +-- * AIRBASE.Syria.Al_Dumayr +-- * AIRBASE.Syria.Al_Qusayr +-- * AIRBASE.Syria.Aleppo +-- * AIRBASE.Syria.Amman +-- * AIRBASE.Syria.An_Nasiriyah -- * AIRBASE.Syria.At_Tanf +-- * AIRBASE.Syria.Bassel_Al_Assad +-- * AIRBASE.Syria.Beirut_Rafic_Hariri +-- * AIRBASE.Syria.Damascus +-- * AIRBASE.Syria.Deir_ez_Zor +-- * AIRBASE.Syria.Ercan +-- * AIRBASE.Syria.Eyn_Shemer +-- * AIRBASE.Syria.Gaziantep +-- * AIRBASE.Syria.Gazipasa +-- * AIRBASE.Syria.Gecitkale -- * AIRBASE.Syria.H3 -- * AIRBASE.Syria.H3_Northwest -- * AIRBASE.Syria.H3_Southwest +-- * AIRBASE.Syria.H4 +-- * AIRBASE.Syria.Haifa +-- * AIRBASE.Syria.Hama +-- * AIRBASE.Syria.Hatay +-- * AIRBASE.Syria.Herzliya +-- * AIRBASE.Syria.Incirlik +-- * AIRBASE.Syria.Jirah +-- * AIRBASE.Syria.Khalkhalah -- * AIRBASE.Syria.Kharab_Ishk --- * AIRBASE.Syria.Raj_al_Issa_East (deleted by ED) --- * AIRBASE.Syria.Raj_al_Issa_West (deleted by ED) +-- * AIRBASE.Syria.King_Abdullah_II +-- * AIRBASE.Syria.King_Hussein_Air_College +-- * AIRBASE.Syria.Kingsfield +-- * AIRBASE.Syria.Kiryat_Shmona +-- * AIRBASE.Syria.Kuweires +-- * AIRBASE.Syria.Lakatamia +-- * AIRBASE.Syria.Larnaca +-- * AIRBASE.Syria.Marj_Ruhayyil +-- * AIRBASE.Syria.Marj_as_Sultan_North +-- * AIRBASE.Syria.Marj_as_Sultan_South +-- * AIRBASE.Syria.Megiddo +-- * AIRBASE.Syria.Mezzeh +-- * AIRBASE.Syria.Minakh +-- * AIRBASE.Syria.Muwaffaq_Salti +-- * AIRBASE.Syria.Naqoura +-- * AIRBASE.Syria.Nicosia +-- * AIRBASE.Syria.Palmyra +-- * AIRBASE.Syria.Paphos +-- * AIRBASE.Syria.Pinarbashi +-- * AIRBASE.Syria.Prince_Hassan +-- * AIRBASE.Syria.Qabr_as_Sitt +-- * AIRBASE.Syria.Ramat_David +-- * AIRBASE.Syria.Rayak +-- * AIRBASE.Syria.Rene_Mouawad +-- * AIRBASE.Syria.Rosh_Pina -- * AIRBASE.Syria.Ruwayshid -- * AIRBASE.Syria.Sanliurfa +-- * AIRBASE.Syria.Sayqal +-- * AIRBASE.Syria.Shayrat +-- * AIRBASE.Syria.Tabqa +-- * AIRBASE.Syria.Taftanaz -- * AIRBASE.Syria.Tal_Siman --- * AIRBASE.Syria.Deir_ez_Zor +-- * AIRBASE.Syria.Tha_lah +-- * AIRBASE.Syria.Tiyas +-- * AIRBASE.Syria.Wujah_Al_Hajar -- --@field Syria AIRBASE.Syria={ - ["Kuweires"]="Kuweires", - ["Marj_Ruhayyil"]="Marj Ruhayyil", - ["Kiryat_Shmona"]="Kiryat Shmona", - ["Marj_as_Sultan_North"]="Marj as Sultan North", - ["Eyn_Shemer"]="Eyn Shemer", - ["Incirlik"]="Incirlik", - ["Damascus"]="Damascus", - ["Bassel_Al_Assad"]="Bassel Al-Assad", - ["Rosh_Pina"]="Rosh Pina", - ["Aleppo"]="Aleppo", - ["Al_Qusayr"]="Al Qusayr", - ["Wujah_Al_Hajar"]="Wujah Al Hajar", - ["Al_Dumayr"]="Al-Dumayr", - ["Gazipasa"]="Gazipasa", - ["Hatay"]="Hatay", - --["Nicosia"]="Nicosia", - ["Pinarbashi"]="Pinarbashi", - ["Paphos"]="Paphos", - ["Kingsfield"]="Kingsfield", - ["Thalah"]="Tha'lah", - ["Haifa"]="Haifa", - ["Khalkhalah"]="Khalkhalah", - ["Megiddo"]="Megiddo", - ["Lakatamia"]="Lakatamia", - ["Rayak"]="Rayak", - ["Larnaca"]="Larnaca", - ["Mezzeh"]="Mezzeh", - ["Gecitkale"]="Gecitkale", - ["Akrotiri"]="Akrotiri", - ["Naqoura"]="Naqoura", - ["Gaziantep"]="Gaziantep", - ["Sayqal"]="Sayqal", - ["Tiyas"]="Tiyas", - ["Shayrat"]="Shayrat", - ["Taftanaz"]="Taftanaz", - ["H4"]="H4", - ["King_Hussein_Air_College"]="King Hussein Air College", - ["Rene_Mouawad"]="Rene Mouawad", - ["Jirah"]="Jirah", - ["Ramat_David"]="Ramat David", - ["Qabr_as_Sitt"]="Qabr as Sitt", - ["Minakh"]="Minakh", - ["Adana_Sakirpasa"]="Adana Sakirpasa", - ["Palmyra"]="Palmyra", - ["Hama"]="Hama", - ["Ercan"]="Ercan", - ["Marj_as_Sultan_South"]="Marj as Sultan South", - ["Tabqa"]="Tabqa", - ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", - ["An_Nasiriyah"]="An Nasiriyah", - ["Abu_al_Duhur"]="Abu al-Duhur", - ["At_Tanf"]="At Tanf", - ["H3"]="H3", - ["H3_Northwest"]="H3 Northwest", - ["H3_Southwest"]="H3 Southwest", - ["Kharab_Ishk"]="Kharab Ishk", - -- ["Raj_al_Issa_East"]="Raj al Issa East", - -- ["Raj_al_Issa_West"]="Raj al Issa West", - ["Ruwayshid"]="Ruwayshid", - ["Sanliurfa"]="Sanliurfa", - ["Tal_Siman"]="Tal Siman", - ["Deir_ez_Zor"] = "Deir ez-Zor", + ["Abu_al_Duhur"] = "Abu al-Duhur", + ["Adana_Sakirpasa"] = "Adana Sakirpasa", + ["Akrotiri"] = "Akrotiri", + ["Al_Dumayr"] = "Al-Dumayr", + ["Al_Qusayr"] = "Al Qusayr", + ["Aleppo"] = "Aleppo", + ["Amman"] = "Amman", + ["An_Nasiriyah"] = "An Nasiriyah", + ["At_Tanf"] = "At Tanf", + ["Bassel_Al_Assad"] = "Bassel Al-Assad", + ["Beirut_Rafic_Hariri"] = "Beirut-Rafic Hariri", + ["Damascus"] = "Damascus", + ["Deir_ez_Zor"] = "Deir ez-Zor", + ["Ercan"] = "Ercan", + ["Eyn_Shemer"] = "Eyn Shemer", + ["Gaziantep"] = "Gaziantep", + ["Gazipasa"] = "Gazipasa", + ["Gecitkale"] = "Gecitkale", + ["H3"] = "H3", + ["H3_Northwest"] = "H3 Northwest", + ["H3_Southwest"] = "H3 Southwest", + ["H4"] = "H4", + ["Haifa"] = "Haifa", + ["Hama"] = "Hama", + ["Hatay"] = "Hatay", + ["Herzliya"] = "Herzliya", + ["Incirlik"] = "Incirlik", + ["Jirah"] = "Jirah", + ["Khalkhalah"] = "Khalkhalah", + ["Kharab_Ishk"] = "Kharab Ishk", + ["King_Abdullah_II"] = "King Abdullah II", + ["King_Hussein_Air_College"] = "King Hussein Air College", + ["Kingsfield"] = "Kingsfield", + ["Kiryat_Shmona"] = "Kiryat Shmona", + ["Kuweires"] = "Kuweires", + ["Lakatamia"] = "Lakatamia", + ["Larnaca"] = "Larnaca", + ["Marj_Ruhayyil"] = "Marj Ruhayyil", + ["Marj_as_Sultan_North"] = "Marj as Sultan North", + ["Marj_as_Sultan_South"] = "Marj as Sultan South", + ["Megiddo"] = "Megiddo", + ["Mezzeh"] = "Mezzeh", + ["Minakh"] = "Minakh", + ["Muwaffaq_Salti"] = "Muwaffaq Salti", + ["Naqoura"] = "Naqoura", + ["Nicosia"] = "Nicosia", + ["Palmyra"] = "Palmyra", + ["Paphos"] = "Paphos", + ["Pinarbashi"] = "Pinarbashi", + ["Prince_Hassan"] = "Prince Hassan", + ["Qabr_as_Sitt"] = "Qabr as Sitt", + ["Ramat_David"] = "Ramat David", + ["Rayak"] = "Rayak", + ["Rene_Mouawad"] = "Rene Mouawad", + ["Rosh_Pina"] = "Rosh Pina", + ["Ruwayshid"] = "Ruwayshid", + ["Sanliurfa"] = "Sanliurfa", + ["Sayqal"] = "Sayqal", + ["Shayrat"] = "Shayrat", + ["Tabqa"] = "Tabqa", + ["Taftanaz"] = "Taftanaz", + ["Tal_Siman"] = "Tal Siman", + ["Tha_lah"] = "Tha'lah", + ["Tiyas"] = "Tiyas", + ["Wujah_Al_Hajar"] = "Wujah Al Hajar", } --- Airbases of the Mariana Islands map: -- --- * AIRBASE.MarianaIslands.Rota_Intl -- * AIRBASE.MarianaIslands.Andersen_AFB -- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl --- * AIRBASE.MarianaIslands.Saipan_Intl --- * AIRBASE.MarianaIslands.Tinian_Intl +-- * AIRBASE.MarianaIslands.North_West_Field -- * AIRBASE.MarianaIslands.Olf_Orote -- * AIRBASE.MarianaIslands.Pagan_Airstrip --- * AIRBASE.MarianaIslands.North_West_Field +-- * AIRBASE.MarianaIslands.Rota_Intl +-- * AIRBASE.MarianaIslands.Saipan_Intl +-- * AIRBASE.MarianaIslands.Tinian_Intl -- -- @field MarianaIslands AIRBASE.MarianaIslands = { - ["Rota_Intl"] = "Rota Intl", ["Andersen_AFB"] = "Andersen AFB", ["Antonio_B_Won_Pat_Intl"] = "Antonio B. Won Pat Intl", - ["Saipan_Intl"] = "Saipan Intl", - ["Tinian_Intl"] = "Tinian Intl", + ["North_West_Field"] = "North West Field", ["Olf_Orote"] = "Olf Orote", ["Pagan_Airstrip"] = "Pagan Airstrip", - ["North_West_Field"] = "North West Field", + ["Rota_Intl"] = "Rota Intl", + ["Saipan_Intl"] = "Saipan Intl", + ["Tinian_Intl"] = "Tinian Intl", } --- Airbases of the South Atlantic map: -- --- * AIRBASE.SouthAtlantic.Port_Stanley --- * AIRBASE.SouthAtlantic.Mount_Pleasant --- * AIRBASE.SouthAtlantic.San_Carlos_FOB --- * AIRBASE.SouthAtlantic.Rio_Grande --- * AIRBASE.SouthAtlantic.Rio_Gallegos --- * AIRBASE.SouthAtlantic.Ushuaia --- * AIRBASE.SouthAtlantic.Ushuaia_Helo_Port --- * AIRBASE.SouthAtlantic.Punta_Arenas --- * AIRBASE.SouthAtlantic.Pampa_Guanaco --- * AIRBASE.SouthAtlantic.San_Julian --- * AIRBASE.SouthAtlantic.Puerto_Williams --- * AIRBASE.SouthAtlantic.Puerto_Natales --- * AIRBASE.SouthAtlantic.El_Calafate --- * AIRBASE.SouthAtlantic.Puerto_Santa_Cruz --- * AIRBASE.SouthAtlantic.Comandante_Luis_Piedrabuena --- * AIRBASE.SouthAtlantic.Aerodromo_De_Tolhuin --- * AIRBASE.SouthAtlantic.Porvenir_Airfield --- * AIRBASE.SouthAtlantic.Almirante_Schroeders --- * AIRBASE.SouthAtlantic.Rio_Turbio --- * AIRBASE.SouthAtlantic.Rio_Chico --- * AIRBASE.SouthAtlantic.Franco_Bianco --- * AIRBASE.SouthAtlantic.Goose_Green --- * AIRBASE.SouthAtlantic.Hipico --- * AIRBASE.SouthAtlantic.CaletaTortel --- +-- * AIRBASE.Falklands.Almirante_Schroeders +-- * AIRBASE.Falklands.Caleta_Tortel +-- * AIRBASE.Falklands.Comandante_Luis_Piedrabuena +-- * AIRBASE.Falklands.Cullen +-- * AIRBASE.Falklands.El_Calafate +-- * AIRBASE.Falklands.Franco_Bianco +-- * AIRBASE.Falklands.Gobernador_Gregores +-- * AIRBASE.Falklands.Goose_Green +-- * AIRBASE.Falklands.Gull_Point +-- * AIRBASE.Falklands.Hipico_Flying_Club +-- * AIRBASE.Falklands.Mount_Pleasant +-- * AIRBASE.Falklands.O_Higgins +-- * AIRBASE.Falklands.Pampa_Guanaco +-- * AIRBASE.Falklands.Port_Stanley +-- * AIRBASE.Falklands.Porvenir +-- * AIRBASE.Falklands.Puerto_Natales +-- * AIRBASE.Falklands.Puerto_Santa_Cruz +-- * AIRBASE.Falklands.Puerto_Williams +-- * AIRBASE.Falklands.Punta_Arenas +-- * AIRBASE.Falklands.Rio_Chico +-- * AIRBASE.Falklands.Rio_Gallegos +-- * AIRBASE.Falklands.Rio_Grande +-- * AIRBASE.Falklands.Rio_Turbio +-- * AIRBASE.Falklands.San_Carlos_FOB +-- * AIRBASE.Falklands.San_Julian +-- * AIRBASE.Falklands.Tolhuin +-- * AIRBASE.Falklands.Ushuaia +-- * AIRBASE.Falklands.Ushuaia_Helo_Port +-- --@field MarianaIslands AIRBASE.SouthAtlantic={ - ["Port_Stanley"]="Port Stanley", - ["Mount_Pleasant"]="Mount Pleasant", - ["San_Carlos_FOB"]="San Carlos FOB", - ["Rio_Grande"]="Rio Grande", - ["Rio_Gallegos"]="Rio Gallegos", - ["Ushuaia"]="Ushuaia", - ["Ushuaia_Helo_Port"]="Ushuaia Helo Port", - ["Punta_Arenas"]="Punta Arenas", - ["Pampa_Guanaco"]="Pampa Guanaco", - ["San_Julian"]="San Julian", - ["Puerto_Williams"]="Puerto Williams", - ["Puerto_Natales"]="Puerto Natales", - ["El_Calafate"]="El Calafate", - ["Puerto_Santa_Cruz"]="Puerto Santa Cruz", - ["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena", - ["Aerodromo_De_Tolhuin"]="Aerodromo De Tolhuin", - ["Porvenir_Airfield"]="Porvenir Airfield", - ["Almirante_Schroeders"]="Almirante Schroeders", - ["Rio_Turbio"]="Rio Turbio", - ["Rio_Chico"] = "Rio Chico", + ["Almirante_Schroeders"] = "Almirante Schroeders", + ["Caleta_Tortel"] = "Caleta Tortel", + ["Comandante_Luis_Piedrabuena"] = "Comandante Luis Piedrabuena", + ["Cullen"] = "Cullen", + ["El_Calafate"] = "El Calafate", ["Franco_Bianco"] = "Franco Bianco", + ["Gobernador_Gregores"] = "Gobernador Gregores", ["Goose_Green"] = "Goose Green", - ["Hipico_Flying_Club"] = "Hipico Flying Club", - ["CaletaTortel"] = "CaletaTortel", - ["Aeropuerto_de_Gobernador_Gregores"] = "Aeropuerto de Gobernador Gregores", - ["Aerodromo_O_Higgins"] = "Aerodromo O'Higgins", - ["Cullen_Airport"] = "Cullen Airport", ["Gull_Point"] = "Gull Point", + ["Hipico_Flying_Club"] = "Hipico Flying Club", + ["Mount_Pleasant"] = "Mount Pleasant", + ["O_Higgins"] = "O'Higgins", + ["Pampa_Guanaco"] = "Pampa Guanaco", + ["Port_Stanley"] = "Port Stanley", + ["Porvenir"] = "Porvenir", + ["Puerto_Natales"] = "Puerto Natales", + ["Puerto_Santa_Cruz"] = "Puerto Santa Cruz", + ["Puerto_Williams"] = "Puerto Williams", + ["Punta_Arenas"] = "Punta Arenas", + ["Rio_Chico"] = "Rio Chico", + ["Rio_Gallegos"] = "Rio Gallegos", + ["Rio_Grande"] = "Rio Grande", + ["Rio_Turbio"] = "Rio Turbio", + ["San_Carlos_FOB"] = "San Carlos FOB", + ["San_Julian"] = "San Julian", + ["Tolhuin"] = "Tolhuin", + ["Ushuaia"] = "Ushuaia", + ["Ushuaia_Helo_Port"] = "Ushuaia Helo Port", } --- Airbases of the Sinai map: -- --- * AIRBASE.Sinai.Abu_Suwayr --- * AIRBASE.Sinai.Sde_Dov --- * AIRBASE.Sinai.AzZaqaziq --- * AIRBASE.Sinai.Hatzor --- * AIRBASE.Sinai.Kedem --- * AIRBASE.Sinai.Nevatim --- * AIRBASE.Sinai.Cairo_International_Airport --- * AIRBASE.Sinai.Al_Ismailiyah --- * AIRBASE.Sinai.As_Salihiyah --- * AIRBASE.Sinai.Fayed --- * AIRBASE.Sinai.Bilbeis_Air_Base --- * AIRBASE.Sinai.Ramon_Airbase --- * AIRBASE.Sinai.Kibrit_Air_Base --- * AIRBASE.Sinai.El_Arish --- * AIRBASE.Sinai.Ovda --- * AIRBASE.Sinai.Melez --- * AIRBASE.Sinai.Al_Mansurah --- * AIRBASE.Sinai.Palmahim --- * AIRBASE.Sinai.Baluza --- * AIRBASE.Sinai.El_Gora --- * AIRBASE.Sinai.Difarsuwar_Airfield --- * AIRBASE.Sinai.Wadi_al_Jandali --- * AIRBASE.Sinai.St_Catherine --- * AIRBASE.Sinai.Tel_Nof --- * AIRBASE.Sinai.Abu_Rudeis --- * AIRBASE.Sinai.Inshas_Airbase --- * AIRBASE.Sinai.Ben_Gurion --- * AIRBASE.Sinai.Bir_Hasanah --- * AIRBASE.Sinai.Cairo_West --- +-- * AIRBASE.SinaiMap.Abu_Rudeis +-- * AIRBASE.SinaiMap.Abu_Suwayr +-- * AIRBASE.SinaiMap.Al_Ismailiyah +-- * AIRBASE.SinaiMap.Al_Mansurah +-- * AIRBASE.SinaiMap.As_Salihiyah +-- * AIRBASE.SinaiMap.AzZaqaziq +-- * AIRBASE.SinaiMap.Baluza +-- * AIRBASE.SinaiMap.Ben_Gurion +-- * AIRBASE.SinaiMap.Bilbeis_Air_Base +-- * AIRBASE.SinaiMap.Bir_Hasanah +-- * AIRBASE.SinaiMap.Cairo_International_Airport +-- * AIRBASE.SinaiMap.Cairo_West +-- * AIRBASE.SinaiMap.Difarsuwar_Airfield +-- * AIRBASE.SinaiMap.El_Arish +-- * AIRBASE.SinaiMap.El_Gora +-- * AIRBASE.SinaiMap.Fayed +-- * AIRBASE.SinaiMap.Hatzerim +-- * AIRBASE.SinaiMap.Hatzor +-- * AIRBASE.SinaiMap.Inshas_Airbase +-- * AIRBASE.SinaiMap.Kedem +-- * AIRBASE.SinaiMap.Kibrit_Air_Base +-- * AIRBASE.SinaiMap.Melez +-- * AIRBASE.SinaiMap.Nevatim +-- * AIRBASE.SinaiMap.Ovda +-- * AIRBASE.SinaiMap.Palmahim +-- * AIRBASE.SinaiMap.Ramon_Airbase +-- * AIRBASE.SinaiMap.Sde_Dov +-- * AIRBASE.SinaiMap.St_Catherine +-- * AIRBASE.SinaiMap.Tel_Nof +-- * AIRBASE.SinaiMap.Wadi_al_Jandali +-- -- @field Sinai AIRBASE.Sinai = { - ["Hatzerim"] = "Hatzerim", + ["Abu_Rudeis"] = "Abu Rudeis", ["Abu_Suwayr"] = "Abu Suwayr", - ["Sde_Dov"] = "Sde Dov", - ["AzZaqaziq"] = "AzZaqaziq", - ["Hatzor"] = "Hatzor", - ["Kedem"] = "Kedem", - ["Nevatim"] = "Nevatim", - ["Cairo_International_Airport"] = "Cairo International Airport", ["Al_Ismailiyah"] = "Al Ismailiyah", - ["As_Salihiyah"] = "As Salihiyah", - ["Fayed"] = "Fayed", - ["Bilbeis_Air_Base"] = "Bilbeis Air Base", - ["Ramon_Airbase"] = "Ramon Airbase", - ["Kibrit_Air_Base"] = "Kibrit Air Base", - ["El_Arish"] = "El Arish", - ["Ovda"] = "Ovda", - ["Melez"] = "Melez", ["Al_Mansurah"] = "Al Mansurah", - ["Palmahim"] = "Palmahim", + ["As_Salihiyah"] = "As Salihiyah", + ["AzZaqaziq"] = "AzZaqaziq", ["Baluza"] = "Baluza", - ["El_Gora"] = "El Gora", + ["Ben_Gurion"] = "Ben-Gurion", + ["Bilbeis_Air_Base"] = "Bilbeis Air Base", + ["Bir_Hasanah"] = "Bir Hasanah", + ["Cairo_International_Airport"] = "Cairo International Airport", + ["Cairo_West"] = "Cairo West", ["Difarsuwar_Airfield"] = "Difarsuwar Airfield", - ["Wadi_al_Jandali"] = "Wadi al Jandali", + ["El_Arish"] = "El Arish", + ["El_Gora"] = "El Gora", + ["Fayed"] = "Fayed", + ["Hatzerim"] = "Hatzerim", + ["Hatzor"] = "Hatzor", + ["Inshas_Airbase"] = "Inshas Airbase", + ["Kedem"] = "Kedem", + ["Kibrit_Air_Base"] = "Kibrit Air Base", + ["Melez"] = "Melez", + ["Nevatim"] = "Nevatim", + ["Ovda"] = "Ovda", + ["Palmahim"] = "Palmahim", + ["Ramon_Airbase"] = "Ramon Airbase", + ["Sde_Dov"] = "Sde Dov", ["St_Catherine"] = "St Catherine", ["Tel_Nof"] = "Tel Nof", - ["Abu_Rudeis"] = "Abu Rudeis", - ["Inshas_Airbase"] = "Inshas Airbase", - ["Ben_Gurion"] = "Ben-Gurion", - ["Bir_Hasanah"] = "Bir Hasanah", - ["Cairo_West"] = "Cairo West", + ["Wadi_al_Jandali"] = "Wadi al Jandali", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". @@ -806,13 +823,13 @@ function AIRBASE:Register(AirbaseName) -- Category. self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME - + -- H2 is bugged --if self.AirbaseName == "H4" and self.descriptors == nil then --self:E("***** H4 on Syria map is currently bugged!") --return nil --end - + -- Set category. if self.category==Airbase.Category.AIRDROME then self.isAirdrome=true @@ -830,10 +847,10 @@ function AIRBASE:Register(AirbaseName) else self:E("ERROR: Unknown airbase category!") end - + -- Init Runways. self:_InitRunways() - + -- Set the active runways based on wind direction. if self.isAirdrome then self:SetActiveRunway() @@ -847,7 +864,7 @@ function AIRBASE:Register(AirbaseName) -- Init coordinate. self:GetCoordinate() - + -- Storage. self.storage=_DATABASE:AddStorage(AirbaseName) @@ -863,7 +880,7 @@ function AIRBASE:Register(AirbaseName) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end - + -- Debug info. self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName))) @@ -949,7 +966,7 @@ function AIRBASE:GetWarehouse() return warehouse end ---- Get the warehouse storage of this airbase. The returned `STORAGE` object is the wrapper of the DCS warehouse. +--- Get the warehouse storage of this airbase. The returned `STORAGE` object is the wrapper of the DCS warehouse. -- This allows you to add and remove items such as aircraft, liquids, weapons and other equipment. -- @param #AIRBASE self -- @return Wrapper.Storage#STORAGE The storage. @@ -964,7 +981,7 @@ end function AIRBASE:SetAutoCapture(Switch) local airbase=self:GetDCSObject() - + if airbase then airbase:autoCapture(Switch) end @@ -990,11 +1007,11 @@ end --- Returns whether auto capturing of the airbase is on or off. -- @param #AIRBASE self --- @return #boolean Returns `true` if auto capturing is on, `false` if off and `nil` if the airbase object cannot be retrieved. +-- @return #boolean Returns `true` if auto capturing is on, `false` if off and `nil` if the airbase object cannot be retrieved. function AIRBASE:IsAutoCapture() local airbase=self:GetDCSObject() - + local auto=nil if airbase then auto=airbase:autoCaptureIsOn() @@ -1010,11 +1027,11 @@ end function AIRBASE:SetCoalition(Coal) local airbase=self:GetDCSObject() - + if airbase then airbase:setCoalition(Coal) end - + return self end @@ -1142,7 +1159,7 @@ function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist) return self end ---- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS. +--- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS. -- Note that this DOES NOT remove the airbase from the list. It just makes it unresponsive and silent to any radio calls to it. -- @param #AIRBASE self -- @param #boolean Silent If `true`, enable silent mode. If `false` or `nil`, disable silent mode. @@ -1151,12 +1168,12 @@ function AIRBASE:SetRadioSilentMode(Silent) -- Get DCS airbase object. local airbase=self:GetDCSObject() - + -- Set mode. if airbase then airbase:setRadioSilentMode(Silent) end - + return self end @@ -1164,18 +1181,18 @@ end -- @param #AIRBASE self -- @return #boolean If `true`, silent mode is enabled. function AIRBASE:GetRadioSilentMode() - + -- Is silent? local silent=nil -- Get DCS airbase object. local airbase=self:GetDCSObject() - + -- Set mode. if airbase then silent=airbase:getRadioSilentMode() end - + return silent end @@ -1359,7 +1376,7 @@ function AIRBASE:_InitParkingSpots() self.NparkingTerminal[terminalType]=0 end - -- Get client coordinates. + -- Get client coordinates. local function isClient(coord) local clients=_DATABASE.CLIENTS for clientname, _client in pairs(clients) do @@ -1848,7 +1865,7 @@ end -- @param #string Name Name of the runway, e.g. "31" or "21L". -- @return #AIRBASE.Runway Runway data. function AIRBASE:GetRunwayByName(Name) - + if Name==nil then return end @@ -1856,10 +1873,10 @@ function AIRBASE:GetRunwayByName(Name) if Name then for _,_runway in pairs(self.runways) do local runway=_runway --#AIRBASE.Runway - + -- Name including L or R, e.g. "31L". local name=self:GetRunwayName(runway) - + if name==Name:upper() then return runway end @@ -1888,19 +1905,19 @@ function AIRBASE:_InitRunways(IncludeInverse) self.runways={} return {} end - + --- Function to create a runway data table. local function _createRunway(name, course, width, length, center) -- Bearing in rad. local bearing=-1*course - + -- Heading in degrees. local heading=math.deg(bearing) - + -- Data table. local runway={} --#AIRBASE.Runway - + local namefromheading = math.floor(heading/10) if self.AirbaseName == AIRBASE.Syria.Beirut_Rafic_Hariri and math.abs(namefromheading-name) > 1 then @@ -1908,12 +1925,12 @@ function AIRBASE:_InitRunways(IncludeInverse) else runway.name=string.format("%02d", tonumber(name)) end - + --runway.name=string.format("%02d", tonumber(name)) runway.magheading=tonumber(runway.name)*10 runway.heading=heading runway.width=width or 0 - runway.length=length or 0 + runway.length=length or 0 runway.center=COORDINATE:NewFromVec3(center) -- Ensure heading is [0,360] @@ -1922,7 +1939,7 @@ function AIRBASE:_InitRunways(IncludeInverse) elseif runway.heading<0 then runway.heading=runway.heading+360 end - + -- For example at Nellis, DCS reports two runways, i.e. 03 and 21, BUT the "course" of both is -0.700 rad = 40 deg! -- As a workaround, I check the difference between the "magnetic" heading derived from the name and the true heading. -- If this is too large then very likely the "inverse" heading is the one we are looking for. @@ -1930,31 +1947,31 @@ function AIRBASE:_InitRunways(IncludeInverse) self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f", runway.name, runway.heading, runway.magheading)) runway.heading=runway.heading-180 end - + -- Ensure heading is [0,360] if runway.heading>360 then runway.heading=runway.heading-360 elseif runway.heading<0 then runway.heading=runway.heading+360 end - + -- Start and endpoint of runway. runway.position=runway.center:Translate(-runway.length/2, runway.heading) runway.endpoint=runway.center:Translate( runway.length/2, runway.heading) - + local init=runway.center:GetVec3() local width = runway.width/2 local L2=runway.length/2 - + local offset1 = {x = init.x + (math.cos(bearing + math.pi) * L2), y = init.z + (math.sin(bearing + math.pi) * L2)} local offset2 = {x = init.x - (math.cos(bearing + math.pi) * L2), y = init.z - (math.sin(bearing + math.pi) * L2)} - + local points={} points[1] = {x = offset1.x + (math.cos(bearing + (math.pi/2)) * width), y = offset1.y + (math.sin(bearing + (math.pi/2)) * width)} points[2] = {x = offset1.x + (math.cos(bearing - (math.pi/2)) * width), y = offset1.y + (math.sin(bearing - (math.pi/2)) * width)} points[3] = {x = offset2.x + (math.cos(bearing - (math.pi/2)) * width), y = offset2.y + (math.sin(bearing - (math.pi/2)) * width)} points[4] = {x = offset2.x + (math.cos(bearing + (math.pi/2)) * width), y = offset2.y + (math.sin(bearing + (math.pi/2)) * width)} - + -- Runway zone. runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s", self.AirbaseName, runway.name), points) @@ -1964,54 +1981,54 @@ function AIRBASE:_InitRunways(IncludeInverse) -- Get DCS object. local airbase=self:GetDCSObject() - + if airbase then - + -- Get DCS runways. local runways=airbase:getRunways() - + -- Debug info. self:T2(runways) - + if runways then - + -- Loop over runways. for _,rwy in pairs(runways) do - + -- Debug info. self:T(rwy) - + -- Get runway data. local runway=_createRunway(rwy.Name, rwy.course, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway - + -- Add to table. table.insert(Runways, runway) - + -- Include "inverse" runway. if IncludeInverse then - + -- Create "inverse". local idx=tonumber(runway.name) local name2=tostring(idx-18) if idx<18 then name2=tostring(idx+18) end - + -- Create "inverse" runway. local runway=_createRunway(name2, rwy.course-math.pi, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway - + -- Add inverse to table. table.insert(Runways, runway) - + end - + end - + end - + end - + -- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis. local rpairs={} for i,_ri in pairs(Runways) do @@ -2025,44 +2042,44 @@ function AIRBASE:_InitRunways(IncludeInverse) end end end - + local function isLeft(a, b, c) --return ((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0 return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0 end - + for i,j in pairs(rpairs) do local ri=Runways[i] --#AIRBASE.Runway local rj=Runways[j] --#AIRBASE.Runway - + -- Draw arrow. --ri.center:ArrowToAll(rj.center) - + local c0=ri.center - + -- Vector in the direction of the runway. local a=UTILS.VecTranslate(c0, 1000, ri.heading) - + -- Vector from runway i to runway j. - local b=UTILS.VecSubstract(rj.center, ri.center) + local b=UTILS.VecSubstract(rj.center, ri.center) b=UTILS.VecAdd(ri.center, b) - + -- Check if rj is left of ri. local left=isLeft(c0, a, b) - + --env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left))) - + if left then ri.isLeft=false rj.isLeft=true else ri.isLeft=true - rj.isLeft=false + rj.isLeft=false end - + --break end - + -- Set runways. self.runways=Runways @@ -2242,9 +2259,9 @@ end -- @param #string Name Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction. -- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right. function AIRBASE:SetActiveRunway(Name, PreferLeft) - + self:SetActiveRunwayTakeoff(Name, PreferLeft) - + self:SetActiveRunwayLanding(Name,PreferLeft) end @@ -2257,17 +2274,17 @@ end function AIRBASE:SetActiveRunwayLanding(Name, PreferLeft) local runway=self:GetRunwayByName(Name) - + if not runway then runway=self:GetRunwayIntoWind(PreferLeft) end - + if runway then self:T(string.format("%s: Setting active runway for landing as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for landing!") end - + self.runwayLanding=runway return runway @@ -2305,17 +2322,17 @@ end function AIRBASE:SetActiveRunwayTakeoff(Name, PreferLeft) local runway=self:GetRunwayByName(Name) - + if not runway then runway=self:GetRunwayIntoWind(PreferLeft) end - + if runway then self:T(string.format("%s: Setting active runway for takeoff as %s", self.AirbaseName, self:GetRunwayName(runway))) else self:E("ERROR: Could not set the runway for takeoff!") end - + self.runwayTakeoff=runway return runway @@ -2328,7 +2345,7 @@ end -- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right. -- @return #AIRBASE.Runway Active runway data table. function AIRBASE:GetRunwayIntoWind(PreferLeft) - + -- Get runway data. local runways=self:GetRunways() @@ -2351,24 +2368,24 @@ function AIRBASE:GetRunwayIntoWind(PreferLeft) local dotmin=nil for i,_runway in pairs(runways) do local runway=_runway --#AIRBASE.Runway - + if PreferLeft==nil or PreferLeft==runway.isLeft then -- Angle in rad. local alpha=math.rad(runway.heading) - + -- Runway vector. local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} - + -- Dot product: parallel component of the two vectors. local dot=UTILS.VecDot(Vwind, Vrunway) - + -- New min? if dotmin==nil or dot 0 then @@ -778,9 +778,9 @@ end -- @param #number Delay (Optional) Delay in seconds before the LINK4 is activated. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) - + local freq = Frequency or 336 - + -- Command to activate Link4 system. local CommandActivateLink4= { id = "ActivateLink4", @@ -790,9 +790,9 @@ function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) ["name"] = Callsign or "LNK", } } - + self:T({CommandActivateLink4}) - + if Delay and Delay>0 then SCHEDULER:New(nil, self.CommandActivateLink4, {self, Frequency, UnitID, Callsign}, Delay) else @@ -918,10 +918,10 @@ end function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay) local CommandSetFuel = { - id = 'SetUnlimitedFuel', - params = { - value = OnOff - } + id = 'SetUnlimitedFuel', + params = { + value = OnOff + } } if Delay and Delay > 0 then @@ -966,7 +966,7 @@ end -- @param #number Frequency Radio frequency in MHz. -- @param #number Modulation Radio modulation. Default `radio.modulation.AM`. -- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10. --- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. +-- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for. -- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay) @@ -1144,19 +1144,19 @@ end -- attacker:SetTask(task,2) function CONTROLLABLE:TaskStrafing( Vec2, AttackQty, Length, WeaponType, WeaponExpend, Direction, GroupAttack ) - local DCSTask = { - id = 'Strafing', - params = { + local DCSTask = { + id = 'Strafing', + params = { point = Vec2, -- req weaponType = WeaponType or 1073741822, - expend = WeaponExpend or "Auto", + expend = WeaponExpend or "Auto", attackQty = AttackQty or 1, -- req attackQtyLimit = AttackQty >1 and true or false, - direction = Direction and math.rad(Direction) or 0, + direction = Direction and math.rad(Direction) or 0, directionEnabled = Direction and true or false, - groupAttack = GroupAttack or false, - length = Length, - } + groupAttack = GroupAttack or false, + length = Length, + } } return DCSTask @@ -1495,20 +1495,20 @@ end -- @param #number LastWptNumber (optional) Waypoint of carrier group that when reached, ends the recovery tanker task -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup, Speed, Altitude, LastWptNumber) - + local LastWptFlag = type(LastWptNumber) == "number" and true or false - - local DCSTask = { + + local DCSTask = { id = "RecoveryTanker", params = { groupId = CarrierGroup:GetID(), - speed = Speed, - altitude = Altitude, - lastWptIndexFlag = LastWptFlag, + speed = Speed, + altitude = Altitude, + lastWptIndexFlag = LastWptFlag, lastWptIndex = LastWptNumber } } - + return DCSTask end @@ -1618,7 +1618,7 @@ function CONTROLLABLE:TaskGroundEscort( FollowControllable, LastWaypointIndex, O groupId = FollowControllable and FollowControllable:GetID() or nil, engagementDistMax = OrbitDistance or 2000, lastWptIndexFlag = LastWaypointIndex and true or false, - lastWptIndex = LastWaypointIndex, + lastWptIndex = LastWaypointIndex, targetTypes = TargetTypes or {"Ground vehicles"}, lastWptIndexFlagChangedManually = true, }, @@ -1811,7 +1811,7 @@ function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority) id = 'EngageTargets', key = "AntiShip", --auto = false, - --enabled = true, + --enabled = true, params = { targetTypes = TargetTypes or {"Ships"}, priority = Priority or 0 @@ -1832,7 +1832,7 @@ function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes, Priority) id = 'EngageTargets', key = "SEAD", --auto = false, - --enabled = true, + --enabled = true, params = { targetTypes = TargetTypes or {"Air Defence"}, priority = Priority or 0 @@ -1853,7 +1853,7 @@ function CONTROLLABLE:EnRouteTaskCAP(TargetTypes, Priority) id = 'EngageTargets', key = "CAP", --auto = true, - enabled = true, + enabled = true, params = { targetTypes = TargetTypes or {"Air"}, priority = Priority or 0 @@ -1863,7 +1863,7 @@ function CONTROLLABLE:EnRouteTaskCAP(TargetTypes, Priority) return DCSTask end ---- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; +--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; -- it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_engageGroup). -- @param #CONTROLLABLE self @@ -2004,7 +2004,7 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT frequency = (Frequency or 133)*1000000, modulation = Modulation or radio.modulation.AM, callname = CallsignID, - number = CallsignNumber, + number = CallsignNumber, priority = Priority or 0, }, } @@ -2013,7 +2013,7 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT end --- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. --- Assigns the controlled group to act as a Forward Air Controller or JTAC. Any detected targets will be assigned as targets to the player via the JTAC radio menu. +-- Assigns the controlled group to act as a Forward Air Controller or JTAC. Any detected targets will be assigned as targets to the player via the JTAC radio menu. -- Target designation is set to auto and is dependent on the circumstances. -- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_fac). -- @param #CONTROLLABLE self @@ -2031,7 +2031,7 @@ function CONTROLLABLE:EnRouteTaskFAC( Frequency, Modulation, CallsignID, Callsig frequency = (Frequency or 133)*1000000, modulation = Modulation or radio.modulation.AM, callname = CallsignID, - number = CallsignNumber, + number = CallsignNumber, priority = Priority or 0 } } @@ -2173,7 +2173,7 @@ do -- Patrol methods local Waypoint = Waypoints[#Waypoints] PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details. + PatrolGroup:Route( Waypoints, 2 ) -- Move after a random seconds to the Route. See the Route method for details. end end @@ -2328,7 +2328,7 @@ function CONTROLLABLE:TaskRoute( Points ) route = {points = Points}, }, } - + return DCSTask end @@ -3800,7 +3800,7 @@ function CONTROLLABLE:OptionProhibitAfterburner( Prohibit ) return self end ---- [Air] Defines the usage of Electronic Counter Measures by airborne forces. +--- [Air] Defines the usage of Electronic Counter Measures by airborne forces. -- @param #CONTROLLABLE self -- @param #number ECMvalue Can be - 0=Never on, 1=if locked by radar, 2=if detected by radar, 3=always on, defaults to 1 -- @return #CONTROLLABLE self @@ -3825,7 +3825,7 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) - + self:OptionECM(0) return self @@ -3871,6 +3871,10 @@ end -- @param #CONTROLLABLE self -- @param #table WayPoints If WayPoints is given, then use the route. -- @return #CONTROLLABLE self +-- @usage Intended Workflow is: +-- mygroup:WayPointInitialize() +-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) +-- mygroup:WayPointExecute() function CONTROLLABLE:WayPointInitialize( WayPoints ) self:F( { WayPoints } ) @@ -3902,9 +3906,15 @@ end -- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions. -- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters. -- @return #CONTROLLABLE self +-- @usage Intended Workflow is: +-- mygroup:WayPointInitialize() +-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) +-- mygroup:WayPointExecute() function CONTROLLABLE:WayPointFunction( 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 ) self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPointFunction, arg ) return self @@ -3917,6 +3927,10 @@ end -- @param #number WayPoint The WayPoint from where to execute the mission. -- @param #number WaitTime The amount seconds to wait before initiating the mission. -- @return #CONTROLLABLE self +-- @usage Intended Workflow is: +-- mygroup:WayPointInitialize() +-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... ) +-- mygroup:WayPointExecute() function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime ) self:F( { WayPoint, WaitTime } ) @@ -4102,6 +4116,74 @@ function CONTROLLABLE:SetOptionRadarUsingForContinousSearch() return self 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. -- @param #CONTROLLABLE self -- @param #number speed Speed of the controllable, default 20 @@ -4184,10 +4266,10 @@ function CONTROLLABLE:IsSubmarine() end ---- Sets the controlled group to go at the specified speed in meters per second. +--- Sets the controlled group to go at the specified speed in meters per second. -- @param #CONTROLLABLE self -- @param #number Speed Speed in meters per second --- @param #boolean Keep (Optional) When set to true, will maintain the speed on passing waypoints. If not present or false, the controlled group will return to the speed as defined by their route. +-- @param #boolean Keep (Optional) When set to true, will maintain the speed on passing waypoints. If not present or false, the controlled group will return to the speed as defined by their route. -- @return #CONTROLLABLE self function CONTROLLABLE:SetSpeed(Speed, Keep) self:F2( { self.ControllableName } ) @@ -4206,7 +4288,7 @@ end --- [AIR] Sets the controlled aircraft group to fly at the specified altitude in meters. -- @param #CONTROLLABLE self -- @param #number Altitude Altitude in meters. --- @param #boolean Keep (Optional) When set to true, will maintain the altitude on passing waypoints. If not present or false, the controlled group will return to the altitude as defined by their route. +-- @param #boolean Keep (Optional) When set to true, will maintain the altitude on passing waypoints. If not present or false, the controlled group will return to the altitude as defined by their route. -- @param #string AltType (Optional) Specifies the altitude type used. If nil, the altitude type of the current waypoint will be used. Accepted values are "BARO" and "RADIO". -- @return #CONTROLLABLE self function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType) @@ -4231,7 +4313,7 @@ end -- @usage -- local plane = GROUP:FindByName("Aerial-1") -- -- get a task shell --- local aerotask = plane:TaskAerobatics() +-- local aerotask = plane:TaskAerobatics() -- -- add a series of maneuvers -- aerotask = plane:TaskAerobaticsHorizontalEight(aerotask,1,5000,850,true,false,1,70) -- aerotask = plane:TaskAerobaticsWingoverFlight(aerotask,1,0,0,true,true,20) @@ -4246,7 +4328,7 @@ function CONTROLLABLE:TaskAerobatics() ["maneuversSequency"] = {}, }, ["enabled"] = true, - ["auto"] = false, + ["auto"] = false, } return DCSTaskAerobatics @@ -4262,15 +4344,15 @@ end -- @param #boolean StartImmediately (Optional) If true, start immediately and ignore InitAltitude and InitSpeed. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsCandle(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local CandleTask = { ["name"] = "CANDLE", ["params"] = { @@ -4298,9 +4380,9 @@ function CONTROLLABLE:TaskAerobaticsCandle(TaskAerobatics,Repeats,InitAltitude,I } } } - + table.insert(TaskAerobatics.params["maneuversSequency"],CandleTask) - + return TaskAerobatics end @@ -4316,20 +4398,20 @@ end -- @param #number Side (Optional) On which side to fly, 0 == left, 1 == right side, defaults to 0. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsEdgeFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime,Side) - + local maxrepeats = 10 local maxflight = 200 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local flighttime = FlightTime or 10 - + if flighttime > 200 then maxflight = flighttime end - + local EdgeTask = { ["name"] = "EDGE_FLIGHT", ["params"] = { @@ -4368,9 +4450,9 @@ function CONTROLLABLE:TaskAerobaticsEdgeFlight(TaskAerobatics,Repeats,InitAltitu }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],EdgeTask) - + return TaskAerobatics end @@ -4385,20 +4467,20 @@ end -- @param #number FlightTime (Optional) Time to fly this manoever in seconds, defaults to 10. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsWingoverFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) - + local maxrepeats = 10 local maxflight = 200 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local flighttime = FlightTime or 10 - + if flighttime > 200 then maxflight = flighttime end - + local WingoverTask = { ["name"] = "WINGOVER_FLIGHT", ["params"] = { @@ -4433,9 +4515,9 @@ function CONTROLLABLE:TaskAerobaticsWingoverFlight(TaskAerobatics,Repeats,InitAl }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],WingoverTask) - + return TaskAerobatics end @@ -4449,15 +4531,15 @@ end -- @param #boolean StartImmediately (Optional) If true, start immediately and ignore InitAltitude and InitSpeed. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local LoopTask = { ["name"] = "LOOP", ["params"] = { @@ -4485,9 +4567,9 @@ function CONTROLLABLE:TaskAerobaticsLoop(TaskAerobatics,Repeats,InitAltitude,Ini } } } - + table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) - + return TaskAerobatics end @@ -4503,15 +4585,15 @@ end -- @param #number RollDeg (Optional) Roll degrees for Roll 1 and 2, defaults to 60. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsHorizontalEight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local LoopTask = { ["name"] = "HORIZONTAL_EIGHT", ["params"] = { @@ -4549,12 +4631,12 @@ function CONTROLLABLE:TaskAerobaticsHorizontalEight(TaskAerobatics,Repeats,InitA ["order"] = 8, ["value"] = RollDeg or 60, }, - + } } - + table.insert(TaskAerobatics.params["maneuversSequency"],LoopTask) - + return TaskAerobatics end @@ -4569,15 +4651,15 @@ end -- @param #number Side (Optional) On which side to fly, 0 == left, 1 == right side, defaults to 0. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsHammerhead(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "HUMMERHEAD", ["params"] = { @@ -4606,12 +4688,12 @@ function CONTROLLABLE:TaskAerobaticsHammerhead(TaskAerobatics,Repeats,InitAltitu ["SIDE"] = { ["order"] = 6, ["value"] = Side or 0, - }, + }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4627,15 +4709,15 @@ end -- @param #number RollDeg (Optional) Roll degrees for Roll 1 and 2, defaults to 60. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsSkewedLoop(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "SKEWED_LOOP", ["params"] = { @@ -4668,12 +4750,12 @@ function CONTROLLABLE:TaskAerobaticsSkewedLoop(TaskAerobatics,Repeats,InitAltitu ["SIDE"] = { ["order"] = 7, ["value"] = Side or 0, - }, + }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4691,15 +4773,15 @@ end -- @param #number Angle (Optional) How many degrees to turn, defaults to 180. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollDeg,Pull,Angle) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "TURN", ["params"] = { @@ -4740,12 +4822,12 @@ function CONTROLLABLE:TaskAerobaticsTurn(TaskAerobatics,Repeats,InitAltitude,Ini ["SIDE"] = { ["order"] = 9, ["value"] = Side or 0, - }, + }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4761,19 +4843,19 @@ end -- @param #number FinalAltitude (Optional) Final altitude in meters, defaults to 1000. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsDive(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) - + local maxrepeats = 10 - + local angle = Angle - + if angle < 15 then angle = 15 elseif angle > 90 then angle = 90 end - + if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "DIVE", ["params"] = { @@ -4809,12 +4891,12 @@ function CONTROLLABLE:TaskAerobaticsDive(TaskAerobatics,Repeats,InitAltitude,Ini ["FinalAltitude"] = { ["order"] = 7, ["value"] = FinalAltitude or 1000, - }, + }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4828,15 +4910,15 @@ end -- @param #boolean StartImmediately (Optional) If true, start immediately and ignore InitAltitude and InitSpeed. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsMilitaryTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "MILITARY_TURN", ["params"] = { @@ -4864,9 +4946,9 @@ function CONTROLLABLE:TaskAerobaticsMilitaryTurn(TaskAerobatics,Repeats,InitAlti } } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4880,15 +4962,15 @@ end -- @param #boolean StartImmediately (Optional) If true, start immediately and ignore InitAltitude and InitSpeed. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsImmelmann(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "IMMELMAN", ["params"] = { @@ -4916,9 +4998,9 @@ function CONTROLLABLE:TaskAerobaticsImmelmann(TaskAerobatics,Repeats,InitAltitud } } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -4933,23 +5015,23 @@ end -- @param #number FlightTime (Optional) Time to fly this manoever in seconds, defaults to 10. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsStraightFlight(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FlightTime) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local maxflight = 200 if Repeats > maxrepeats then maxrepeats = Repeats end - + local flighttime = FlightTime or 10 - + if flighttime > 200 then maxflight = flighttime end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "STRAIGHT_FLIGHT", ["params"] = { @@ -4984,9 +5066,9 @@ function CONTROLLABLE:TaskAerobaticsStraightFlight(TaskAerobatics,Repeats,InitAl }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5002,15 +5084,15 @@ end -- @param #number FinalAltitude (Optional) Altitude to climb to in meters. Defaults to 5000m. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsClimb(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Angle,FinalAltitude) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "CLIMB", ["params"] = { @@ -5049,9 +5131,9 @@ function CONTROLLABLE:TaskAerobaticsClimb(TaskAerobatics,Repeats,InitAltitude,In }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5070,17 +5152,17 @@ end -- @param #number Angle (Optional) Angle to spiral. Can be between 15 and 90 degrees. Defaults to 45 degrees. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Roll,Side,UpDown,Angle) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 local updown = UpDown and 1 or 0 local side = Side and 1 or 0 - + local Task = { ["name"] = "SPIRAL", ["params"] = { @@ -5121,7 +5203,7 @@ function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,I ["UPDOWN"] = { ["order"] = 9, ["value"] = updown or 0, - }, + }, ["Angle"] = { ["max_v"] = 90, ["min_v"] = 15, @@ -5131,9 +5213,9 @@ function CONTROLLABLE:TaskAerobaticsSpiral(TaskAerobatics,Repeats,InitAltitude,I }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5148,21 +5230,21 @@ end -- @param #number FinalSpeed (Optional) Final speed to reach in KPH. Defaults to 500 kph. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsSplitS(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,FinalSpeed) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local maxflight = 200 if Repeats > maxrepeats then maxrepeats = Repeats end - + local finalspeed = FinalSpeed or 500 - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "SPLIT_S", ["params"] = { @@ -5194,9 +5276,9 @@ function CONTROLLABLE:TaskAerobaticsSplitS(TaskAerobatics,Repeats,InitAltitude,I }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5214,19 +5296,19 @@ end -- @param #number FixAngle (Optional) No idea what this does, can be between 0 and 180 degrees, defaults to 180. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsAileronRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle,FixAngle) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local maxflight = 200 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "AILERON_ROLL", ["params"] = { @@ -5276,9 +5358,9 @@ function CONTROLLABLE:TaskAerobaticsAileronRoll(TaskAerobatics,Repeats,InitAltit }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5296,18 +5378,18 @@ end -- @param #number MinSpeed (Optional) Minimum speed to keep in kph, defaults to 250 kph. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsForcedTurn(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,TurnAngle,Side,FlightTime,MinSpeed) - + local maxrepeats = 10 local flighttime = FlightTime or 30 local maxtime = 200 if flighttime > 200 then maxtime = flighttime end - + if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "FORCED_TURN", ["params"] = { @@ -5357,9 +5439,9 @@ function CONTROLLABLE:TaskAerobaticsForcedTurn(TaskAerobatics,Repeats,InitAltitu }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end @@ -5376,15 +5458,15 @@ end -- @param #number TurnAngle (Optional) Turn angle, defaults to 360 degrees. -- @return DCS#Task function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitude,InitSpeed,UseSmoke,StartImmediately,Side,RollRate,TurnAngle) - + local maxrepeats = 10 if Repeats > maxrepeats then maxrepeats = Repeats end - + local usesmoke = UseSmoke and 1 or 0 - + local startimmediately = StartImmediately and 1 or 0 - + local Task = { ["name"] = "BARREL_ROLL", ["params"] = { @@ -5413,7 +5495,7 @@ function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitu ["SIDE"] = { ["order"] = 6, ["value"] = Side or 0, - }, + }, ["RollRate"] = { ["max_v"] = 450, ["min_v"] = 15, @@ -5427,13 +5509,13 @@ function CONTROLLABLE:TaskAerobaticsBarrelRoll(TaskAerobatics,Repeats,InitAltitu }, } } - + table.insert(TaskAerobatics.params["maneuversSequency"],Task) - + return TaskAerobatics end - + --- [Air] Make an airplane or helicopter patrol between two points in a racetrack - resulting in a much tighter track around the start and end points. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Point1 Start point. @@ -5447,23 +5529,23 @@ end function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation, AGL, Delay) local PatrolGroup = self -- Wrapper.Group#GROUP - + if not self:IsInstanceOf( "GROUP" ) then PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end - + local delay = Delay or 1 - + self:F( { PatrolGroup = PatrolGroup:GetName() } ) - - if PatrolGroup:IsAir() then + + if PatrolGroup:IsAir() then if Formation then PatrolGroup:SetOption(AI.Option.Air.id.FORMATION,Formation) -- https://wiki.hoggitworld.com/view/DCS_option_formation end - + local FromCoord = PatrolGroup:GetCoordinate() local ToCoord = Point1:GetCoordinate() - + -- Calculate the new Route if Altitude then local asl = true @@ -5471,16 +5553,16 @@ function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation FromCoord:SetAltitude(Altitude, asl) ToCoord:SetAltitude(Altitude, asl) end - + -- Create a "air waypoint", which is a "point" structure that can be given as a parameter to a Task - local Route = {} + local Route = {} Route[#Route + 1] = FromCoord:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, description, timeReFuAr ) Route[#Route + 1] = ToCoord:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, description, timeReFuAr ) - + local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRaceTrack", Point2, Point1, Altitude, Speed, Formation, Delay ) PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. PatrolGroup:Route( Route, Delay ) -- Move after delay seconds to the Route. See the Route method for details. end - + return self end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 954d585fa..96ea2599d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1,9 +1,9 @@ --- **Wrapper** - GROUP wraps the DCS Class Group objects. --- +-- -- === --- +-- -- The @{#GROUP} class is a wrapper class to handle the DCS Group objects. --- +-- -- ## Features: -- -- * Support all DCS Group APIs. @@ -14,27 +14,31 @@ -- **IMPORTANT: ONE SHOULD NEVER SANITIZE these GROUP OBJECT REFERENCES! (make the GROUP object references nil).** -- -- === --- +-- -- For each DCS Group object alive within a running mission, a GROUP wrapper object (instance) will be created within the global _DATABASE object (an instance of @{Core.Database#DATABASE}). -- This is done at the beginning of the mission (when the mission starts), and dynamically when new DCS Group objects are spawned (using the @{Core.Spawn} class). --- +-- -- The GROUP class does not contain a :New() method, rather it provides :Find() methods to retrieve the object reference -- using the DCS Group or the DCS GroupName. -- -- The GROUP methods will reference the DCS Group object by name when it is needed during API execution. -- If the DCS Group object does not exist or is nil, the GROUP methods will return nil and may log an exception in the DCS.log file. --- +-- -- === --- +-- +-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Group) +-- +-- === +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- * **Entropy**, **Afinegan**: Came up with the requirement for AIOnOff(). -- * **Applevangelist**: various --- +-- -- === --- +-- -- @module Wrapper.Group -- @image Wrapper_Group.JPG @@ -45,9 +49,9 @@ --- Wrapper class of the DCS world Group object. --- +-- -- ## Finding groups --- +-- -- The GROUP class provides the following functions to retrieve quickly the relevant GROUP instance: -- -- * @{#GROUP.Find}(): Find a GROUP instance from the global _DATABASE object (an instance of @{Core.Database#DATABASE}) using a DCS Group object. @@ -57,28 +61,28 @@ -- -- ## Tasking of groups -- --- A GROUP is derived from the wrapper class CONTROLLABLE (@{Wrapper.Controllable#CONTROLLABLE}). +-- A GROUP is derived from the wrapper class CONTROLLABLE (@{Wrapper.Controllable#CONTROLLABLE}). -- See the @{Wrapper.Controllable} task methods section for a description of the task methods. -- -- But here is an example how a group can be assigned a task. --- +-- -- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. --- +-- -- First we look up the objects. We create a GROUP object `HeliGroup`, using the @{#GROUP:FindByName}() method, looking up the `"Helicopter"` group object. -- Same for the `"AttackGroup"`. --- +-- -- local HeliGroup = GROUP:FindByName( "Helicopter" ) -- local AttackGroup = GROUP:FindByName( "AttackGroup" ) --- --- Now we retrieve the @{Wrapper.Unit#UNIT} objects of the `AttackGroup` object, using the method `:GetUnits()`. --- +-- +-- Now we retrieve the @{Wrapper.Unit#UNIT} objects of the `AttackGroup` object, using the method `:GetUnits()`. +-- -- local AttackUnits = AttackGroup:GetUnits() --- +-- -- Tasks are actually text strings that we build using methods of GROUP. --- So first, we declare an list of `Tasks`. --- +-- So first, we declare an list of `Tasks`. +-- -- local Tasks = {} --- +-- -- Now we loop over the `AttackUnits` using a for loop. -- We retrieve the `AttackUnit` using the `AttackGroup:GetUnit()` method. -- Each `AttackUnit` found, will be attacked by `HeliGroup`, using the method `HeliGroup:TaskAttackUnit()`. @@ -86,74 +90,74 @@ -- The code will assign the task string command to the next element in the `Task` list, using `Tasks[#Tasks+1]`. -- This little code will take the count of `Task` using `#` operator, and will add `1` to the count. -- This result will be the index of the `Task` element. --- +-- -- for i = 1, #AttackUnits do -- local AttackUnit = AttackGroup:GetUnit( i ) -- Tasks[#Tasks+1] = HeliGroup:TaskAttackUnit( AttackUnit ) -- end --- +-- -- Once these tasks have been executed, a function `_Resume` will be called ... --- +-- -- Tasks[#Tasks+1] = HeliGroup:TaskFunction( "_Resume", { "''" } ) --- +-- -- -- @param Wrapper.Group#GROUP HeliGroup -- function _Resume( HeliGroup ) -- env.info( '_Resume' ) --- +-- -- HeliGroup:MessageToAll( "Resuming",10,"Info") -- end --- +-- -- Now here is where the task gets assigned! -- Using `HeliGroup:PushTask`, the task is pushed onto the task queue of the group `HeliGroup`. -- Since `Tasks` is an array of tasks, we use the `HeliGroup:TaskCombo` method to execute the tasks. -- The `HeliGroup:PushTask` method can receive a delay parameter in seconds. -- In the example, `30` is given as a delay. --- --- --- HeliGroup:PushTask( +-- +-- +-- HeliGroup:PushTask( -- HeliGroup:TaskCombo( -- Tasks --- ), 30 --- ) --- +-- ), 30 +-- ) +-- -- That's it! -- But again, please refer to the @{Wrapper.Controllable} task methods section for a description of the different task methods that are available. --- --- +-- +-- -- -- ### Obtain the mission from group templates --- +-- -- Group templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a group and assign it to another: --- +-- -- * @{Wrapper.Controllable#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. -- -- ## GROUP Command methods -- -- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} command methods section for a description of the command methods. --- +-- -- ## GROUP option methods -- -- A GROUP is a @{Wrapper.Controllable}. See the @{Wrapper.Controllable} option methods section for a description of the option methods. --- +-- -- ## GROUP Zone validation methods --- +-- -- The group can be validated whether it is completely, partly or not within a @{Core.Zone}. -- Use the following Zone validation methods on the group: --- +-- -- * @{#GROUP.IsCompletelyInZone}: Returns true if all units of the group are within a @{Core.Zone}. -- * @{#GROUP.IsPartlyInZone}: Returns true if some units of the group are within a @{Core.Zone}. -- * @{#GROUP.IsNotInZone}: Returns true if none of the group units of the group are within a @{Core.Zone}. --- +-- -- The zone can be of any @{Core.Zone} class derived from @{Core.Zone#ZONE_BASE}. So, these methods are polymorphic to the zones tested on. --- +-- -- ## GROUP AI methods --- +-- -- A GROUP has AI methods to control the AI activation. --- +-- -- * @{#GROUP.SetAIOnOff}(): Turns the GROUP AI On or Off. -- * @{#GROUP.SetAIOn}(): Turns the GROUP AI On. -- * @{#GROUP.SetAIOff}(): Turns the GROUP AI Off. --- +-- -- @field #GROUP GROUP GROUP = { ClassName = "GROUP", @@ -255,7 +259,7 @@ function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID if not _DATABASE.GROUPS[GroupName] then _DATABASE.GROUPS[GroupName] = self - end + end self:SetEventPriority( 4 ) return self @@ -270,9 +274,9 @@ end function GROUP:Register( GroupName ) local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) -- #GROUP - + self.GroupName = GroupName - + self:SetEventPriority( 4 ) return self end @@ -308,20 +312,20 @@ end -- -- Find a group with a partial group name -- local grp = GROUP:FindByMatching( "Apple" ) -- -- will return e.g. a group named "Apple-1-1" --- +-- -- -- using a pattern -- local grp = GROUP:FindByMatching( ".%d.%d$" ) -- -- will return the first group found ending in "-1-1" to "-9-9", but not e.g. "-10-1" function GROUP:FindByMatching( Pattern ) local GroupFound = nil - + for name,group in pairs(_DATABASE.GROUPS) do if string.match(name, Pattern ) then GroupFound = group break end end - + return GroupFound end @@ -333,19 +337,19 @@ end -- -- Find all group with a partial group name -- local grptable = GROUP:FindAllByMatching( "Apple" ) -- -- will return all groups with "Apple" in the name --- +-- -- -- using a pattern -- local grp = GROUP:FindAllByMatching( ".%d.%d$" ) -- -- will return the all groups found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10" function GROUP:FindAllByMatching( Pattern ) local GroupsFound = {} - + for name,group in pairs(_DATABASE.GROUPS) do if string.match(name, Pattern ) then GroupsFound[#GroupsFound+1] = group end end - + return GroupsFound end @@ -368,12 +372,12 @@ end --- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive. +-- @return DCS#Position The 3D position vectors of the POSITIONABLE or #nil if the groups not existing or alive. function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() - + if DCSPositionable then local unit = DCSPositionable:getUnits()[1] if unit then @@ -382,19 +386,19 @@ function GROUP:GetPositionVec3() -- Overridden from POSITIONABLE:GetPositionVec3 return PositionablePosition end end - + return nil end --- Returns if the group is alive. -- The Group must: --- +-- -- * Exist at run-time. -- * Has at least one unit. --- +-- -- When the first @{Wrapper.Unit} of the group is active, it will return true. -- If the first @{Wrapper.Unit} of the group is inactive, it will return false. --- +-- -- @param #GROUP self -- @return #boolean `true` if the group is alive *and* active, `false` if the group is alive but inactive or `#nil` if the group does not exist anymore. function GROUP:IsAlive() @@ -418,17 +422,17 @@ end --- Returns if the group is activated. -- @param #GROUP self --- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive. +-- @return #boolean `true` if group is activated or `#nil` The group is not existing or alive. function GROUP:IsActive() self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() -- DCS#Group - - if DCSGroup then + + if DCSGroup and DCSGroup:isExist() then local unit = DCSGroup:getUnit(1) if unit then local GroupIsActive = unit:isActive() - return GroupIsActive + return GroupIsActive end end @@ -456,21 +460,21 @@ end -- -- Ship unit example: destroy the Ship silently. -- Ship = GROUP:FindByName( "Ship" ) -- Ship:Destroy() --- +-- -- @usage -- -- Destroy without event generation example. -- Ship = GROUP:FindByName( "Boat" ) -- Ship:Destroy( false ) -- Don't generate an event upon destruction. --- +-- function GROUP:Destroy( GenerateEvent, delay ) self:F2( self.GroupName ) - + if delay and delay>0 then self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else local DCSGroup = self:GetDCSObject() - + if DCSGroup then for Index, UnitData in pairs( DCSGroup:getUnits() ) do if GenerateEvent and GenerateEvent == true then @@ -490,19 +494,19 @@ function GROUP:Destroy( GenerateEvent, delay ) DCSGroup = nil end end - + return nil end --- Returns category of the DCS Group. Returns one of --- +-- -- * Group.Category.AIRPLANE -- * Group.Category.HELICOPTER -- * Group.Category.GROUND -- * Group.Category.SHIP -- * Group.Category.TRAIN --- +-- -- @param #GROUP self -- @return DCS#Group.Category The category ID. function GROUP:GetCategory() @@ -586,12 +590,12 @@ function GROUP:HasAttribute(attribute, all) -- Get all units of the group. local _units=self:GetUnits() - + if _units then - + local _allhave=true local _onehas=false - + for _,_unit in pairs(_units) do local _unit=_unit --Wrapper.Unit#UNIT if _unit then @@ -601,17 +605,17 @@ function GROUP:HasAttribute(attribute, all) else _allhave=false end - end + end end - + if all==true then return _allhave else return _onehas end - + end - + return nil end @@ -624,27 +628,27 @@ function GROUP:GetSpeedMax() local DCSGroup = self:GetDCSObject() if DCSGroup then - + local Units=self:GetUnits() - + local speedmax=nil - + for _,unit in pairs(Units) do local unit=unit --Wrapper.Unit#UNIT - + local speed=unit:GetSpeedMax() - + if speedmax==nil or speed threatlevelMax then threatlevelMax=threatlevel @@ -2373,12 +2378,12 @@ end --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self --- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. +-- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. function GROUP:InAir() self:F2( self.GroupName ) local DCSGroup = self:GetDCSObject() - + if DCSGroup then local DCSUnit = DCSGroup:getUnit(1) if DCSUnit then @@ -2387,7 +2392,7 @@ function GROUP:InAir() return GroupInAir end end - + return nil end @@ -2400,58 +2405,58 @@ function GROUP:IsAirborne(AllUnits) -- Get all units of the group. local units=self:GetUnits() - + if units then - + if AllUnits then - + --- We want to know if ALL units are airborne. for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT - + if unit then - + -- Unit in air or not. local inair=unit:InAir() - + -- At least one unit is not in air. if not inair then return false end end - + end - + -- All units are in air. return true - + else - + --- We want to know if ANY unit is airborne. for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT - + if unit then - + -- Unit in air or not. local inair=unit:InAir() - + if inair then -- At least one unit is in air. return true end - + end - + -- No unit is in air. return false - + end end end - + return nil end @@ -2460,17 +2465,17 @@ end --- Returns the DCS descriptor table of the nth unit of the group. -- @param #GROUP self -- @param #number n (Optional) The number of the unit for which the dscriptor is returned. --- @return DCS#Object.Desc The descriptor of the first unit of the group or #nil if the group does not exist any more. +-- @return DCS#Object.Desc The descriptor of the first unit of the group or #nil if the group does not exist any more. function GROUP:GetDCSDesc(n) -- Default. n=n or 1 - + local unit=self:GetUnit(n) if unit and unit:IsAlive()~=nil then local desc=unit:GetDesc() return desc end - + return nil end @@ -2532,14 +2537,14 @@ function GROUP:GetAttribute() elseif bomber then attribute=GROUP.Attribute.AIR_BOMBER elseif awacs then - attribute=GROUP.Attribute.AIR_AWACS + attribute=GROUP.Attribute.AIR_AWACS elseif transportplane then attribute=GROUP.Attribute.AIR_TRANSPORTPLANE elseif tanker then attribute=GROUP.Attribute.AIR_TANKER -- helos elseif attackhelicopter then - attribute=GROUP.Attribute.AIR_ATTACKHELO + attribute=GROUP.Attribute.AIR_ATTACKHELO elseif transporthelo then attribute=GROUP.Attribute.AIR_TRANSPORTHELO elseif uav then @@ -2552,15 +2557,15 @@ function GROUP:GetAttribute() elseif aaa then attribute=GROUP.Attribute.GROUND_AAA elseif artillery then - attribute=GROUP.Attribute.GROUND_ARTILLERY + attribute=GROUP.Attribute.GROUND_ARTILLERY elseif tank then - attribute=GROUP.Attribute.GROUND_TANK + attribute=GROUP.Attribute.GROUND_TANK elseif ifv then - attribute=GROUP.Attribute.GROUND_IFV + attribute=GROUP.Attribute.GROUND_IFV elseif apc then attribute=GROUP.Attribute.GROUND_APC elseif infantry then - attribute=GROUP.Attribute.GROUND_INFANTRY + attribute=GROUP.Attribute.GROUND_INFANTRY elseif truck then attribute=GROUP.Attribute.GROUND_TRUCK elseif train then @@ -2593,67 +2598,67 @@ end do -- Route methods - --- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}. + --- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}. -- The following things are to be taken into account: - -- + -- -- * The group is respawned to achieve the RTB, there may be side artefacts as a result of this. (Like weapons suddenly come back). -- * A group consisting out of more than one unit, may rejoin formation when respawned. -- * A speed can be given in km/h. If no speed is specified, the maximum speed of the first unit will be taken to return to base. -- * When there is no @{Wrapper.Airbase} object specified, the group will return to the home base if the route of the group is pinned at take-off or at landing to a base. -- * When there is no @{Wrapper.Airbase} object specified and the group route is not pinned to any airbase, it will return to the nearest airbase. - -- + -- -- @param #GROUP self -- @param Wrapper.Airbase#AIRBASE RTBAirbase (optional) The @{Wrapper.Airbase} to return to. If blank, the controllable will return to the nearest friendly airbase. - -- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected. + -- @param #number Speed (optional) The Speed, if no Speed is given, 80% of maximum Speed of the group is selected. -- @return #GROUP self function GROUP:RouteRTB( RTBAirbase, Speed ) self:F( { RTBAirbase:GetName(), Speed } ) - + local DCSGroup = self:GetDCSObject() - + if DCSGroup then - + if RTBAirbase then - + -- If speed is not given take 80% of max speed. local Speed=Speed or self:GetSpeedMax()*0.8 - + -- Curent (from) waypoint. local coord=self:GetCoordinate() local PointFrom=coord:WaypointAirTurningPoint(nil, Speed) - + -- Airbase coordinate. --local PointAirbase=RTBAirbase:GetCoordinate():SetAltitude(coord.y):WaypointAirTurningPoint(nil ,Speed) - + -- Landing waypoint. More general than prev version since it should also work with FAPRS and ships. - local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed, RTBAirbase) - + local PointLanding=RTBAirbase:GetCoordinate():WaypointAirLanding(Speed, RTBAirbase) + -- Waypoint table. local Points={PointFrom, PointLanding} --local Points={PointFrom, PointAirbase, PointLanding} - + -- Debug info. self:T3(Points) -- Get group template. local Template=self:GetTemplate() - + -- Set route points. Template.route.points=Points - + -- Respawn the group. self:Respawn(Template, true) - + -- Route the group or this will not work. self:Route(Points) else - + -- Clear all tasks. self:ClearTasks() - + end end - + return self end @@ -2672,20 +2677,20 @@ do -- Event Handling -- @param #function EventFunction (optional) The function to be called when the event occurs for the GROUP. -- @return #GROUP function GROUP:HandleEvent( Event, EventFunction, ... ) - + self:EventDispatcher():OnEventForGroup( self:GetName(), EventFunction, self, Event, ... ) - + return self end - + --- UnSubscribe to a DCS event. -- @param #GROUP self -- @param Core.Event#EVENTS Event -- @return #GROUP function GROUP:UnHandleEvent( Event ) - + self:EventDispatcher():RemoveEvent( self, Event ) - + return self end @@ -2693,13 +2698,13 @@ do -- Event Handling -- @param #GROUP self -- @return #GROUP function GROUP:ResetEvents() - + self:EventDispatcher():Reset( self ) - + for UnitID, UnitData in pairs( self:GetUnits() ) do UnitData:ResetEvents() end - + return self end @@ -2712,11 +2717,11 @@ do -- Players -- @return #table The group has players, an array of player names is returned. -- @return #nil The group has no players function GROUP:GetPlayerNames() - + local HasPlayers = false - + local PlayerNames = {} - + local Units = self:GetUnits() for UnitID, UnitData in pairs( Units ) do local Unit = UnitData -- Wrapper.Unit#UNIT @@ -2725,14 +2730,14 @@ do -- Players PlayerNames = PlayerNames or {} table.insert( PlayerNames, PlayerName ) HasPlayers = true - end + end end - if HasPlayers == true then + if HasPlayers == true then self:F2( PlayerNames ) return PlayerNames end - + return nil end @@ -2741,35 +2746,35 @@ do -- Players -- @param #GROUP self -- @return #number The amount of players. function GROUP:GetPlayerCount() - + local PlayerCount = 0 - + local Units = self:GetUnits() for UnitID, UnitData in pairs( Units or {} ) do local Unit = UnitData -- Wrapper.Unit#UNIT local PlayerName = Unit:GetPlayerName() if PlayerName and PlayerName ~= "" then PlayerCount = PlayerCount + 1 - end + end end return PlayerCount end - + end --- GROUND - Switch on/off radar emissions for the group. -- @param #GROUP self -- @param #boolean switch If true, emission is enabled. If false, emission is disabled. --- @return #GROUP self +-- @return #GROUP self function GROUP:EnableEmission(switch) self:F2( self.GroupName ) local switch = switch or false - + local DCSUnit = self:GetDCSObject() - + if DCSUnit then - + DCSUnit:enableEmission(switch) end @@ -2780,8 +2785,16 @@ 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 +-- @return #GROUP self 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 ) if switch==nil then switch=false @@ -2794,8 +2807,16 @@ 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 +-- @return #GROUP self 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 ) if switch==nil then switch=false @@ -2841,17 +2862,17 @@ function GROUP:GetHighestThreat() if tl>maxtl then maxtl=tl threat=unit - end + end end end - return threat, maxtl + return threat, maxtl end return nil, nil end ---- Get TTS friendly, optionally customized callsign mainly for **player groups**. A customized callsign is taken from the #GROUP name, after an optional '#' sign, e.g. "Aerial 1-1#Ghostrider" resulting in "Ghostrider 9", or, +--- Get TTS friendly, optionally customized callsign mainly for **player groups**. A customized callsign is taken from the #GROUP name, after an optional '#' sign, e.g. "Aerial 1-1#Ghostrider" resulting in "Ghostrider 9", or, -- if that isn't available, from the playername, as set in the mission editor main screen under Logbook, after an optional '|' sign (actually, more of a personal call sign), e.g. "Apple|Moose" results in "Moose 9 1". Options see below. -- @param #GROUP self -- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1" @@ -2863,7 +2884,7 @@ end -- -- suppose there are three groups with one (client) unit each: -- -- Slot 1 -- with mission editor callsign Enfield-1 -- -- Slot 2 # Apollo 403 -- with mission editor callsign Enfield-2 --- -- Slot 3 | Apollo -- with mission editor callsign Enfield-3 +-- -- Slot 3 | Apollo -- with mission editor callsign Enfield-3 -- -- Slot 4 | Apollo -- with mission editor callsign Devil-4 -- -- and suppose these Custom CAP Flight Callsigns for use with TTS are set -- mygroup:GetCustomCallSign(true,false,{ @@ -2925,7 +2946,7 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) end return callsign end - + -- AI or not personalized if ShortCallsign then callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9 @@ -2935,7 +2956,7 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) --self:I("Generated Callsign = " .. callsign) end - + return callsign end @@ -2949,21 +2970,21 @@ end -- @param #number LastWaypoint (optional) Waypoint number of carrier group that when reached, ends the recovery tanker task. -- @return #GROUP self function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint) - + local speed = ToKIAS == true and UTILS.KnotsToAltKIAS(Speed,Altitude) or Speed speed = UTILS.KnotsToMps(speed) - + local alt = UTILS.FeetToMeters(Altitude) local delay = Delay or 1 - + local task = self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint) self:SetTask(task,delay) - + local tankertask = self:EnRouteTaskTanker() self:PushTask(tankertask,delay+2) - - return self + + return self end --- Get a list of Link16 S/TN data from a GROUP. Can (as of Nov 2023) be obtained from F-18, F-16, F-15E (not the user flyable one) and A-10C-II groups. @@ -3026,8 +3047,8 @@ function GROUP:IsAAA() local desc = unit:GetDesc() or {} local attr = desc.attributes or {} if unit:HasSEAD() then return false end - if attr["AAA"] or attr["SAM related"] then - issam = true + if attr["AAA"] or attr["SAM related"] then + issam = true end end return issam diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index 933052517..916b78a84 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -12,7 +12,8 @@ -- @image Wrapper_Static.JPG ---- @type STATIC +--- +-- @type STATIC -- @extends Wrapper.Positionable#POSITIONABLE --- Wrapper class to handle Static objects. @@ -236,7 +237,7 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay) end ---- Respawn the @{Wrapper.Unit} at the same location with the same properties. +--- Respawn the @{Wrapper.Static} at the same location with the same properties. -- This is useful to respawn a cargo after it has been destroyed. -- @param #STATIC self -- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently. @@ -248,7 +249,7 @@ function STATIC:ReSpawn(CountryID, Delay) else CountryID=CountryID or self:GetCountry() - + local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID) SpawnStatic:Spawn(nil, self.StaticName) @@ -270,8 +271,8 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay) if Delay and Delay>0 then SCHEDULER:New(nil, self.ReSpawnAt, {self, Coordinate, Heading}, Delay) - else - + else + local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry()) SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName) @@ -280,3 +281,52 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay) return self 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 diff --git a/Moose Development/Moose/Wrapper/Storage.lua b/Moose Development/Moose/Wrapper/Storage.lua index 7d4b6da12..04210136a 100644 --- a/Moose Development/Moose/Wrapper/Storage.lua +++ b/Moose Development/Moose/Wrapper/Storage.lua @@ -8,7 +8,7 @@ -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Storage). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage). -- -- === -- @@ -33,93 +33,93 @@ -- === -- -- # The STORAGE Concept --- --- The STORAGE class offers an easy-to-use wrapper interface to all DCS API functions of DCS warehouses. +-- +-- The STORAGE class offers an easy-to-use wrapper interface to all DCS API functions of DCS warehouses. -- We named the class STORAGE, because the name WAREHOUSE is already taken by another MOOSE class. --- +-- -- This class allows you to add and remove items to a DCS warehouse, such as aircraft, liquids, weapons and other equipment. --- +-- -- # Constructor --- +-- -- A DCS warehouse is associated with an airbase. Therefore, a `STORAGE` instance is automatically created, once an airbase is registered and added to the MOOSE database. --- --- You can get the `STORAGE` object from the --- --- -- Create a STORAGE instance of the Batumi warehouse +-- +-- You can get the `STORAGE` object from the +-- +-- -- Create a STORAGE instance of the Batumi warehouse -- local storage=STORAGE:FindByName("Batumi") --- +-- -- An other way to get the `STORAGE` object is to retrieve it from the AIRBASE function `AIRBASE:GetStorage()` --- +-- -- -- Get storage instance of Batumi airbase -- local Batumi=AIRBASE:FindByName("Batumi") -- local storage=Batumi:GetStorage() --- +-- -- # Aircraft, Weapons and Equipment --- +-- -- ## Adding Items --- +-- -- To add aircraft, weapons and/or othe equipment, you can use the @{#STORAGE.AddItem}() function --- +-- -- storage:AddItem("A-10C", 3) -- storage:AddItem("weapons.missiles.AIM_120C", 10) --- +-- -- This will add three A-10Cs and ten AIM-120C missiles to the warehouse inventory. --- +-- -- ## Setting Items --- +-- -- You can also explicitly set, how many items are in the inventory with the @{#STORAGE.SetItem}() function. --- +-- -- ## Removing Items --- +-- -- Items can be removed from the inventory with the @{#STORAGE.RemoveItem}() function. --- +-- -- ## Getting Amount --- +-- -- The number of items currently in the inventory can be obtained with the @{#STORAGE.GetItemAmount}() function --- +-- -- local N=storage:GetItemAmount("A-10C") -- env.info(string.format("We currently have %d A-10Cs available", N)) --- +-- -- # Liquids --- +-- -- Liquids can be added and removed by slightly different functions as described below. Currently there are four types of liquids --- +-- -- * Jet fuel `STORAGE.Liquid.JETFUEL` -- * Aircraft gasoline `STORAGE.Liquid.GASOLINE` -- * MW 50 `STORAGE.Liquid.MW50` -- * Diesel `STORAGE.Liquid.DIESEL` --- +-- -- ## Adding Liquids --- +-- -- To add a certain type of liquid, you can use the @{#STORAGE.AddItem}(Type, Amount) function --- +-- -- storage:AddLiquid(STORAGE.Liquid.JETFUEL, 10000) -- storage:AddLiquid(STORAGE.Liquid.DIESEL, 20000) --- +-- -- This will add 10,000 kg of jet fuel and 20,000 kg of diesel to the inventory. --- +-- -- ## Setting Liquids --- +-- -- You can also explicitly set the amount of liquid with the @{#STORAGE.SetLiquid}(Type, Amount) function. --- +-- -- ## Removing Liquids --- +-- -- Liquids can be removed with @{#STORAGE.RemoveLiquid}(Type, Amount) function. --- +-- -- ## Getting Amount --- +-- -- The current amount of a certain liquid can be obtained with the @{#STORAGE.GetLiquidAmount}(Type) function --- +-- -- local N=storage:GetLiquidAmount(STORAGE.Liquid.DIESEL) -- env.info(string.format("We currently have %d kg of Diesel available", N)) --- --- +-- +-- -- # Inventory --- +-- -- The current inventory of the warehouse can be obtained with the @{#STORAGE.GetInventory}() function. This returns three tables with the aircraft, liquids and weapons: --- +-- -- local aircraft, liquids, weapons=storage:GetInventory() --- +-- -- UTILS.PrintTableToLog(aircraft) -- UTILS.PrintTableToLog(liquids) -- UTILS.PrintTableToLog(weapons) @@ -168,7 +168,7 @@ function STORAGE:New(AirbaseName) local self=BASE:Inherit(self, BASE:New()) -- #STORAGE self.airbase=Airbase.getByName(AirbaseName) - + if Airbase.getWarehouse then self.warehouse=self.airbase:getWarehouse() end @@ -322,7 +322,7 @@ end function STORAGE:GetLiquidName(Type) local name="Unknown" - + if Type==STORAGE.Liquid.JETFUEL then name = "Jet fuel" elseif Type==STORAGE.Liquid.GASOLINE then @@ -411,25 +411,25 @@ function STORAGE:IsUnlimited(Type) -- Get current amount of type. local N=self:GetAmount(Type) - + local unlimited=false - + if N>0 then - + -- Remove one item. self:RemoveAmount(Type, 1) - + -- Get amount. local n=self:GetAmount(Type) - + -- If amount did not change, it is unlimited. unlimited=n==N - + -- Add item back. if not unlimited then self:AddAmount(Type, 1) end - + -- Debug info. self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) end @@ -523,7 +523,7 @@ end function STORAGE:GetInventory(Item) local inventory=self.warehouse:getInventory(Item) - + return inventory.aircraft, inventory.liquids, inventory.weapon end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 322d23540..781c71d48 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1244,7 +1244,9 @@ function UNIT:GetThreatLevel() if Attributes["Fighters"] then ThreatLevel = 10 elseif Attributes["Multirole fighters"] then ThreatLevel = 9 + elseif Attributes["Interceptors"] then ThreatLevel = 9 elseif Attributes["Battleplanes"] then ThreatLevel = 8 + elseif Attributes["Battle airplanes"] then ThreatLevel = 8 elseif Attributes["Attack helicopters"] then ThreatLevel = 7 elseif Attributes["Strategic bombers"] then ThreatLevel = 6 elseif Attributes["Bombers"] then ThreatLevel = 5 diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 42fffded4..5c9ebc53d 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -14,7 +14,7 @@ -- -- ## Additional Material: -- --- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Weapon) +-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Weapon) -- * **YouTube videos:** None -- * **Guides:** None -- @@ -69,77 +69,77 @@ -- === -- -- # The WEAPON Concept --- +-- -- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions. --- +-- -- Probably, the most striking highlight is that the position of the weapon can be tracked and its impact position can be determined, which is not -- possible with the native DCS scripting engine functions. -- -- **Note** that this wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`. -- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data. --- +-- -- # Tracking --- +-- -- The status of the weapon can be tracked with the @{#WEAPON.StartTrack} function. This function will try to determin the position of the weapon in (normally) relatively -- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 seconds. --- +-- -- Once the position cannot be retrieved any more, the weapon has impacted (or was destroyed otherwise) and the last known position is safed as the impact point. -- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions. --- +-- -- ## Impact Point Marking --- +-- -- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}. --- +-- -- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}. --- +-- -- ## Callback functions --- +-- -- It is possible to define functions that are called during the tracking of the weapon and upon impact, which help you to customize further actions. --- +-- -- ### Callback on Impact --- +-- -- The function called on impact can be set with @{#WEAPON.SetFuncImpact} --- +-- -- ### Callback when Tracking --- +-- -- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack} --- +-- -- # Target --- --- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT +-- +-- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT -- but it could also be a STATIC object. --- +-- -- Also note that the weapon does not always have a target, it can loose a target and re-aquire it and the target might change to another unit. --- +-- -- You can get the target name with the @{#WEAPON.GetTargetName} function. --- +-- -- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function. --- +-- -- # Category --- +-- -- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function. --- --- You can check if the weapon is a --- +-- +-- You can check if the weapon is a +-- -- * bomb with @{#WEAPON.IsBomb} -- * rocket with @{#WEAPON.IsRocket} -- * missile with @{#WEAPON.IsMissile} -- * shell with @{#WEAPON.IsShell} -- * torpedo with @{#WEAPON.IsTorpedo} --- +-- -- # Parameters --- +-- -- You can get various parameters of the weapon, *e.g.* --- +-- -- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate} -- * speed: @{#WEAPON.GetSpeed} -- * coalition: @{#WEAPON.GetCoalition} -- * country: @{#WEAPON.GetCountry} --- +-- -- # Dependencies --- +-- -- This class is used (at least) in the MOOSE classes: --- +-- -- * RANGE (to determine the impact points of bombs and missiles) -- * ARTY (to destroy and replace shells with smoke or illumination) -- * FOX (to destroy the missile before it hits the target) @@ -181,48 +181,48 @@ function WEAPON:New(WeaponObject) -- Inherit everything from FSM class. local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON - + -- Set DCS weapon object. self.weapon=WeaponObject - + -- Descriptors containing a lot of info. self.desc=WeaponObject:getDesc() -- This gives the object category which is always Object.Category.WEAPON! --self.category=WeaponObject:getCategory() - + -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) self.category = self.desc.category - if self:IsMissile() and self.desc.missileCategory then + if self:IsMissile() and self.desc.missileCategory then self.categoryMissile=self.desc.missileCategory end - + -- Get type name. self.typeName=WeaponObject:getTypeName() or "Unknown Type" - + -- Get name of object. Usually a number like "1234567". self.name=WeaponObject:getName() - + -- Get coaliton of weapon. self.coalition=WeaponObject:getCoalition() - + -- Get country of weapon. self.country=WeaponObject:getCountry() - + -- Get DCS unit of the launcher. self.launcher=WeaponObject:getLauncher() - + -- Get launcher of weapon. self.launcherName="Unknown Launcher" if self.launcher then self.launcherName=self.launcher:getName() self.launcherUnit=UNIT:Find(self.launcher) end - + -- Init the coordinate of the weapon from that of the launcher. self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) - + -- Set log ID. self.lid=string.format("[%s] %s | ", self.typeName, self.name) @@ -237,12 +237,12 @@ function WEAPON:New(WeaponObject) -- Set default parameters self:SetTimeStepTrack() self:SetDistanceInterceptPoint() - + -- Debug info. - local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", + local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", self.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName) self:T(self.lid..text) - + -- Descriptors. self:T2(self.desc) @@ -312,13 +312,13 @@ function WEAPON:SetSmokeImpact(Switch, SmokeColor) else self.impactSmoke=true end - + self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red return self end ---- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object. +--- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object. -- Note that this can be called many times per second. So be careful for performance reasons. -- @param #WEAPON self -- @param #function FuncTrack Function called during tracking. @@ -335,19 +335,19 @@ end -- @param #function FuncImpact Function called once the weapon impacted. -- @param ... Optional function arguments. -- @return #WEAPON self --- +-- -- @usage -- -- Function called on impact. -- local function OnImpact(Weapon) -- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon") -- end --- +-- -- -- Set which function to call. -- myweapon:SetFuncImpact(OnImpact) --- +-- -- -- Start tracking. -- myweapon:Track() --- +-- function WEAPON:SetFuncImpact(FuncImpact, ...) self.impactFunc=FuncImpact self.impactArg=arg or {} @@ -368,37 +368,37 @@ end function WEAPON:GetTarget() local target=nil --Wrapper.Object#OBJECT - + if self.weapon then - + -- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase. local object=self.weapon:getTarget() - + if object then - + -- Get object category. local category=Object.getCategory(object) - + --Target name local name=object:getName() - + -- Debug info. - self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category)) - + self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category)) + if category==Object.Category.UNIT then - + target=UNIT:FindByName(name) - + elseif category==Object.Category.STATIC then - + target=STATIC:FindByName(name, false) - + elseif category==Object.Category.SCENERY then self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) else self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) end - + end end @@ -413,25 +413,25 @@ function WEAPON:GetTargetDistance(ConversionFunction) -- Get the target of the weapon. local target=self:GetTarget() --Wrapper.Unit#UNIT - + local distance=nil if target then -- Current position of target. local tv3=target:GetVec3() - + -- Current position of weapon. local wv3=self:GetVec3() - + if tv3 and wv3 then distance=UTILS.VecDist3D(tv3, wv3) - + if ConversionFunction then distance=ConversionFunction(distance) end - + end - + end return distance @@ -445,10 +445,10 @@ function WEAPON:GetTargetName() -- Get the target of the weapon. local target=self:GetTarget() --Wrapper.Unit#UNIT - + local name="None" if target then - name=target:GetName() + name=target:GetName() end return name @@ -476,13 +476,13 @@ function WEAPON:GetSpeed(ConversionFunction) if self.weapon then local v=self:GetVelocityVec3() - + speed=UTILS.VecNorm(v) - + if ConversionFunction then speed=ConversionFunction(speed) end - + end return speed @@ -508,11 +508,11 @@ end function WEAPON:GetVec2() local vec3=self:GetVec3() - + if vec3 then - + local vec2={x=vec3.x, y=vec3.z} - + return vec2 end @@ -521,28 +521,28 @@ end --- Get type name. -- @param #WEAPON self --- @return #string The type name. +-- @return #string The type name. function WEAPON:GetTypeName() return self.typeName end --- Get coalition. -- @param #WEAPON self --- @return #number Coalition ID. +-- @return #number Coalition ID. function WEAPON:GetCoalition() return self.coalition end --- Get country. -- @param #WEAPON self --- @return #number Country ID. +-- @return #number Country ID. function WEAPON:GetCountry() return self.country end --- Get DCS object. -- @param #WEAPON self --- @return DCS#Weapon The weapon object. +-- @return DCS#Weapon The weapon object. function WEAPON:GetDCSObject() -- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE return self.weapon @@ -675,23 +675,23 @@ end function WEAPON:Destroy(Delay) if Delay and Delay>0 then - self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0) + self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0) else if self.weapon then self:T(self.lid.."Destroying Weapon NOW!") self:StopTrack() self.weapon:destroy() - end + end end - + return self end --- Start tracking the weapon until it impacts or is destroyed otherwise. --- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is --- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many +-- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is +-- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many -- calculations per second need to be carried out. --- @param #WEAPON self +-- @param #WEAPON self -- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec. -- @return #WEAPON self function WEAPON:StartTrack(Delay) @@ -700,8 +700,8 @@ function WEAPON:StartTrack(Delay) Delay=math.max(Delay or 0.001, 0.001) -- Debug info. - self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay)) - + self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay)) + -- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds. self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay) @@ -710,7 +710,7 @@ end --- Stop tracking the weapon by removing the scheduler function. --- @param #WEAPON self +-- @param #WEAPON self -- @param #number Delay (Optional) Delay in seconds before the tracking is stopped. -- @return #WEAPON self function WEAPON:StopTrack(Delay) @@ -719,13 +719,13 @@ function WEAPON:StopTrack(Delay) -- Delayed call. self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0) else - + if self.trackScheduleID then timer.removeFunction(self.trackScheduleID) end - + end return self @@ -762,10 +762,10 @@ function WEAPON:_TrackWeapon(time) -- Update last known position. self.pos3 = pos3 - + -- Update last known vec3. self.vec3 = UTILS.DeepCopy(self.pos3.p) - + -- Update coordinate. self.coordinate:UpdateFromVec3(self.vec3) @@ -774,70 +774,70 @@ function WEAPON:_TrackWeapon(time) -- Keep on tracking by returning the next time below. self.tracking=true - + -- Callback function. if self.trackFunc then self.trackFunc(self, unpack(self.trackArg)) end - + -- Verbose output. if self.verbose>=5 then - + -- Get vec2 of current position. local vec2={x=self.vec3.x, y=self.vec3.z} - + -- Land hight. local height=land.getHeight(vec2) - -- Current height above ground level. + -- Current height above ground level. local agl=self.vec3.y-height - + -- Estimated IP (if any) local ip=self:_GetIP(self.distIP) - + -- Distance between positon and estimated impact. local d=0 if ip then d=UTILS.VecDist3D(self.vec3, ip) end - + -- Output. self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d)) - + end else - + --------------------------- -- Weapon does NOT exist -- - --------------------------- - + --------------------------- + -- Get intercept point from position (p) and direction (x) in 50 meters. local ip = self:_GetIP(self.distIP) - + if self.verbose>=10 and ip then - + -- Output. self:I(self.lid.."Got intercept point!") - + -- Coordinate of the impact point. local coord=COORDINATE:NewFromVec3(ip) - + -- Mark coordinate. coord:MarkToAll("Intercept point") coord:SmokeBlue() - + -- Distance to last known pos. local d=UTILS.VecDist3D(ip, self.vec3) - + -- Output. self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d)) - + end - + -- Safe impact vec3. self.impactVec3=ip or self.vec3 - + -- Safe impact coordinate. self.impactCoord=COORDINATE:NewFromVec3(self.vec3) @@ -848,22 +848,22 @@ function WEAPON:_TrackWeapon(time) if self.impactMark then self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName)) end - + -- Smoke on impact point. if self.impactSmoke then self.impactCoord:Smoke(self.impactSmokeColor) end - + -- Call callback function. if self.impactFunc then self.impactFunc(self, unpack(self.impactArg or {})) end - + -- Stop tracking by returning nil below. self.tracking=false - + end - + -- Return next time the function is called or nil to stop the scheduler. if self.tracking then if self.dtTrack and self.dtTrack>=0.00001 then @@ -885,12 +885,12 @@ function WEAPON:_GetIP(Distance) Distance=Distance or 50 local ip=nil --DCS#Vec3 - + if Distance>0 and self.pos3 then -- Get intercept point from position (p) and direction (x) in 20 meters. ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3 - + end return ip diff --git a/docs/advanced/concepts.md b/docs/advanced/concepts.md new file mode 100644 index 000000000..a5c73de4d --- /dev/null +++ b/docs/advanced/concepts.md @@ -0,0 +1,263 @@ +--- +title: Concepts +parent: Advanced +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 +concepts that we will explain here. You will need them for the later pages. + +# Git and GitHub + +Moose has about 260.000 lines of code and the amount is increasing each week. +To maintain such a big code base a vcs (version control system) is needed. +Moose uses [GitHub] as developer platform to create, store, and manage the code. +[GitHub] uses [Git] as version control system and provides additional +functionality like access control, bug tracking, feature requests and much more. + +As a Moose user you don't need to learn how to use [Git]. You can download the +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. + +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 +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 +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]. + +Moose uses more than one repository on [GitHub] which doesn't exactly make it +any clearer. A list can be found on the [reposities] page. + +# Branches: master & develop + +As already explained in the [overview] two branches are used: + +- Branch [master]: Stable release branch. +- Branch [develop]: Newest development with more OPS classes. + +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` +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. + +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 +practice to save a bookmark in your browser with the links above. +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 +different repositories: + +- Documentation of `master` branch: [MOOSE_DOCS] +- Documentation of `develop` branch: [MOOSE_DOCS_DEVELOP] + +# Build result vs. source files + +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 +semantic is pre-defined for [IntelliSense] to work. + +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 +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 +your missions. + +The individual `.lua` files are used by the Moose developers and power users. +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 +Moose framework. + +# Static loading + +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 +file. This mission file (file extension .MIZ) is only a compressed ZIP archive +with another file ending. + +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. +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. + +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 +the scripts do we call `static loading` and the resulting mission is very +portable. + +# Dynamic loading of mission scripts + +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 +field to enter the code directly into it. It is only useful for very small +script snippets. But we can use it to load a file from your hard drive like +this: + +```lua +dofile('C:/MyScripts/hello-world.lua') +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++ + +As a beginner you should start with a good text editor, which supports syntax +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 +[GitHub]: https://github.com/ +[YouTube]: https://www.youtube.com/ +[Moose Discord]: https://discord.gg/gj68fm969S +[overview]: ../index.md +[reposities]: ../repositories.md +[master]: https://github.com/FlightControl-Master/MOOSE/tree/master +[develop]: https://github.com/FlightControl-Master/MOOSE/tree/develop +[GitHub pages]: https://pages.github.com/ +[MOOSE_DOCS]: https://flightcontrol-master.github.io/MOOSE_DOCS/ +[MOOSE_DOCS_DEVELOP]: https://flightcontrol-master.github.io/MOOSE_DOCS_DEVELOP/ +[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/ diff --git a/docs/advanced/debugger.md b/docs/advanced/debugger.md new file mode 100644 index 000000000..2c43bff06 --- /dev/null +++ b/docs/advanced/debugger.md @@ -0,0 +1,8 @@ +--- +title: Debugger +parent: Advanced +nav_order: 100 +--- + +{: .warning } +> THIS DOCUMENT IS STILL WORK IN PROGRESS! diff --git a/docs/advanced/desanitize-dcs.md b/docs/advanced/desanitize-dcs.md index 91fad46d1..2134802ae 100644 --- a/docs/advanced/desanitize-dcs.md +++ b/docs/advanced/desanitize-dcs.md @@ -1,7 +1,7 @@ --- title: De-Sanitize DCS parent: Advanced -nav_order: 2 +nav_order: 98 --- # De-Sanitize the DCS scripting environment {: .no_toc } diff --git a/docs/advanced/eclipse-installation.md b/docs/advanced/eclipse-installation.md index 0bdcd37cb..942fff30c 100644 --- a/docs/advanced/eclipse-installation.md +++ b/docs/advanced/eclipse-installation.md @@ -1,6 +1,6 @@ --- parent: Advanced -nav_order: 1 +nav_order: 97 --- # Eclipse Installation {: .no_toc } diff --git a/docs/advanced/text-to-speech.md b/docs/advanced/text-to-speech.md index 129039708..c3851da25 100644 --- a/docs/advanced/text-to-speech.md +++ b/docs/advanced/text-to-speech.md @@ -1,6 +1,6 @@ --- parent: Advanced -nav_order: 2 +nav_order: 99 --- # Text to Speech {: .no_toc } diff --git a/docs/beginner/ask-for-help.md b/docs/beginner/ask-for-help.md index bf8a9c8f6..060f26223 100644 --- a/docs/beginner/ask-for-help.md +++ b/docs/beginner/ask-for-help.md @@ -35,16 +35,24 @@ Please remember when posting a question: - Before posting anything follow the [troubleshooting steps]. - **Read your logs**. +### Formulate a good description + A post should contain the following: -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. +- A description what you expected to happen and what actually happened. + - Do not use vague words this stuff is hard to help with! Be specific. -2. Describe what happens instead. - - 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. +- Describe what happens instead. + - 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. -3. Post your code in Discord as formatted code: +### Format your 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: @@ -54,14 +62,31 @@ A post should contain the following: ![discord-multi-line-code.png](../images/beginner/discord-multi-line-code.png) -- Post your log lines with the error or warning messages. Format them like this: +### Do not post a screenshot of your code - ![discord-fomat-logs.png](../images/beginner/discord-fomat-logs.png) +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 +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. - But post your mission only when requested. - 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 help you.
diff --git a/docs/beginner/problems.md b/docs/beginner/problems.md index ebc71077a..ba88276a3 100644 --- a/docs/beginner/problems.md +++ b/docs/beginner/problems.md @@ -11,10 +11,14 @@ nav_order: 05 ## Something went wrong -If the mission shows not the expected behaviour do the following steps: +If the mission shows not the expected behavior do the following steps: 1. Double check if you added the changed mission script to the mission again! -1. Check if the triggers are configured as requested in the last sections. +1. Check if the triggers are configured as requested in the last sections: + - To load MOOSE: `4 MISSION START`, nothing on `CONDITIONS`, `DO SCRIPT FILE` to load `Moose_.lua`. + - To load mission script(s): `1 ONCE`, in `CONDITIONS` add `TIME MORE` = 1, `DO SCRIPT FILE` to load `yourscript.lua`. +1. Double check if you have the right version of MOOSE (some classes need the develop branch). +1. Try the newest version of MOOSE. ## Read the logs @@ -22,8 +26,7 @@ The DCS log is a super important and useful log for the entire of DCS World. All scripting and other errors are recorded here. It is the one stop shop for things that occurred in your mission. It will tell you if there was a mistake. -1. Open the file `dcs.log` in the `Logs` subfolder in your DCS - [Saved Games folder]. +1. Open the file `dcs.log` in the `Logs` subfolder in your DCS [Saved Games folder]. 1. Search for the following line: `*** MOOSE INCLUDE END ***` - If it is included in the log, Moose was loaded. diff --git a/docs/images/beginner/discord-fomat-logs.png b/docs/images/beginner/discord-format-logs.png similarity index 100% rename from docs/images/beginner/discord-fomat-logs.png rename to docs/images/beginner/discord-format-logs.png