Merge branch 'FF/Ops' into FF/OpsDev

This commit is contained in:
Frank 2024-03-24 17:25:02 +01:00
commit 75068a2a26
53 changed files with 3639 additions and 2484 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -78,7 +78,7 @@ CLIENTMENU_ID = 0
--- Create an new CLIENTMENU object.
-- @param #CLIENTMENU self
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is.
-- @param Wrapper.Client#CLIENT Client The client for whom this entry is. Leave as nil for a generic entry.
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
-- @param #string Function (optional) Function to call when the entry is used.
@ -324,6 +324,22 @@ end
--
-- Many functions can either change the tree for one client or for all clients.
--
-- ## Conceptual remarks
--
-- There's a couple of things to fully understand:
--
-- 1) **CLIENTMENUMANAGER** manages a set of entries from **CLIENTMENU**, it's main purpose is to administer the *shadow menu tree*, ie. a menu structure which is not
-- (yet) visible to any client
-- 2) The entries are **CLIENTMENU** objects, which are linked in a tree form. There's two ways to create them:
-- A) in the manager with ":NewEntry()" which initially
-- adds it to the shadow menu **only**
-- B) stand-alone directly as `CLIENTMENU:NewEntry()` - here it depends on whether or not you gave a CLIENT object if the entry is created as generic entry or pushed
-- a **specific** client. **Be aware** though that the entries are not managed by the CLIENTMANAGER before the next step!
-- A generic entry can be added to the manager (and the shadow tree) with `:AddEntry()` - this will also push it to all clients(!) if no client is given, or a specific client only.
-- 3) Pushing only works for alive clients.
-- 4) Live and shadow tree entries are managed via the CLIENTMENUMANAGER object.
-- 5) `Propagate()`refreshes the menu tree for all, or a single client.
--
-- ## Create a base reference tree and send to all clients
--
-- local clientset = SET_CLIENT:New():FilterStart()
@ -492,7 +508,7 @@ function CLIENTMENUMANAGER:_EventHandler(EventData)
return self
end
--- Set this Client Manager to auto-propagate menus to newly joined players. Useful if you have **one** menu structure only.
--- Set this Client Manager to auto-propagate menus **once** to newly joined players. Useful if you have **one** menu structure only. Does not automatically push follow-up changes to the client(s).
-- @param #CLIENTMENUMANAGER self
-- @return #CLIENTMENUMANAGER self
function CLIENTMENUMANAGER:InitAutoPropagation()
@ -507,7 +523,7 @@ function CLIENTMENUMANAGER:InitAutoPropagation()
return self
end
--- Create a new entry in the generic structure.
--- Create a new entry in the **generic** structure.
-- @param #CLIENTMENUMANAGER self
-- @param #string Text Text of the F10 menu entry.
-- @param #CLIENTMENU Parent The parent menu entry.
@ -695,7 +711,7 @@ function CLIENTMENUMANAGER:Propagate(Client)
return self
end
--- Push a single previously created entry into the menu structure of all clients.
--- Push a single previously created entry into the F10 menu structure of all clients.
-- @param #CLIENTMENUMANAGER self
-- @param #CLIENTMENU Entry The entry to add.
-- @param Wrapper.Client#CLIENT Client (optional) If given, make this change only for this client.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
@ -1689,8 +1781,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
-- If there is a SpawnFunction hook defined, call it.
if self.SpawnFunctionHook then
-- delay calling this for .1 seconds so that it hopefully comes after the BIRTH event of the group.
self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.1 )
-- delay calling this for .3 seconds so that it hopefully comes after the BIRTH event of the group.
self.SpawnHookScheduler:Schedule( nil, self.SpawnFunctionHook, { self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) }, 0.3 )
end
-- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
-- if self.Repeat then
@ -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

View File

@ -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

View File

@ -46,6 +46,10 @@
--
-- ===
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Zone)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit**
--
@ -340,14 +344,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()
@ -913,7 +917,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
@ -924,7 +929,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,
@ -939,7 +944,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
@ -1189,7 +1194,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()
@ -1224,7 +1229,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()
@ -1524,7 +1529,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.
@ -1555,7 +1560,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.
@ -1999,7 +2004,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.
@ -2843,7 +2848,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()
@ -2856,7 +2861,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()
@ -3844,18 +3849,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.
@ -3995,7 +4000,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.

View File

@ -914,12 +914,12 @@ function AUTOLASE:onafterMonitor(From, Event, To)
self:SetPilotMenu()
local detecteditems = self.Contacts or {} -- #table of Ops.Intelligence#INTEL.Contact
local detecteditems = self.Contacts or {} -- #table of Ops.Intel#INTEL.Contact
local groupsbythreat = {}
local report = REPORT:New("Detections")
local lines = 0
for _,_contact in pairs(detecteditems) do
local contact = _contact -- Ops.Intelligence#INTEL.Contact
local contact = _contact -- Ops.Intel#INTEL.Contact
local grp = contact.group
local coord = contact.position
local reccename = contact.recce or "none"

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Last Update: Dec 2023
-- Last Update: Feb 2024
-------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE
@ -347,17 +347,17 @@ MANTIS.SamType = {
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamData = {
["Hawk"] = { Range=44, Blindspot=0, Height=9, Type="Medium", Radar="Hawk" }, -- measures in km
["NASAMS"] = { Range=14, Blindspot=0, Height=3, Type="Short", Radar="NSAMS" },
["Patriot"] = { Range=99, Blindspot=0, Height=9, Type="Long", Radar="Patriot" },
["Rapier"] = { Range=6, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
["Hawk"] = { Range=35, Blindspot=0, Height=12, Type="Medium", Radar="Hawk" }, -- measures in km
["NASAMS"] = { Range=14, Blindspot=0, Height=7, Type="Short", Radar="NSAMS" }, -- AIM 120B
["Patriot"] = { Range=99, Blindspot=0, Height=25, Type="Long", Radar="Patriot" },
["Rapier"] = { Range=10, Blindspot=0, Height=3, Type="Short", Radar="rapier" },
["SA-2"] = { Range=40, Blindspot=7, Height=25, Type="Medium", Radar="S_75M_Volhov" },
["SA-3"] = { Range=18, Blindspot=6, Height=18, Type="Short", Radar="5p73 s-125 ln" },
["SA-5"] = { Range=250, Blindspot=7, Height=40, Type="Long", Radar="5N62V" },
["SA-6"] = { Range=25, Blindspot=0, Height=8, Type="Medium", Radar="1S91" },
["SA-10"] = { Range=119, Blindspot=0, Height=18, Type="Long" , Radar="S-300PS 4"},
["SA-11"] = { Range=35, Blindspot=0, Height=20, Type="Medium", Radar="SA-11" },
["Roland"] = { Range=8, Blindspot=0, Height=3, Type="Short", Radar="Roland" },
["Roland"] = { Range=5, Blindspot=0, Height=5, Type="Short", Radar="Roland" },
["HQ-7"] = { Range=12, Blindspot=0, Height=3, Type="Short", Radar="HQ-7" },
["SA-9"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="Strela" },
["SA-8"] = { Range=10, Blindspot=0, Height=5, Type="Short", Radar="Osa 9A33" },
@ -376,7 +376,7 @@ MANTIS.SamData = {
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" },
["TAMIR IDFA"] = { Range=20, Blindspot=0.6, Height=12.3, Type="Short", Radar="IRON_DOME_LN" },
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
["STUNNER IDFA"] = { Range=250, Blindspot=1, Height=45, Type="Long", Radar="DAVID_SLING_LN" },
}
--- SAM data HDS
@ -1222,10 +1222,10 @@ do
function MANTIS:_PreFilterHeight(height)
self:T(self.lid.."_PreFilterHeight")
local set = {}
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
local detectedgroups = dlink:GetContactTable()
for _,_contact in pairs(detectedgroups) do
local contact = _contact -- Ops.Intelligence#INTEL.Contact
local contact = _contact -- Ops.Intel#INTEL.Contact
local grp = contact.group -- Wrapper.Group#GROUP
if grp:IsAlive() then
if grp:GetHeight(true) < height then
@ -1777,7 +1777,7 @@ do
-- @return #MANTIS self
function MANTIS:_CheckDLinkState()
self:T(self.lid .. "_CheckDLinkState")
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
local TS = timer.getAbsTime()
if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
self.DLink = false

View File

@ -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.4"
}
local _SCORINGCoalition = {
@ -245,13 +248,15 @@ local _SCORINGCategory = {
--- Creates a new SCORING object to administer the scoring achieved by players.
-- @param #SCORING self
-- @param #string GameName The name of the game. This name is also logged in the CSV score file.
-- @param #string SavePath (Optional) Path where to save the CSV file, defaults to your **<User>\\Saved Games\\DCS\\Logs** folder.
-- @param #boolean AutoSave (Optional) If passed as `false`, then swith autosave off.
-- @return #SCORING self
-- @usage
--
-- -- Define a new scoring object for the mission Gori Valley.
-- ScoringObject = SCORING:New( "Gori Valley" )
--
function SCORING:New( GameName )
function SCORING:New( GameName, SavePath, AutoSave )
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() ) -- #SCORING
@ -314,7 +319,8 @@ function SCORING:New( GameName )
end )
-- Create the CSV file.
self.AutoSave = true
self.AutoSavePath = SavePath
self.AutoSave = AutoSave or true
self:OpenCSV( GameName )
return self
@ -428,6 +434,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,11 +1061,11 @@ 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
if PlayerHit.ThreatType == nil then
if PlayerHit.ThreatType == nil or PlayerHit.ThreatType == "" then
PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown"
end
@ -1141,7 +1172,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 +1319,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 +1357,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 )
@ -1811,10 +1842,11 @@ end
function SCORING:OpenCSV( ScoringCSV )
self:F( ScoringCSV )
if lfs and io and os and self.AutoSave then
if lfs and io and os and self.AutoSave == true then
if ScoringCSV then
self.ScoringCSV = ScoringCSV
local fdir = lfs.writedir() .. [[Logs\]] .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
local path = self.AutoSavePath or lfs.writedir() .. [[Logs\]]
local fdir = path .. self.ScoringCSV .. " " .. os.date( "%Y-%m-%d %H-%M-%S" ) .. ".csv"
self.CSVFile, self.err = io.open( fdir, "w+" )
if not self.CSVFile then
@ -1935,23 +1967,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
end

View File

@ -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"

View File

@ -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

View File

@ -1629,7 +1629,7 @@ WAREHOUSE = {
-- @field #boolean arrived If true, asset arrived at its destination.
--
-- @field #number damage Damage of asset group in percent.
-- @field Ops.AirWing#AIRWING.Payload payload The payload of the asset.
-- @field Ops.Airwing#AIRWING.Payload payload The payload of the asset.
-- @field Ops.OpsGroup#OPSGROUP flightgroup The flightgroup object.
-- @field Ops.Cohort#COHORT cohort The cohort this asset belongs to.
-- @field Ops.Legion#LEGION legion The legion this asset belonts to.

View File

@ -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()
@ -12123,16 +12123,18 @@ function AIRBOSS:_LSOgrade( playerData )
local GIC, nIC = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.IC )
local GAR, nAR = self:_Flightdata2Text( playerData, AIRBOSS.GroovePos.AR )
-- VTOL approach, which is graded differently (currently only Harrier).
local vtol=playerData.actype==AIRBOSS.AircraftCarrier.AV8B
-- Put everything together.
local G = GXX .. " " .. GIM .. " " .. " " .. GIC .. " " .. GAR
-- Count number of minor, normal and major deviations.
-- Count number of minor/small nS, normal nN and major/large deviations nL.
local N=nXX+nIM+nIC+nAR
local Nv=nXX+nIM
local nL=count(G, '_')/2
local nS=count(G, '%(')
local nN=N-nS-nL
local nNv=Nv-nS-nL
-- Groove time 15-18.99 sec for a unicorn. Or 60-65 for V/STOL unicorn.
local Tgroove=playerData.Tgroove
@ -12148,34 +12150,64 @@ function AIRBOSS:_LSOgrade( playerData )
G = "Unicorn"
else
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
if nL > 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Larger deviations ==> "No grade" 2.0 points.
grade="--"
points=2.0
elseif nNv >= 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Only average deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
elseif nNv < 1 and playerData.actype==AIRBOSS.AircraftCarrier.AV8B then
-- Only minor average deviations ==> "OK" Pass with minor deviations and corrections. (test nNv<=1 and)
grade="OK"
points=4.0
elseif nL > 0 then
-- Larger deviations ==> "No grade" 2.0 points.
grade="--"
points=2.0
elseif nN> 0 then
-- No larger but average deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
else
-- Only minor corrections
grade="OK"
points=4.0
end
if vtol then
-- Add AV-8B Harrier devation allowances due to lower groundspeed and 3x conventional groove time, this allows to maintain LSO tolerances while respecting the deviations are not unsafe.--Pene testing
-- Large devaitions still result in a No Grade, A Unicorn still requires a clean pass with no deviation.
-- Normal laning part at the beginning
local Gb = GXX .. " " .. GIM
-- Number of deviations that occurred at the the beginning of the landing (XX or IM). These are graded like in non-VTOL landings, i.e. on deviations is
local N=nXX+nIM
local nL=count(Gb, '_')/2
local nS=count(Gb, '%(')
local nN=N-nS-nL
-- VTOL part of the landing
local Gv = GIC .. " " .. GAR
-- Number of deviations that occurred at the the end (VTOL part) of the landing (IC or AR).
local Nv=nIC+nAR
local nLv=count(Gv, '_')/2
local nSv=count(Gv, '%(')
local nNv=Nv-nSv-nLv
if nL>0 or nLv>1 then
-- Larger deviations at XX or IM or at least one larger deviation IC or AR==> "No grade" 2.0 points.
-- In other words, we allow one larger deviation at IC+AR
grade="--"
points=2.0
elseif nN>0 or nNv>1 or nLv==1 then
-- Average deviations at XX+IM or more than one normal deviation IC or AR ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
else
-- Only minor corrections
grade="OK"
points=4.0
end
else
-- This is a normal (non-VTOL) landing.
if nL > 0 then
-- Larger deviations ==> "No grade" 2.0 points.
grade="--"
points=2.0
elseif nN> 0 then
-- No larger but average/normal deviations ==> "Fair Pass" Pass with average deviations and corrections.
grade="(OK)"
points=3.0
else
-- Only minor corrections ==> "Okay pass" 4.0 points.
grade="OK"
points=4.0
end
end
end
-- Replace" )"( and "__"

File diff suppressed because it is too large Load Diff

View File

@ -145,7 +145,7 @@
-- @field #table NassetsLegMax Number of required warehouse assets for each assigned legion.
-- @field #table requestID The ID of the queued warehouse request. Necessary to cancel the request if the mission was cancelled before the request is processed.
-- @field #table payloads User specified airwing payloads for this mission. Only these will be considered for the job!
-- @field Ops.AirWing#AIRWING.PatrolData patroldata Patrol data.
-- @field Ops.Airwing#AIRWING.PatrolData patroldata Patrol data.
--
-- @field #table specialLegions User specified legions assigned for this mission. Only these will be considered for the job!
-- @field #table specialCohorts User specified cohorts assigned for this mission. Only these will be considered for the job!
@ -338,7 +338,7 @@
--
-- ## Legion Level
--
-- Adding an AUFTRAG to an airwing is done via the @{Ops.AirWing#AIRWING.AddMission} function. See AIRWING docs for further details.
-- Adding an AUFTRAG to an airwing is done via the @{Ops.Airwing#AIRWING.AddMission} function. See AIRWING docs for further details.
-- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function.
--
-- ## Commander Level
@ -3734,7 +3734,7 @@ end
--- Add a required payload for this mission. Only these payloads will be used for this mission. If they are not available, the mission cannot start. Only available for use with an AIRWING.
-- @param #AUFTRAG self
-- @param Ops.AirWing#AIRWING.Payload Payload Required payload.
-- @param Ops.Airwing#AIRWING.Payload Payload Required payload.
-- @return #AUFTRAG self
function AUFTRAG:AddRequiredPayload(Payload)

View File

@ -36,7 +36,7 @@ do
-- @field #number Frequency
-- @field #number Modulation
-- @field Wrapper.Airbase#AIRBASE Airbase
-- @field Ops.AirWing#AIRWING AirWing
-- @field Ops.Airwing#AIRWING AirWing
-- @field #number AwacsAngels
-- @field Core.Zone#ZONE OrbitZone
-- @field #number CallSign
@ -159,10 +159,10 @@ do
--
-- ## 3 Airwing(s)
--
-- The AWACS plane, the optional escort planes, and the AI CAP planes work based on the @{Ops.AirWing} class. Read and understand the manual for this class in
-- The AWACS plane, the optional escort planes, and the AI CAP planes work based on the @{Ops.Airwing} class. Read and understand the manual for this class in
-- order to set everything up correctly. You will at least need one Squadron containing the AWACS plane itself.
--
-- Set up the AirWing
-- Set up the Airwing
--
-- local AwacsAW = AIRWING:New("AirForce WH-1","AirForce One")
-- AwacsAW:SetMarker(false)
@ -226,7 +226,7 @@ do
--
-- ## 5 Set up AWACS
--
-- -- Set up AWACS called "AWACS North". It will use the AwacsAW AirWing set up above and be of the "blue" coalition. Homebase is Kutaisi.
-- -- Set up AWACS called "AWACS North". It will use the AwacsAW Airwing set up above and be of the "blue" coalition. Homebase is Kutaisi.
-- -- The AWACS Orbit Zone is a round zone set in the mission editor named "Awacs Orbit", the FEZ is a Polygon-Zone called "Rock" we have also
-- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock".
-- -- The CAP station zone is called "Fremont". We will be on 255 AM.
@ -248,7 +248,7 @@ do
--
-- ### 5.1 Alternative - Set up as GCI (no AWACS plane needed) Theater Air Control System (TACS)
--
-- -- Set up as TACS called "GCI Senaki". It will use the AwacsAW AirWing set up above and be of the "blue" coalition. Homebase is Senaki.
-- -- Set up as TACS called "GCI Senaki". It will use the AwacsAW Airwing set up above and be of the "blue" coalition. Homebase is Senaki.
-- -- No need to set the AWACS Orbit Zone; the FEZ is still a Polygon-Zone called "Rock" we have also
-- -- set up in the mission editor with a late activated helo named "Rock#ZONE_POLYGON". Note this also sets the BullsEye to be referenced as "Rock".
-- -- The CAP station zone is called "Fremont". We will be on 255 AM. Note the Orbit Zone is given as *nil* in the `New()`-Statement
@ -508,7 +508,7 @@ do
-- @field #AWACS
AWACS = {
ClassName = "AWACS", -- #string
version = "0.2.61", -- #string
version = "0.2.64", -- #string
lid = "", -- #string
coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string
@ -852,8 +852,8 @@ AWACS.Messages = {
--- Contact Data
-- @type AWACS.ManagedContact
-- @field #number CID
-- @field Ops.Intelligence#INTEL.Contact Contact
-- @field Ops.Intelligence#INTEL.Cluster Cluster
-- @field Ops.Intel#INTEL.Contact Contact
-- @field Ops.Intel#INTEL.Cluster Cluster
-- @field #string IFF -- ID'ed or not (yet)
-- @field Ops.Target#TARGET Target
-- @field #number LinkedTask --> TID
@ -902,8 +902,8 @@ AWACS.TaskStatus = {
-- @field #AWACS.TaskStatus Status
-- @field #AWACS.TaskDescription ToDo
-- @field #string ScreenText Long descrition
-- @field Ops.Intelligence#INTEL.Contact Contact
-- @field Ops.Intelligence#INTEL.Cluster Cluster
-- @field Ops.Intel#INTEL.Contact Contact
-- @field Ops.Intel#INTEL.Cluster Cluster
-- @field #number CurrentAuftrag
-- @field #number RequestedTimestamp
@ -956,7 +956,7 @@ AWACS.TaskStatus = {
-- DONE - Shift Change, Change on asset RTB or dead or mission done (done for AWACS and Escorts)
-- DONE - TripWire - WIP - Threat (35nm), Meld (45nm, on mission), Merged (<3nm)
--
-- DONE - Escorts via AirWing not staying on
-- DONE - Escorts via Airwing not staying on
-- DONE - Borders for INTEL. Optional, i.e. land based defense within borders
-- DONE - Use AO as Anchor of Bulls, AO as default
-- DONE - SRS TTS output
@ -984,7 +984,7 @@ AWACS.TaskStatus = {
--- Set up a new AI AWACS.
-- @param #AWACS self
-- @param #string Name Name of this AWACS for the radio menu.
-- @param #string AirWing The core Ops.AirWing#AIRWING managing the AWACS, Escort and (optionally) AI CAP planes for us.
-- @param #string AirWing The core Ops.Airwing#AIRWING managing the AWACS, Escort and (optionally) AI CAP planes for us.
-- @param #number Coalition Coalition, e.g. coalition.side.BLUE. Can also be passed as "blue", "red" or "neutral".
-- @param #string AirbaseName Name of the home airbase.
-- @param #string AwacsOrbit Name of the round, mission editor created zone where this AWACS orbits.
@ -1024,7 +1024,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
-- base setup
self.Name = Name -- #string
self.AirWing = AirWing -- Ops.AirWing#AIRWING object
self.AirWing = AirWing -- Ops.Airwing#AIRWING object
AirWing:SetUsingOpsAwacs(self)
@ -1032,7 +1032,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
self.CAPAirwings:Push(AirWing,1)
self.AwacsFG = nil
--self.AwacsPayload = PayLoad -- Ops.AirWing#AIRWING.Payload
--self.AwacsPayload = PayLoad -- Ops.Airwing#AIRWING.Payload
--self.ModernEra = true -- use of EPLRS
self.RadarBlur = 15 -- +/-15% detection precision i.e. 85-115 reported group size
if type(OpsZone) == "string" then
@ -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)
@ -2463,7 +2469,7 @@ function AWACS:_UpdateContactFromCluster(CID)
local function GetFirstAliveContact(table)
for _,_contact in pairs (table) do
local contact = _contact -- Ops.Intelligence#INTEL.Contact
local contact = _contact -- Ops.Intel#INTEL.Contact
if contact and contact.group and contact.group:IsAlive() then
return contact
end
@ -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("====================")
@ -4260,7 +4279,7 @@ function AWACS:_StartIntel(awacs)
intel:__Start(5)
self.intel = intel -- Ops.Intelligence#INTEL
self.intel = intel -- Ops.Intel#INTEL
return self
end
@ -4420,8 +4439,8 @@ end
-- @param #table Object Object for Ops.Target#TARGET assignment
-- @param #AWACS.TaskStatus TaskStatus Status of this task
-- @param Ops.Auftrag#AUFTRAG Auftrag The Auftrag for this task if any
-- @param Ops.Intelligence#INTEL.Cluster Cluster Intel Cluster for this task
-- @param Ops.Intelligence#INTEL.Contact Contact Intel Contact for this task
-- @param Ops.Intel#INTEL.Cluster Cluster Intel Cluster for this task
-- @param Ops.Intel#INTEL.Contact Contact Intel Contact for this task
-- @return #number TID Task ID created
function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object,TaskStatus,Auftrag,Cluster,Contact)
self:T(self.lid.."_CreateTaskForGroup "..GroupID .." Description: "..Description)
@ -4620,7 +4639,7 @@ function AWACS:_CheckTaskQueue()
-- Check ranges for TAC and MELD
-- postions relative to CAP position
--[[
local targetgrp = entry.Contact.group
local position = entry.Contact.position or entry.Cluster.coordinate
if targetgrp and targetgrp:IsAlive() and managedgroup then
@ -4645,6 +4664,7 @@ function AWACS:_CheckTaskQueue()
end
end
end
--]]
local auftrag = entry.Auftrag -- Ops.Auftrag#AUFTRAG
local auftragstatus = "Not Known"
@ -4843,6 +4863,7 @@ function AWACS:_CheckTaskQueue()
elseif entry.Status == AWACS.TaskStatus.ASSIGNED then
self:T("Open Tasks VID ASSIGNED for GroupID "..entry.AssignedGroupID)
-- check TAC/MELD ranges
--[[
local targetgrp = entry.Contact.group
local position = entry.Contact.position or entry.Cluster.coordinate
if targetgrp and targetgrp:IsAlive() and managedgroup then
@ -4867,6 +4888,7 @@ function AWACS:_CheckTaskQueue()
end
end
end
--]]
elseif entry.Status == AWACS.TaskStatus.SUCCESS then
self:T("Open Tasks VID success for GroupID "..entry.AssignedGroupID)
-- outcomes - player ID'd
@ -4978,7 +5000,7 @@ end
--- [User] Add another AirWing for AI CAP Flights under management
-- @param #AWACS self
-- @param Ops.AirWing#AIRWING AirWing The AirWing to (also) obtain CAP flights from
-- @param Ops.Airwing#AIRWING AirWing The AirWing to (also) obtain CAP flights from
-- @param Core.Zone#ZONE_RADIUS Zone (optional) This AirWing has it's own station zone, AI CAP will be send there
-- @return #AWACS self
function AWACS:AddCAPAirWing(AirWing,Zone)
@ -5063,7 +5085,7 @@ function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope,Tag,IsPopup,Repo
end
local cluster = Contact.Cluster
local intel = self.intel -- Ops.Intelligence#INTEL
local intel = self.intel -- Ops.Intel#INTEL
local size = self.intel:ClusterCountUnits(cluster)
local threatsize, threatsizetext = self:_GetBlurredSize(size)
@ -5465,9 +5487,10 @@ function AWACS:_TACRangeCall(GID,Contact)
if not Contact then return self end
local pilotcallsign = self:_GetCallSign(nil,GID)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local contact = Contact.Contact -- Ops.Intelligence#INTEL.Contact
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming
if contact and not Contact.TACCallDone then
local name = managedgroup.GroupName
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())
@ -5475,8 +5498,18 @@ function AWACS:_TACRangeCall(GID,Contact)
local grptxt = self.gettext:GetEntry("GROUP",self.locale)
local miles = self.gettext:GetEntry("MILES",self.locale)
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)
if not self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
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
if self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end
end
return self
@ -5494,9 +5527,10 @@ function AWACS:_MeldRangeCall(GID,Contact)
local pilotcallsign = self:_GetCallSign(nil,GID)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local flightpos = managedgroup.Group:GetCoordinate()
local contact = Contact.Contact -- Ops.Intelligence#INTEL.Contact
local contacttag = Contact.TargetGroupNaming
if contact and not Contact.MeldCallDone then
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming or "Bogey"
local name = managedgroup.GroupName
if contact then --and not Contact.MeldCallDone then
local position = contact.position -- Core.Point#COORDINATE
if position then
local BRATExt = ""
@ -5507,8 +5541,19 @@ function AWACS:_MeldRangeCall(GID,Contact)
end
local grptxt = self.gettext:GetEntry("GROUP",self.locale)
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)
if not self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
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
@ -5524,8 +5569,10 @@ function AWACS:_ThreatRangeCall(GID,Contact)
local pilotcallsign = self:_GetCallSign(nil,GID)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local flightpos = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition
local contact = Contact.Contact -- Ops.Intelligence#INTEL.Contact
local contacttag = Contact.TargetGroupNaming
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming or "Bogey"
local name = managedgroup.GroupName
local IsSub = self.TacticalSubscribers[name] and true or false
if contact then
local position = contact.position or contact.group:GetCoordinate() -- Core.Point#COORDINATE
if position then
@ -5538,7 +5585,18 @@ function AWACS:_ThreatRangeCall(GID,Contact)
local grptxt = self.gettext:GetEntry("GROUP",self.locale)
local thrt = self.gettext:GetEntry("THREAT",self.locale)
local text = string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt, thrt, BRATExt)
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
if IsSub == false then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
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
@ -5554,11 +5612,17 @@ function AWACS:_MergedCall(GID)
local pilotcallsign = self:_GetCallSign(nil,GID)
local merge = self.gettext:GetEntry("MERGED",self.locale)
local text = string.format("%s. %s. %s.",self.callsigntxt,pilotcallsign,merge)
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local name
if managedgroup then
name = managedgroup.GroupName or "none"
end
if not self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
end
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 managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
if self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
@ -5832,7 +5896,7 @@ function AWACS:onafterStart(From, Event, To)
if not self.GCI then
-- set up the AWACS and let it orbit
local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING
local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
mission:SetTime(nil,timeonstation)
@ -5953,6 +6017,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
@ -6426,7 +6494,7 @@ end
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.Intelligence#INTEL.Cluster Cluster
-- @param Ops.Intel#INTEL.Cluster Cluster
-- @return #AWACS self
function AWACS:onafterNewCluster(From,Event,To,Cluster)
self:T({From, Event, To, Cluster.index})
@ -6438,7 +6506,7 @@ function AWACS:onafterNewCluster(From,Event,To,Cluster)
local function GetFirstAliveContact(table)
for _,_contact in pairs (table) do
local contact = _contact -- Ops.Intelligence#INTEL.Contact
local contact = _contact -- Ops.Intel#INTEL.Contact
if contact and contact.group and contact.group:IsAlive() then
return contact, contact.group
end
@ -6446,7 +6514,7 @@ function AWACS:onafterNewCluster(From,Event,To,Cluster)
return nil
end
local Contact, Group = GetFirstAliveContact(ContactTable) -- Ops.Intelligence#INTEL.Contact
local Contact, Group = GetFirstAliveContact(ContactTable) -- Ops.Intel#INTEL.Contact
if not Contact then return self end
@ -6457,7 +6525,7 @@ function AWACS:onafterNewCluster(From,Event,To,Cluster)
local targetset = SET_GROUP:New()
-- SET for TARGET
for _,_grp in pairs(ContactTable) do
local grp = _grp -- Ops.Intelligence#INTEL.Contact
local grp = _grp -- Ops.Intel#INTEL.Contact
targetset:AddGroup(grp.group, true)
end
local managedcontact = {} -- #AWACS.ManagedContact
@ -6519,7 +6587,7 @@ end
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.Intelligence#INTEL.Contact Contact
-- @param Ops.Intel#INTEL.Contact Contact
-- @return #AWACS self
function AWACS:onafterNewContact(From,Event,To,Contact)
self:T({From, Event, To, Contact})
@ -6548,7 +6616,7 @@ end
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.Intelligence#INTEL.Contact Contact
-- @param Ops.Intel#INTEL.Contact Contact
-- @return #AWACS self
function AWACS:onafterLostContact(From,Event,To,Contact)
self:T({From, Event, To, Contact})
@ -6560,7 +6628,7 @@ end
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.Intelligence#INTEL.Cluster Cluster
-- @param Ops.Intel#INTEL.Cluster Cluster
-- @param Ops.Auftrag#AUFTRAG Mission
-- @return #AWACS self
function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission)
@ -6632,7 +6700,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
@ -6761,7 +6829,7 @@ function AWACS:onafterAwacsShiftChange(From,Event,To)
self.AwacsTimeStamp = timer.getTime()
-- set up the AWACS and let it orbit
local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING
local AwacsAW = self.AirWing -- Ops.Airwing#AIRWING
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
self.CatchAllMissions[#self.CatchAllMissions+1] = mission
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600

View File

@ -313,8 +313,8 @@ end
--
-- local Path = FilePath or "C:\\Users\\<yourname>\\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\\<yourname>\\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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -702,7 +702,7 @@ function EASYGCICAP:_SetTankerPatrolPoints()
self:T(self.lid.."_SetTankerPatrolPoints")
for _,_data in pairs(self.ManagedTK) do
local data = _data --#EASYGCICAP.CapPoint
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
@ -721,7 +721,7 @@ function EASYGCICAP:_SetAwacsPatrolPoints()
self:T(self.lid.."_SetAwacsPatrolPoints")
for _,_data in pairs(self.ManagedEWR) do
local data = _data --#EASYGCICAP.CapPoint
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
@ -740,7 +740,7 @@ function EASYGCICAP:_SetCAPPatrolPoints()
self:T(self.lid.."_SetCAPPatrolPoints")
for _,_data in pairs(self.ManagedCP) do
local data = _data --#EASYGCICAP.CapPoint
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
@ -759,7 +759,7 @@ function EASYGCICAP:_SetReconPatrolPoints()
self:T(self.lid.."_SetReconPatrolPoints")
for _,_data in pairs(self.ManagedREC) do
local data = _data --#EASYGCICAP.CapPoint
local Wing = self.wings[data.AirbaseName][1] -- Ops.AirWing#AIRWING
local Wing = self.wings[data.AirbaseName][1] -- Ops.Airwing#AIRWING
local Coordinate = data.Coordinate
local Altitude = data.Altitude
local Speed = data.Speed
@ -947,7 +947,7 @@ function EASYGCICAP:_AddSquadron(TemplateName, SquadName, AirbaseName, AirFrames
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing = self.wings[AirbaseName][1] -- Ops.AirWing#AIRWING
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROLRACETRACK, AUFTRAG.Type.ALERT5},75)
@ -978,7 +978,7 @@ function EASYGCICAP:_AddReconSquadron(TemplateName, SquadName, AirbaseName, AirF
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
local wing = self.wings[AirbaseName][1] -- Ops.AirWing#AIRWING
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.RECON},75)
@ -1014,7 +1014,7 @@ function EASYGCICAP:_AddTankerSquadron(TemplateName, SquadName, AirbaseName, Air
Squadron_One:SetRadio(Frequency,Modulation)
Squadron_One:AddTacanChannel(TACAN,TACAN)
local wing = self.wings[AirbaseName][1] -- Ops.AirWing#AIRWING
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.TANKER},75)
@ -1047,7 +1047,7 @@ function EASYGCICAP:_AddAWACSSquadron(TemplateName, SquadName, AirbaseName, AirF
Squadron_One:SetSkill(Skill or AI.Skill.AVERAGE)
Squadron_One:SetMissionRange(self.missionrange)
Squadron_One:SetRadio(Frequency,Modulation)
local wing = self.wings[AirbaseName][1] -- Ops.AirWing#AIRWING
local wing = self.wings[AirbaseName][1] -- Ops.Airwing#AIRWING
wing:AddSquadron(Squadron_One)
wing:NewPayload(TemplateName,-1,{AUFTRAG.Type.AWACS},75)
@ -1126,7 +1126,7 @@ end
--- Add a zone to the rejected zones set.
-- @param #EASYGCICAP self
-- @param Ops.Intelligence#INTEL.Cluster Cluster
-- @param Ops.Intel#INTEL.Cluster Cluster
-- @return #EASYGCICAP self
function EASYGCICAP:_AssignIntercept(Cluster)
-- Here, we'll decide if we need to launch an intercepting flight, and from where
@ -1151,7 +1151,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local position = self.Intel:CalcClusterFuturePosition(Cluster,300)
-- calculate closest zone
local bestdistance = 2000*1000 -- 2000km
local targetairwing = nil -- Ops.AirWing#AIRWING
local targetairwing = nil -- Ops.Airwing#AIRWING
local targetawname = "" -- #string
local clustersize = self.Intel:ClusterCountUnits(Cluster) or 1
local wingsize = math.abs(overhead * (clustersize+1))
@ -1164,7 +1164,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
if (retrymission) and (wingsize >= 1) then
MESSAGE:New(string.format("**** %s Interceptors need wingsize %d", UTILS.GetCoalitionName(self.coalition), wingsize),15,"CAPGCI"):ToAllIf(self.debug):ToLog()
for _,_data in pairs (wings) do
local airwing = _data[1] -- Ops.AirWing#AIRWING
local airwing = _data[1] -- Ops.Airwing#AIRWING
local zone = _data[2] -- Core.Zone#ZONE
local zonecoord = zone:GetCoordinate()
local name = _data[3] -- #string
@ -1177,7 +1177,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
end
end
for _,_data in pairs (ctlpts) do
--local airwing = _data[1] -- Ops.AirWing#AIRWING
--local airwing = _data[1] -- Ops.Airwing#AIRWING
--local zone = _data[2] -- Core.Zone#ZONE
--local zonecoord = zone:GetCoordinate()
--local name = _data[3] -- #string
@ -1191,7 +1191,7 @@ function EASYGCICAP:_AssignIntercept(Cluster)
local airframes = airwing:CountAssets(true)
if distance < bestdistance and airframes >= wingsize then
bestdistance = distance
targetairwing = airwing -- Ops.AirWing#AIRWING
targetairwing = airwing -- Ops.Airwing#AIRWING
targetawname = name
end
end

View File

@ -44,7 +44,7 @@
-- @field #boolean fuelcritical Fuel critical switch.
-- @field #number fuelcriticalthresh Critical fuel threshold in percent.
-- @field #boolean fuelcriticalrtb RTB on critical fuel switch.
-- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group.
-- @field OPS.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group.
-- @field Ops.Airboss#AIRBOSS airboss The airboss handling this group.
-- @field Core.UserFlag#USERFLAG flaghold Flag for holding.
-- @field #number Tholding Abs. mission time stamp when the group reached the holding point.
@ -54,7 +54,7 @@
-- @field #boolean despawnAfterLanding If `true`, group is despawned after landed at an airbase.
-- @field #boolean despawnAfterHolding If `true`, group is despawned after reaching the holding point.
-- @field #number RTBRecallCount Number that counts RTB calls.
-- @field Ops.FlightControl#FLIGHTCONTROL.HoldingStack stack Holding stack.
-- @field OPS.FlightControl#FLIGHTCONTROL.HoldingStack stack Holding stack.
-- @field #boolean isReadyTO Flight is ready for takeoff. This is for FLIGHTCONTROL.
-- @field #boolean prohibitAB Disallow (true) or allow (false) AI to use the afterburner.
-- @field #boolean jettisonEmptyTanks Allow (true) or disallow (false) AI to jettison empty fuel tanks.
@ -699,7 +699,7 @@ end
--- Get airwing the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return Ops.AirWing#AIRWING The AIRWING object (if any).
-- @return Ops.Airwing#AIRWING The AIRWING object (if any).
function FLIGHTGROUP:GetAirwing()
return self.legion
end
@ -797,7 +797,7 @@ end
--- Set the FLIGHTCONTROL controlling this flight group.
-- @param #FLIGHTGROUP self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object.
-- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object.
-- @return #FLIGHTGROUP self
function FLIGHTGROUP:SetFlightControl(flightcontrol)
@ -826,7 +826,7 @@ end
--- Get the FLIGHTCONTROL controlling this flight group.
-- @param #FLIGHTGROUP self
-- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.
-- @return OPS.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.
function FLIGHTGROUP:GetFlightControl()
return self.flightcontrol
end
@ -2359,10 +2359,10 @@ function FLIGHTGROUP:onafterCruise(From, Event, To)
---
-- CLIENT
---
-- Had this commented out (forgot why, probably because it was not necessary) but re-enabling it because of carrier launch.
self:_UpdateMenu(0.1)
end
end
@ -2892,7 +2892,7 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime)
else
-- Check if not parking (could be on ALERT5 and just spawned (current mission=nil)
if not self:IsParking() then
if not self:IsParking() then
self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions))
self:__Wait(-1)
end
@ -3266,14 +3266,14 @@ function FLIGHTGROUP:_LandAtAirbase(airbase, SpeedTo, SpeedHold, SpeedLand)
local wind = airbase:GetCoordinate():GetWind()
rheading = -wind
end
local papp=airbase:GetCoordinate():Translate(x1, rheading):SetAltitude(h1)
wp[#wp+1]=papp:WaypointAirTurningPoint("BARO", UTILS.KnotsToKmph(SpeedLand), {TaskFinal}, "Final Approach")
-- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach.
local pland=airbase:GetCoordinate():Translate(x2, rheading):SetAltitude(h2)
wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing")
elseif airbase:IsShip() or airbase:IsHelipad() then
---
@ -3882,10 +3882,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.
@ -4956,7 +4957,7 @@ function FLIGHTGROUP:_UpdateMenu(delay)
-- Get all FLIGHTCONTROLS
local fc={}
for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS) do
local flightcontrol=_flightcontrol --Ops.FlightControl#FLIGHTCONTROL
local flightcontrol=_flightcontrol --OPS.FlightControl#FLIGHTCONTROL
-- Get coord of airbase.
local coord=flightcontrol:GetCoordinate()

View File

@ -2272,7 +2272,7 @@ function INTEL:GetHighestThreatContact(Cluster)
for _,_contact in pairs(Cluster.Contacts) do
local contact=_contact --Ops.Intelligence#INTEL.Contact
local contact=_contact --Ops.Intel#INTEL.Contact
if contact.threatlevel>threatlevel then
threatlevel=contact.threatlevel
@ -2312,8 +2312,8 @@ end
-- @field #string alias Alias name for logging.
-- @field #number cachetime Number of seconds to keep an object.
-- @field #number interval Number of seconds between collection runs.
-- @field #table contacts Table of Ops.Intelligence#INTEL.Contact contacts.
-- @field #table clusters Table of Ops.Intelligence#INTEL.Cluster clusters.
-- @field #table contacts Table of Ops.Intel#INTEL.Contact contacts.
-- @field #table clusters Table of Ops.Intel#INTEL.Cluster clusters.
-- @field #table contactcoords Table of contacts' Core.Point#COORDINATE objects.
-- @extends Core.Fsm#FSM
@ -2337,7 +2337,7 @@ INTEL_DLINK.version = "0.0.1"
--- Function to instantiate a new object
-- @param #INTEL_DLINK self
-- @param #table Intels Table of Ops.Intelligence#INTEL objects.
-- @param #table Intels Table of Ops.Intel#INTEL objects.
-- @param #string Alias (optional) Name of this instance. Default "SPECTRE"
-- @param #number Interval (optional) When to query #INTEL objects for detected items (default 20 seconds).
-- @param #number Cachetime (optional) How long to cache detected items (default 300 seconds).
@ -2449,7 +2449,7 @@ end
--- Function to add an #INTEL object to the aggregator
-- @param #INTEL_DLINK self
-- @param Ops.Intelligence#INTEL Intel the #INTEL object to add
-- @param Ops.Intel#INTEL Intel the #INTEL object to add
-- @return #INTEL_DLINK self
function INTEL_DLINK:AddIntel(Intel)
self:T(self.lid .. "AddIntel")

View File

@ -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.

View File

@ -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.

View File

@ -509,7 +509,7 @@ OPSGROUP.CargoStatus={
--- OpsGroup version.
-- @field #string version
OPSGROUP.version="1.0.0"
OPSGROUP.version="1.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -536,7 +536,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
@ -555,10 +555,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()
@ -597,33 +602,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.

View File

@ -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
@ -3946,7 +3955,7 @@ function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
local function NewCluster(Cluster)
if not self.usecluster then return self end
local cluster = Cluster -- Ops.Intelligence#INTEL.Cluster
local cluster = Cluster -- Ops.Intel#INTEL.Cluster
local type = cluster.ctype
self:T({type,self.Type})
if (type == INTEL.Ctype.AIRCRAFT and self.Type == PLAYERTASKCONTROLLER.Type.A2A) or (type == INTEL.Ctype.NAVAL and (self.Type == PLAYERTASKCONTROLLER.Type.A2S or self.Type == PLAYERTASKCONTROLLER.Type.A2GS)) then
@ -3954,7 +3963,7 @@ function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
local contacts = cluster.Contacts -- #table of GROUP
local targetset = SET_GROUP:New()
for _,_object in pairs(contacts) do
local contact = _object -- Ops.Intelligence#INTEL.Contact
local contact = _object -- Ops.Intel#INTEL.Contact
self:T("Adding group: "..contact.groupname)
targetset:AddGroup(contact.group,true)
end
@ -3966,14 +3975,14 @@ function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
if type == INTEL.Ctype.GROUND then
targetset = SET_GROUP:New()
for _,_object in pairs(contacts) do
local contact = _object -- Ops.Intelligence#INTEL.Contact
local contact = _object -- Ops.Intel#INTEL.Contact
self:T("Adding group: "..contact.groupname)
targetset:AddGroup(contact.group,true)
end
elseif type == INTEL.Ctype.STRUCTURE then
targetset = SET_STATIC:New()
for _,_object in pairs(contacts) do
local contact = _object -- Ops.Intelligence#INTEL.Contact
local contact = _object -- Ops.Intel#INTEL.Contact
self:T("Adding static: "..contact.groupname)
targetset:AddStatic(contact.group)
end
@ -3986,7 +3995,7 @@ function PLAYERTASKCONTROLLER:SetupIntel(RecceName)
local function NewContact(Contact)
if self.usecluster then return self end
local contact = Contact -- Ops.Intelligence#INTEL.Contact
local contact = Contact -- Ops.Intel#INTEL.Contact
local type = contact.ctype
self:T({type,self.Type})
if (type == INTEL.Ctype.AIRCRAFT and self.Type == PLAYERTASKCONTROLLER.Type.A2A) or (type == INTEL.Ctype.NAVAL and (self.Type == PLAYERTASKCONTROLLER.Type.A2S or self.Type == PLAYERTASKCONTROLLER.Type.A2GS)) then

View File

@ -243,7 +243,7 @@ end
--- Set airwing.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING Airwing The airwing.
-- @param Ops.Airwing#AIRWING Airwing The airwing.
-- @return #SQUADRON self
function SQUADRON:SetAirwing(Airwing)
self.legion=Airwing
@ -252,7 +252,7 @@ end
--- Get airwing.
-- @param #SQUADRON self
-- @return Ops.AirWing#AIRWING The airwing.
-- @return Ops.Airwing#AIRWING The airwing.
function SQUADRON:GetAirwing(Airwing)
return self.legion
end

View File

@ -36,7 +36,7 @@
-- @field #number prio Priority.
-- @field #number importance Importance.
-- @field Ops.Auftrag#AUFTRAG mission Mission attached to this target.
-- @field Ops.Intelligence#INTEL.Contact contact Contact attached to this target.
-- @field Ops.Intel#INTEL.Contact contact Contact attached to this target.
-- @field #boolean isDestroyed If true, target objects were destroyed.
-- @field #table resources Resource list.
-- @field #table conditionStart Start condition functions.

View File

@ -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

View File

@ -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).
--
-- ===
--
@ -824,7 +824,7 @@ function MSRS:SetVoiceProvider(Voice, Provider)
self:F( {Voice=Voice, Provider=Provider} )
self.poptions=self.poptions or {}
self.poptions[Provider or self:GetProvider()]=Voice
self.poptions[Provider or self:GetProvider()].voice=Voice
return self
end

View File

@ -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
@ -852,6 +860,64 @@ UTILS.tostringLL = function( lat, lon, acc, DMS)
end
end
--[[acc:
in DM: decimal point of minutes.
In DMS: decimal point of seconds.
position after the decimal of the least significant digit:
So:
42.32 - acc of 2.
]]
UTILS.tostringLLM2KData = function( lat, lon, acc)
local latHemi, lonHemi
if lat > 0 then
latHemi = 'N'
else
latHemi = 'S'
end
if lon > 0 then
lonHemi = 'E'
else
lonHemi = 'W'
end
lat = math.abs(lat)
lon = math.abs(lon)
local latDeg = math.floor(lat)
local latMin = (lat - latDeg)*60
local lonDeg = math.floor(lon)
local lonMin = (lon - lonDeg)*60
-- degrees, decimal minutes.
latMin = UTILS.Round(latMin, acc)
lonMin = UTILS.Round(lonMin, acc)
if latMin == 60 then
latMin = 0
latDeg = latDeg + 1
end
if lonMin == 60 then
lonMin = 0
lonDeg = lonDeg + 1
end
local minFrmtStr -- create the formatting string for the minutes place
if acc <= 0 then -- no decimal place.
minFrmtStr = '%02d'
else
local width = 3 + acc -- 01.310 - that's a width of 6, for example.
minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end
-- 024 23'N or 024 23.123'N
return latHemi..string.format('%02d:', latDeg) .. string.format(minFrmtStr, latMin), lonHemi..string.format('%02d:', lonDeg) .. string.format(minFrmtStr, lonMin)
end
-- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
@ -2246,6 +2312,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
@ -3790,3 +3861,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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

263
docs/advanced/concepts.md Normal file
View File

@ -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/

View File

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

View File

@ -1,7 +1,7 @@
---
title: De-Sanitize DCS
parent: Advanced
nav_order: 2
nav_order: 98
---
# De-Sanitize the DCS scripting environment
{: .no_toc }

View File

@ -1,6 +1,6 @@
---
parent: Advanced
nav_order: 1
nav_order: 97
---
# Eclipse Installation
{: .no_toc }

View File

@ -1,6 +1,6 @@
---
parent: Advanced
nav_order: 2
nav_order: 99
---
# Text to Speech
{: .no_toc }

View File

@ -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.
- Dont say it doesnt 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. <br />

View File

@ -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.

View File

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB