Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2024-03-19 18:04:03 +01:00
commit 1253e241ff
44 changed files with 3370 additions and 2349 deletions

View File

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

View File

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

View File

@ -8,6 +8,10 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon)
--
-- ===
--
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Core.Beacon -- @module Core.Beacon
@ -286,6 +290,7 @@ end
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon -- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration) function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self:F({TACANChannel, Message, Bearing, BeaconDuration}) self:F({TACANChannel, Message, Bearing, BeaconDuration})
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
local IsValid = true local IsValid = true

View File

@ -37,6 +37,8 @@
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
-- @field #table CLIENTS Clients. -- @field #table CLIENTS Clients.
-- @field #table STORAGES DCS warehouse storages. -- @field #table STORAGES DCS warehouse storages.
-- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes.
-- @field #table SADL Used Link16 octal numbers for A10/C-II planes.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
@ -93,6 +95,8 @@ DATABASE = {
OPSZONES = {}, OPSZONES = {},
PATHLINES = {}, PATHLINES = {},
STORAGES = {}, STORAGES = {},
STNS={},
SADL={},
} }
local _DATABASECoalition = local _DATABASECoalition =
@ -928,7 +932,7 @@ function DATABASE:Spawn( SpawnTemplate )
SpawnTemplate.CountryID = nil SpawnTemplate.CountryID = nil
SpawnTemplate.CategoryID = nil SpawnTemplate.CategoryID = nil
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID ) self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name )
self:T3( SpawnTemplate ) self:T3( SpawnTemplate )
coalition.addGroup( SpawnCountryID, SpawnCategoryID, 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.ClientsByName[UnitTemplate.name].CountryID = CountryID
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end end
if UnitTemplate.AddPropAircraft then
if UnitTemplate.AddPropAircraft.STN_L16 then
local stn = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16)
if stn == nil or stn < 1 then
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
else
self.STNS[stn] = UnitTemplate.name
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
end
end
if UnitTemplate.AddPropAircraft.SADL_TN then
local sadl = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN)
if sadl == nil or sadl < 1 then
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
else
self.SADL[sadl] = UnitTemplate.name
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
end
end
end
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
end end
-- Debug info. -- Debug info.
self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
@ -1043,6 +1068,80 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
) )
end end
--- Get next (consecutive) free STN as octal number.
-- @param #DATABASE self
-- @param #number octal Starting octal.
-- @param #string unitname Name of the associated unit.
-- @return #number Octal
function DATABASE:GetNextSTN(octal,unitname)
local first = UTILS.OctalToDecimal(octal)
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. --- Get group template.
-- @param #DATABASE self -- @param #DATABASE self
-- @param #string GroupName Group name. -- @param #string GroupName Group name.

View File

@ -1378,6 +1378,7 @@ function EVENT:onEvent( Event )
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos) Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
Event.MarkText=Event.text Event.MarkText=Event.text
Event.MarkCoalition=Event.coalition Event.MarkCoalition=Event.coalition
Event.IniCoalition=Event.coalition
Event.MarkGroupID = Event.groupID Event.MarkGroupID = Event.groupID
end end

View File

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

View File

@ -1097,6 +1097,7 @@ do
GroupPrefixes = nil, GroupPrefixes = nil,
Zones = nil, Zones = nil,
Functions = nil, Functions = nil,
Alive = nil,
}, },
FilterMeta = { FilterMeta = {
Coalitions = { Coalitions = {
@ -1470,7 +1471,7 @@ do
end 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. -- Only the groups that are active will be included within the set.
-- @param #SET_GROUP self -- @param #SET_GROUP self
-- @param #boolean Active (Optional) Include only active groups to the set. -- @param #boolean Active (Optional) Include only active groups to the set.
@ -1495,6 +1496,14 @@ do
self.Filter.Active = Active self.Filter.Active = Active
return self return self
end end
--- Build a set of groups that are alive.
-- @param #SET_GROUP self
-- @return #SET_GROUP self
function SET_GROUP:FilterAlive()
self.Filter.Alive = true
return self
end
--- Starts the filtering. --- Starts the filtering.
-- @param #SET_GROUP self -- @param #SET_GROUP self
@ -1993,7 +2002,16 @@ do
function SET_GROUP:IsIncludeObject( MGroup ) function SET_GROUP:IsIncludeObject( MGroup )
self:F2( MGroup ) self:F2( MGroup )
local MGroupInclude = true local MGroupInclude = true
if self.Filter.Alive == true then
local MGroupAlive = false
self:F( { Active = self.Filter.Active } )
if MGroup and MGroup:IsAlive() then
MGroupAlive = true
end
MGroupInclude = MGroupInclude and MGroupAlive
end
if self.Filter.Active ~= nil then if self.Filter.Active ~= nil then
local MGroupActive = false local MGroupActive = false
self:F( { Active = self.Filter.Active } ) self:F( { Active = self.Filter.Active } )
@ -2997,7 +3015,7 @@ do -- SET_UNIT
local velocity = self:GetVelocity() or 0 local velocity = self:GetVelocity() or 0
Coordinate:SetHeading( heading ) Coordinate:SetHeading( heading )
Coordinate:SetVelocity( velocity ) Coordinate:SetVelocity( velocity )
self:I(UTILS.PrintTableToLog(Coordinate)) self:T(UTILS.PrintTableToLog(Coordinate))
end end
return Coordinate return Coordinate
@ -4521,7 +4539,7 @@ do -- SET_CLIENT
if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then
-- CA Slot entered -- CA Slot entered
local ObjectName, Object = self:AddInDatabase( Event ) local ObjectName, Object = self:AddInDatabase( Event )
self:I( ObjectName, UTILS.PrintTableToLog(Object) ) self:T( ObjectName, UTILS.PrintTableToLog(Object) )
if Object and self:IsIncludeObject( Object ) then if Object and self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object ) self:Add( ObjectName, Object )
end end

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.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp. -- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
--
-- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only)
--
-- *{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
-- *{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
--
-- ### Callsigns
--
-- *{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
-- *{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
--
-- ### Speed
--
-- *{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
-- *{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
-- *{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
-- --
-- ## SPAWN **Spawn** methods -- ## SPAWN **Spawn** methods
-- --
@ -520,7 +536,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
end end
if SpawnTemplate then 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.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix
self.SpawnTemplate.name = SpawnTemplatePrefix self.SpawnTemplate.name = SpawnTemplatePrefix
@ -724,7 +740,7 @@ end
-- @param #number Country Country id as number or enumerator: -- @param #number Country Country id as number or enumerator:
-- --
-- * @{DCS#country.id.RUSSIA} -- * @{DCS#country.id.RUSSIA}
-- * @{DCS#county.id.USA} -- * @{DCS#country.id.USA}
-- --
-- @return #SPAWN self -- @return #SPAWN self
function SPAWN:InitCountry( Country ) function SPAWN:InitCountry( Country )
@ -780,6 +796,82 @@ function SPAWN:InitSkill( Skill )
return self return self
end end
--- [Airplane - F15/16/18/AWACS/B1B/Tanker only] Set the STN Link16 starting number of the Group; each unit of the spawned group will have a consecutive STN set.
-- @param #SPAWN self
-- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique!
-- @return #SPAWN self
function SPAWN:InitSTN(Octal)
self:F( { Octal = Octal } )
self.SpawnInitSTN = Octal or 77777
local num = UTILS.OctalToDecimal(Octal)
if num == nil or num < 1 then
self:E("WARNING - STN "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.STNS[num] ~= nil then
self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal])
end
return self
end
--- [Airplane - A10-C II only] Set the SADL TN starting number of the Group; each unit of the spawned group will have a consecutive SADL set.
-- @param #SPAWN self
-- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique!
-- @return #SPAWN self
function SPAWN:InitSADL(Octal)
self:F( { Octal = Octal } )
self.SpawnInitSADL = Octal or 7777
local num = UTILS.OctalToDecimal(Octal)
if num == nil or num < 1 then
self:E("WARNING - SADL "..tostring(Octal).." is not valid!")
return self
end
if _DATABASE.SADL[num] ~= nil then
self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal])
end
return self
end
--- [Airplane] Set the initial speed on spawning in meters per second. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number MPS The speed in MPS to use.
-- @return #SPAWN self
function SPAWN:InitSpeedMps(MPS)
self:F( { MPS = MPS } )
if MPS == nil or tonumber(MPS)<0 then
MPS=125
end
self.InitSpeed = MPS
return self
end
--- [Airplane] Set the initial speed on spawning in knots. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number Knots The speed in knots to use.
-- @return #SPAWN self
function SPAWN:InitSpeedKnots(Knots)
self:F( { Knots = Knots } )
if Knots == nil or tonumber(Knots)<0 then
Knots=300
end
self.InitSpeed = UTILS.KnotsToMps(Knots)
return self
end
--- [Airplane] Set the initial speed on spawning in kilometers per hour. Useful when spawning in-air only.
-- @param #SPAWN self
-- @param #number KPH The speed in KPH to use.
-- @return #SPAWN self
function SPAWN:InitSpeedKph(KPH)
self:F( { KPH = KPH } )
if KPH == nil or tonumber(KPH)<0 then
KPH=UTILS.KnotsToKmph(300)
end
self.InitSpeed = UTILS.KmphToMps(KPH)
return self
end
--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor. --- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor.
-- @param #SPAWN self -- @param #SPAWN self
-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group. -- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group.
@ -1699,6 +1791,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
end end
self.SpawnGroups[self.SpawnIndex].Spawned = true self.SpawnGroups[self.SpawnIndex].Spawned = true
self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor = self.SpawnTemplatePrefix
return self.SpawnGroups[self.SpawnIndex].Group return self.SpawnGroups[self.SpawnIndex].Group
else else
-- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } ) -- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
@ -3303,7 +3396,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
end end
end end
end end
if self.SpawnInitKeepUnitNames == false then if self.SpawnInitKeepUnitNames == false then
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
@ -3311,9 +3404,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
end end
else else
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" ) local SpawnInitKeepUnitIFF = false
self:T( { UnitPrefix, Rest } ) 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].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
SpawnTemplate.units[UnitID].unitId = nil SpawnTemplate.units[UnitID].unitId = nil
end end
@ -3395,34 +3496,56 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex
end end
end end
-- Speed
if self.InitSpeed then
SpawnTemplate.units[UnitID].speed = self.InitSpeed
end
-- Link16 -- Link16
local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft
if AddProps then if AddProps then
if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
-- 4 digit octal with leading 0 if self.SpawnInitSTN then
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then local octal = self.SpawnInitSTN
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 if UnitID > 1 then
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1 octal = _DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name)
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",UTILS.DecimalToOctal(decimal)) end
else -- ED bug - chars in here SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",octal)
local STN = math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088)) else
STN = STN+UnitID-1 -- 5 digit octal with leading 0
local OSTN = UTILS.DecimalToOctal(STN) if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN) 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
end end
-- A10CII -- A10CII
if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
-- 3 digit octal with leading 0 -- 4 digit octal with leading 0
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then if self.SpawnInitSADL then
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN local octal = self.SpawnInitSADL
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1 if UnitID > 1 then
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",UTILS.DecimalToOctal(decimal)) octal = _DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
else -- ED bug - chars in here end
local STN = math.floor(UTILS.RandomGaussian(504/2,nil,100,504)) SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",octal)
STN = STN+UnitID-1 else
local OSTN = UTILS.DecimalToOctal(STN) if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN) 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
end end
-- VoiceCallsignNumber -- VoiceCallsignNumber

View File

@ -1,36 +1,36 @@
--- **Core** - Spawn statics. --- **Core** - Spawn statics.
-- --
-- === -- ===
-- --
-- ## Features: -- ## Features:
-- --
-- * Spawn new statics from a static already defined in the mission editor. -- * 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 template.
-- * Spawn new statics from a given type. -- * Spawn new statics from a given type.
-- * Spawn with a custom heading and location. -- * Spawn with a custom heading and location.
-- * Spawn within a zone. -- * Spawn within a zone.
-- * Spawn statics linked to units, .e.g on aircraft carriers. -- * 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 -- # YouTube Channel
-- --
-- ## No videos yet! -- ## No videos yet!
-- --
-- === -- ===
-- --
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky** -- ### Contributions: **funkyfranky**
-- --
-- === -- ===
-- --
-- @module Core.SpawnStatic -- @module Core.SpawnStatic
-- @image Core_Spawnstatic.JPG -- @image Core_Spawnstatic.JPG
@ -58,37 +58,37 @@
--- Allows to spawn dynamically new @{Wrapper.Static}s into your mission. --- 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. -- 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: -- 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**. -- * 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 -- # 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. -- 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 -- ## 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 -- 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 the static.
-- --
-- ## From a Template -- ## 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. -- 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 -- ## 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 -- 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. -- are used. For example, if no spawn coordinate is given, the static will be created at the origin of the map.
-- --
-- # Setting Parameters -- # 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! -- 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.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.InitHeading}(Heading) sets the orientation of the static.
-- * @{#SPAWNSTATIC.InitLivery}(LiveryName) sets the livery of the static. Not all statics support this. -- * @{#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. -- * @{#SPAWNSTATIC.InitLinkToUnit}(Unit, OffsetX, OffsetY, OffsetAngle) links the static to a unit, e.g. to an aircraft carrier.
-- --
-- # Spawning the Statics -- # 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 -- 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. -- 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.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.SpawnFromCoordinate}(Coordinate, Heading, NewName) spawn the static at the given coordinate. Optionally, heading and name can be given. The name **must be unique**!
-- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a POINT_VEC2 coordinate. Optionally, heading and name can be given. The name **must be unique**! -- * @{#SPAWNSTATIC.SpawnFromPointVec2}(PointVec2, Heading, NewName) spawns the static at a 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**! -- * @{#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 -- @field #SPAWNSTATIC SPAWNSTATIC
-- --
SPAWNSTATIC = { SPAWNSTATIC = {
ClassName = "SPAWNSTATIC", ClassName = "SPAWNSTATIC",
SpawnIndex = 0, SpawnIndex = 0,
@ -139,9 +139,9 @@ SPAWNSTATIC = {
function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID) function SPAWNSTATIC:NewFromStatic(SpawnTemplateName, SpawnCountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName) local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate(SpawnTemplateName)
if TemplateStatic then if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplateName self.SpawnTemplatePrefix = SpawnTemplateName
self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1]) self.TemplateStaticUnit = UTILS.DeepCopy(TemplateStatic.units[1])
@ -166,11 +166,11 @@ end
function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID) function SPAWNSTATIC:NewFromTemplate(SpawnTemplate, CountryID)
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate) self.TemplateStaticUnit = UTILS.DeepCopy(SpawnTemplate)
self.SpawnTemplatePrefix = SpawnTemplate.name self.SpawnTemplatePrefix = SpawnTemplate.name
self.CountryID = CountryID or country.id.USA self.CountryID = CountryID or country.id.USA
return self return self
end end
@ -189,7 +189,7 @@ function SPAWNSTATIC:NewFromType(StaticType, StaticCategory, CountryID)
self.InitStaticCategory=StaticCategory self.InitStaticCategory=StaticCategory
self.CountryID=CountryID or country.id.USA self.CountryID=CountryID or country.id.USA
self.SpawnTemplatePrefix=self.InitStaticType self.SpawnTemplatePrefix=self.InitStaticType
self.InitStaticCoordinate=COORDINATE:New(0, 0, 0) self.InitStaticCoordinate=COORDINATE:New(0, 0, 0)
self.InitStaticHeading=0 self.InitStaticHeading=0
@ -291,7 +291,7 @@ function SPAWNSTATIC:InitCountry(CountryID)
return self return self
end 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 #SPAWNSTATIC self
-- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name. -- @param #string NamePrefix Name prefix of statics spawned. Will append #0001, etc to the name.
-- @return #SPAWNSTATIC self -- @return #SPAWNSTATIC self
@ -327,13 +327,13 @@ function SPAWNSTATIC:Spawn(Heading, NewName)
if Heading then if Heading then
self.InitStaticHeading=Heading self.InitStaticHeading=Heading
end end
if NewName then if NewName then
self.InitStaticName=NewName self.InitStaticName=NewName
end end
return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID) return self:_SpawnStatic(self.TemplateStaticUnit, self.CountryID)
end end
--- Creates a new @{Wrapper.Static} from a POINT_VEC2. --- 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 vec2={x=PointVec2:GetX(), y=PointVec2:GetY()}
local Coordinate=COORDINATE:NewFromVec2(vec2) local Coordinate=COORDINATE:NewFromVec2(vec2)
return self:SpawnFromCoordinate(Coordinate, Heading, NewName) return self:SpawnFromCoordinate(Coordinate, Heading, NewName)
end end
@ -362,11 +362,11 @@ function SPAWNSTATIC:SpawnFromCoordinate(Coordinate, Heading, NewName)
-- Set up coordinate. -- Set up coordinate.
self.InitStaticCoordinate=Coordinate self.InitStaticCoordinate=Coordinate
if Heading then if Heading then
self.InitStaticHeading=Heading self.InitStaticHeading=Heading
end end
if NewName then if NewName then
self.InitStaticName=NewName self.InitStaticName=NewName
end end
@ -385,7 +385,7 @@ function SPAWNSTATIC:SpawnFromZone(Zone, Heading, NewName)
-- Spawn the new static at the center of the zone. -- Spawn the new static at the center of the zone.
local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName ) local Static = self:SpawnFromPointVec2( Zone:GetPointVec2(), Heading, NewName )
return Static return Static
end end
@ -399,45 +399,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template=Template or {} Template=Template or {}
local CountryID=CountryID or self.CountryID local CountryID=CountryID or self.CountryID
if self.InitStaticType then if self.InitStaticType then
Template.type=self.InitStaticType Template.type=self.InitStaticType
end end
if self.InitStaticCategory then if self.InitStaticCategory then
Template.category=self.InitStaticCategory Template.category=self.InitStaticCategory
end end
if self.InitStaticCoordinate then if self.InitStaticCoordinate then
Template.x = self.InitStaticCoordinate.x Template.x = self.InitStaticCoordinate.x
Template.y = self.InitStaticCoordinate.z Template.y = self.InitStaticCoordinate.z
Template.alt = self.InitStaticCoordinate.y Template.alt = self.InitStaticCoordinate.y
end end
if self.InitStaticHeading then if self.InitStaticHeading then
Template.heading = math.rad(self.InitStaticHeading) Template.heading = math.rad(self.InitStaticHeading)
end end
if self.InitStaticShape then if self.InitStaticShape then
Template.shape_name=self.InitStaticShape Template.shape_name=self.InitStaticShape
end end
if self.InitStaticLivery then if self.InitStaticLivery then
Template.livery_id=self.InitStaticLivery Template.livery_id=self.InitStaticLivery
end end
if self.InitStaticDead~=nil then if self.InitStaticDead~=nil then
Template.dead=self.InitStaticDead Template.dead=self.InitStaticDead
end end
if self.InitStaticCargo~=nil then if self.InitStaticCargo~=nil then
Template.canCargo=self.InitStaticCargo Template.canCargo=self.InitStaticCargo
end end
if self.InitStaticCargoMass~=nil then if self.InitStaticCargoMass~=nil then
Template.mass=self.InitStaticCargoMass Template.mass=self.InitStaticCargoMass
end end
if self.InitLinkUnit then if self.InitLinkUnit then
Template.linkUnit=self.InitLinkUnit:GetID() Template.linkUnit=self.InitLinkUnit:GetID()
Template.linkOffset=true Template.linkOffset=true
@ -446,45 +446,45 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.offsets.x=self.InitOffsetX Template.offsets.x=self.InitOffsetX
Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0 Template.offsets.angle=self.InitOffsetAngle and math.rad(self.InitOffsetAngle) or 0
end end
if self.InitFarp then if self.InitFarp then
Template.heliport_callsign_id = self.InitFarpCallsignID Template.heliport_callsign_id = self.InitFarpCallsignID
Template.heliport_frequency = self.InitFarpFreq Template.heliport_frequency = self.InitFarpFreq
Template.heliport_modulation = self.InitFarpModu Template.heliport_modulation = self.InitFarpModu
Template.unitId=nil Template.unitId=nil
end end
-- Increase spawn index counter. -- Increase spawn index counter.
self.SpawnIndex = self.SpawnIndex + 1 self.SpawnIndex = self.SpawnIndex + 1
-- Name of the spawned static. -- Name of the spawned static.
Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex) Template.name = self.InitStaticName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex)
-- Add and register the new static. -- Add and register the new static.
local mystatic=_DATABASE:AddStatic(Template.name) local mystatic=_DATABASE:AddStatic(Template.name)
-- Debug output. -- Debug output.
self:T(Template) self:T(Template)
-- Add static to the game. -- Add static to the game.
local Static=nil --DCS#StaticObject local Static=nil --DCS#StaticObject
if self.InitFarp then if self.InitFarp then
local TemplateGroup={} local TemplateGroup={}
TemplateGroup.units={} TemplateGroup.units={}
TemplateGroup.units[1]=Template TemplateGroup.units[1]=Template
TemplateGroup.visible=true TemplateGroup.visible=true
TemplateGroup.hidden=false TemplateGroup.hidden=false
TemplateGroup.x=Template.x TemplateGroup.x=Template.x
TemplateGroup.y=Template.y TemplateGroup.y=Template.y
TemplateGroup.name=Template.name TemplateGroup.name=Template.name
self:T("Spawning FARP") self:T("Spawning FARP")
self:T({Template=Template}) self:T({Template=Template})
self:T({TemplateGroup=TemplateGroup}) self:T({TemplateGroup=TemplateGroup})
-- ED's dirty way to spawn FARPS. -- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup) Static=coalition.addGroup(CountryID, -1, TemplateGroup)
@ -499,10 +499,10 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
world.onEvent(Event) world.onEvent(Event)
else else
self:T("Spawning Static") self:T("Spawning Static")
self:T2({Template=Template}) self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID, Template) Static=coalition.addStaticObject(CountryID, Template)
end end
return mystatic return mystatic
end end

View File

@ -46,6 +46,10 @@
-- --
-- === -- ===
-- --
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Zone)
--
-- ===
--
-- ### Author: **FlightControl** -- ### Author: **FlightControl**
-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit** -- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit**
-- --
@ -326,14 +330,14 @@ function ZONE_BASE:GetRandomVec2()
return nil return nil
end 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 -- @param #ZONE_BASE self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_BASE:GetRandomPointVec2() function ZONE_BASE:GetRandomPointVec2()
return nil return nil
end end
--- Define a random @{Core.Point#POINT_VEC3} within the zone. --- 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 -- @param #ZONE_BASE self
-- @return Core.Point#POINT_VEC3 The PointVec3 coordinates. -- @return Core.Point#POINT_VEC3 The PointVec3 coordinates.
function ZONE_BASE:GetRandomPointVec3() function ZONE_BASE:GetRandomPointVec3()
@ -899,7 +903,8 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
local Point = {} local Point = {}
local Vec2 = self:GetVec2() local Vec2 = self:GetVec2()
local countryID = CountryID or country.id.USA
Points = Points and Points or 360 Points = Points and Points or 360
local Angle local Angle
@ -910,7 +915,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
local CountryName = _DATABASE.COUNTRY_NAME[CountryID] local CountryName = _DATABASE.COUNTRY_NAME[countryID]
local Tire = { local Tire = {
["country"] = CountryName, ["country"] = CountryName,
@ -925,7 +930,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
["heading"] = 0, ["heading"] = 0,
} -- end of ["group"] } -- end of ["group"]
local Group = coalition.addStaticObject( CountryID, Tire ) local Group = coalition.addStaticObject( countryID, Tire )
if UnBound and UnBound == true then if UnBound and UnBound == true then
Group:destroy() Group:destroy()
end end
@ -1175,7 +1180,7 @@ function ZONE_RADIUS:RemoveJunk()
return n return n
end end
--- Count the number of different coalitions inside the zone. --- Get a table of scanned units.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @return #table Table of DCS units and DCS statics inside the zone. -- @return #table Table of DCS units and DCS statics inside the zone.
function ZONE_RADIUS:GetScannedUnits() function ZONE_RADIUS:GetScannedUnits()
@ -1210,7 +1215,7 @@ function ZONE_RADIUS:GetScannedSetUnit()
return SetUnit return SetUnit
end end
--- Get a set of scanned units. --- Get a set of scanned groups.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @return Core.Set#SET_GROUP Set of groups. -- @return Core.Set#SET_GROUP Set of groups.
function ZONE_RADIUS:GetScannedSetGroup() function ZONE_RADIUS:GetScannedSetGroup()
@ -1510,7 +1515,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
return point return point
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- 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 #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@ -1541,7 +1546,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer )
end 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 #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@ -1985,7 +1990,7 @@ function ZONE_GROUP:GetRandomVec2()
return Point return Point
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- 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 #ZONE_GROUP self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
@ -2829,7 +2834,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2()
end end
end end
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. --- 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 -- @param #ZONE_POLYGON_BASE self
-- @return @{Core.Point#POINT_VEC2} -- @return @{Core.Point#POINT_VEC2}
function ZONE_POLYGON_BASE:GetRandomPointVec2() function ZONE_POLYGON_BASE:GetRandomPointVec2()
@ -2842,7 +2847,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2()
return PointVec2 return PointVec2
end end
--- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. --- 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 -- @param #ZONE_POLYGON_BASE self
-- @return @{Core.Point#POINT_VEC3} -- @return @{Core.Point#POINT_VEC3}
function ZONE_POLYGON_BASE:GetRandomPointVec3() function ZONE_POLYGON_BASE:GetRandomPointVec3()
@ -3830,18 +3835,18 @@ function ZONE_OVAL:GetRandomVec2()
return {x=rx, y=ry} return {x=rx, y=ry}
end end
--- Define a random @{Core.Point#POINT_VEC2} within the zone. --- 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 -- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec2() function ZONE_OVAL:GetRandomPointVec2()
return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
end end
--- Define a random @{Core.Point#POINT_VEC2} within the zone. --- 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 -- @param #ZONE_OVAL self
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. -- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
function ZONE_OVAL:GetRandomPointVec3() function ZONE_OVAL:GetRandomPointVec3()
return POINT_VEC2:NewFromVec3(self:GetRandomVec2()) return POINT_VEC3:NewFromVec3(self:GetRandomVec2())
end end
--- Draw the zone on the F10 map. --- Draw the zone on the F10 map.
@ -3981,7 +3986,7 @@ do -- ZONE_AIRBASE
return ZoneVec2 return ZoneVec2
end end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. --- 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 #ZONE_AIRBASE self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone. -- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,8 @@
-- ### Authors: **FlightControl** -- ### Authors: **FlightControl**
-- --
-- ### Contributions: -- ### Contributions:
-- --
-- * **Applevangelist**: Additional functionality, fixes.
-- * **Wingthor (TAW)**: Testing & Advice. -- * **Wingthor (TAW)**: Testing & Advice.
-- * **Dutch-Baron (TAW)**: Testing & Advice. -- * **Dutch-Baron (TAW)**: Testing & Advice.
-- * **Whisper**: Testing and Advice. -- * **Whisper**: Testing and Advice.
@ -116,11 +117,13 @@
-- Special targets can be set that will give extra scores to the players when these are destroyed. -- Special targets can be set that will give extra scores to the players when these are destroyed.
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s. -- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s.
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s. -- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s.
-- Use the method @{#SCORING.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" ) -- local Scoring = SCORING:New( "Scoring File" )
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 ) -- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
-- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 ) -- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 )
-- local GroupSet = SET_GROUP:New():FilterPrefixes("RAT"):FilterStart()
-- Scoring:AddScoreSetGroup( GroupSet, 100)
-- --
-- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed. -- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed.
-- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over. -- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over.
@ -226,7 +229,7 @@ SCORING = {
ClassID = 0, ClassID = 0,
Players = {}, Players = {},
AutoSave = true, AutoSave = true,
version = "1.17.1" version = "1.18.2"
} }
local _SCORINGCoalition = { local _SCORINGCoalition = {
@ -428,6 +431,31 @@ function SCORING:AddScoreGroup( ScoreGroup, Score )
return self return self
end end
--- Specify a special additional score for a @{Core.Set#SET_GROUP}.
-- @param #SCORING self
-- @param Core.Set#SET_GROUP Set The @{Core.Set#SET_GROUP} for which each @{Wrapper.Unit} in each Group a Score is given.
-- @param #number Score The Score value.
-- @return #SCORING
function SCORING:AddScoreSetGroup(Set, Score)
local set = Set:GetSetObjects()
for _,_group in pairs (set) do
if _group and _group:IsAlive() then
self:AddScoreGroup(_group,Score)
end
end
local function AddScore(group)
self:AddScoreGroup(group,Score)
end
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
AddScore(Object)
end
return self
end
--- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone. --- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone.
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced! -- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
-- This allows for a dynamic destruction zone evolution within your mission. -- This allows for a dynamic destruction zone evolution within your mission.
@ -1030,7 +1058,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the 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 if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
@ -1141,7 +1169,7 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the 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 if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value -- if this fails for some reason, set a good default value
@ -1288,17 +1316,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1 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 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 .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(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 .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@ -1326,14 +1354,14 @@ function SCORING:_EventOnDeadOrCrash( Event )
else else
Player.PlayerKills = 1 Player.PlayerKills = 1
end 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 .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else else
self:OnKillPvE(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 .. " ) " .. MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information ) MESSAGE.Type.Information )
@ -1935,23 +1963,23 @@ end
--- Handles the event when one player kill another player --- Handles the event when one player kill another player
-- @param #SCORING self -- @param #SCORING self
-- @param #PLAYER Player the ataching player -- @param #string PlayerName The attacking player
-- @param #string TargetPlayerName the name of the killed player -- @param #string TargetPlayerName The name of the killed player
-- @param #bool IsTeamKill true if this kill was a team kill -- @param #boolean IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Thread level of the target -- @param #number TargetThreatLevel Threat level of the target
-- @param #number PlayerThreatLevelThread level of the player -- @param #number PlayerThreatLevel Threat level of the player
-- @param #number Score The score based on both threat levels -- @param #number Score The score based on both threat levels
function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score) function SCORING:OnKillPvP(PlayerName, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
end end
--- Handles the event when one player kill another player --- Handles the event when one player kill another player
-- @param #SCORING self -- @param #SCORING self
-- @param #PLAYER Player the ataching player -- @param #string PlayerName The attacking player
-- @param #string TargetUnitName the name of the killed unit -- @param #string TargetUnitName the name of the killed unit
-- @param #bool IsTeamKill true if this kill was a team kill -- @param #boolean IsTeamKill true if this kill was a team kill
-- @param #number TargetThreatLevel Thread level of the target -- @param #number TargetThreatLevel Threat level of the target
-- @param #number PlayerThreatLevelThread level of the player -- @param #number PlayerThreatLevel Threat level of the player
-- @param #number Score The score based on both threat levels -- @param #number Score The score based on both threat levels
function SCORING:OnKillPvE(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 end
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce() local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtcoord = targetzone:GetRandomPointVec2()
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtgrp = seadset:GetRandom() local tgtgrp = seadset:GetRandom()
local _targetgroup = nil local _targetgroup = nil
local _targetgroupname = "none" local _targetgroupname = "none"

View File

@ -41,6 +41,7 @@
-- @field #boolean usebudget -- @field #boolean usebudget
-- @field #number CaptureUnits -- @field #number CaptureUnits
-- @field #number CaptureThreatlevel -- @field #number CaptureThreatlevel
-- @field #boolean ExcludeShips
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
@ -176,7 +177,7 @@ STRATEGO = {
debug = false, debug = false,
drawzone = false, drawzone = false,
markzone = false, markzone = false,
version = "0.2.4", version = "0.2.5",
portweight = 3, portweight = 3,
POIweight = 1, POIweight = 1,
maxrunways = 3, maxrunways = 3,
@ -195,6 +196,7 @@ STRATEGO = {
usebudget = false, usebudget = false,
CaptureUnits = 3, CaptureUnits = 3,
CaptureThreatlevel = 1, CaptureThreatlevel = 1,
ExcludeShips = true,
} }
--- ---
@ -256,6 +258,7 @@ function STRATEGO:New(Name,Coalition,MaxDist)
self.maxdist = MaxDist or 150 -- km self.maxdist = MaxDist or 150 -- km
self.disttable = {} self.disttable = {}
self.routexists = {} self.routexists = {}
self.ExcludeShips = true
self.lid = string.format("STRATEGO %s %s | ",self.name,self.version) self.lid = string.format("STRATEGO %s %s | ",self.name,self.version)
@ -427,6 +430,7 @@ function STRATEGO:AnalyseBases()
self.bases:ForEach( self.bases:ForEach(
function(afb) function(afb)
local ab = afb -- Wrapper.Airbase#AIRBASE local ab = afb -- Wrapper.Airbase#AIRBASE
if self.ExcludeShips and ab:IsShip() then return end
local abname = ab:GetName() local abname = ab:GetName()
local runways = ab:GetRunways() local runways = ab:GetRunways()
local numrwys = #runways local numrwys = #runways
@ -438,14 +442,14 @@ function STRATEGO:AnalyseBases()
local coa = ab:GetCoalition() local coa = ab:GetCoalition()
if coa == nil then return end -- Spawned FARPS issue - these have no tangible data if coa == nil then return end -- Spawned FARPS issue - these have no tangible data
coa = coa+1 coa = coa+1
local abtype = "AIRBASE" local abtype = STRATEGO.Type.AIRBASE
if ab:IsShip() then if ab:IsShip() then
numrwys = 1 numrwys = 1
abtype = "SHIP" abtype = STRATEGO.Type.SHIP
end end
if ab:IsHelipad() then if ab:IsHelipad() then
numrwys = 1 numrwys = 1
abtype = "FARP" abtype = STRATEGO.Type.FARP
end end
local coord = ab:GetCoordinate() local coord = ab:GetCoordinate()
if debug then if debug then
@ -481,10 +485,10 @@ function STRATEGO:UpdateNodeCoalitions()
local newtable = {} local newtable = {}
for _id,_data in pairs(self.airbasetable) do for _id,_data in pairs(self.airbasetable) do
local data = _data -- #STRATEGO.Data local data = _data -- #STRATEGO.Data
if data.type == "AIRBASE" or data.type == "FARP" then 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() data.coalition = AIRBASE:FindByName(data.name):GetCoalition() or 0
else else
data.coalition = data.opszone:GetOwner() data.coalition = data.opszone:GetOwner() or 0
end end
newtable[_id] = _data newtable[_id] = _data
end end
@ -937,11 +941,13 @@ function STRATEGO:FindClosestConsolidationTarget(Startpoint,BaseWeight)
local cname = self.easynames[tname] local cname = self.easynames[tname]
local targetweight = self.airbasetable[cname].baseweight local targetweight = self.airbasetable[cname].baseweight
coa = self.airbasetable[cname].coalition coa = self.airbasetable[cname].coalition
--self:T("Start -> End: "..startpoint.." -> "..cname)
if (dist < shortest) and (coa ~= self.coalition) and (BaseWeight >= targetweight) then if (dist < shortest) and (coa ~= self.coalition) and (BaseWeight >= targetweight) then
self:T("Found Consolidation Target: "..cname)
shortest = dist shortest = dist
target = cname target = cname
weight = self.airbasetable[cname].weight weight = self.airbasetable[cname].weight
coa = self.airbasetable[cname].coalition coa = coa
end end
end end
end end
@ -974,8 +980,9 @@ function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight)
local coa = self.airbasetable[cname].coalition local coa = self.airbasetable[cname].coalition
local tweight = self.airbasetable[cname].baseweight local tweight = self.airbasetable[cname].baseweight
local ttweight = self.airbasetable[cname].weight 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 if (dist < shortest) and (coa ~= self.coalition) and (tweight >= Weight) then
self:T("Found Strategic Target: "..cname)
shortest = dist shortest = dist
target = cname target = cname
weight = self.airbasetable[cname].weight weight = self.airbasetable[cname].weight
@ -996,38 +1003,31 @@ function STRATEGO:FindStrategicTargets()
local data = _data -- #STRATEGO.Data local data = _data -- #STRATEGO.Data
if data.coalition == self.coalition then if data.coalition == self.coalition then
local dist, name, points, coa = self:FindClosestStrategicTarget(data.name,data.weight) local dist, name, points, coa = self:FindClosestStrategicTarget(data.name,data.weight)
if coa == coalition.side.NEUTRAL and points ~= 0 then if points > 0 then
local fpoints = points + self.NeutralBenefit self:T({dist=dist, name=name, points=points, coa=coa})
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,
}
end end
local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE if points ~= 0 then
if coa == enemycoa and points ~= 0 then local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
local fpoints = points self:T("Enemycoa = "..enemycoa)
local tries = 1 if coa == coalition.side.NEUTRAL then
while targets[fpoints] or tries < 100 do local tdata = {}
fpoints = points + (math.random(1,100)) tdata.name = name
tries = tries + 1 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 end
targets[fpoints] = {
name = name,
dist = dist,
points = fpoints,
coalition = coa,
coalitionname = UTILS.GetCoalitionName(coa),
coordinate = self.airbasetable[name].coord,
}
end end
end end
end end
@ -1044,38 +1044,31 @@ function STRATEGO:FindConsolidationTargets()
local data = _data -- #STRATEGO.Data local data = _data -- #STRATEGO.Data
if data.coalition == self.coalition then if data.coalition == self.coalition then
local dist, name, points, coa = self:FindClosestConsolidationTarget(data.name,self.maxrunways-1) local dist, name, points, coa = self:FindClosestConsolidationTarget(data.name,self.maxrunways-1)
if coa == coalition.side.NEUTRAL and points ~= 0 then if points > 0 then
local fpoints = points + self.NeutralBenefit self:T({dist=dist, name=name, points=points, coa=coa})
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,
}
end end
local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE if points ~= 0 then
if coa == enemycoa and points ~= 0 then local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
local fpoints = points self:T("Enemycoa = "..enemycoa)
local tries = 1 if coa == coalition.side.NEUTRAL then
while targets[fpoints] or tries < 100 do local tdata = {}
fpoints = points - (math.random(1,100)) tdata.name = name
tries = tries + 1 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 end
targets[fpoints] = {
name = name,
dist = dist,
points = fpoints,
coalition = coa,
coalitionname = UTILS.GetCoalitionName(coa),
coordinate = self.airbasetable[name].coord,
}
end end
end end
end end
@ -1245,13 +1238,15 @@ end
-- @return #table Target Table with #STRATEGO.Target data or nil if none found. -- @return #table Target Table with #STRATEGO.Target data or nil if none found.
function STRATEGO:FindAffordableStrategicTarget() function STRATEGO:FindAffordableStrategicTarget()
self:T(self.lid.."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 budget = self.Budget
--local leftover = self.Budget --local leftover = self.Budget
local target = nil -- #STRATEGO.Target local ftarget = nil -- #STRATEGO.Target
local Targets = {} local Targets = {}
for _,_data in pairs(targets) do for _,_data in pairs(Stargets) do
local data = _data -- #STRATEGO.Target 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 and budget-data.points < leftover then
if data.points <= budget then if data.points <= budget then
--leftover = budget-data.points --leftover = budget-data.points
@ -1259,14 +1254,18 @@ function STRATEGO:FindAffordableStrategicTarget()
self:T(self.lid.."Affordable strategic target: "..data.name) self:T(self.lid.."Affordable strategic target: "..data.name)
end end
end end
if not targets then if #Targets == 0 then
self:T(self.lid.."No suitable target found!") self:T(self.lid.."No suitable target found!")
return nil return nil
end end
target = Targets[math.random(1,#Targets)] if #Targets > 1 then
if target then ftarget = Targets[math.random(1,#Targets)]
self:T(self.lid.."Final affordable strategic target: "..target.name) else
return target ftarget = Targets[1]
end
if ftarget then
self:T(self.lid.."Final affordable strategic target: "..ftarget.name)
return ftarget
else else
return nil return nil
end end
@ -1277,13 +1276,15 @@ end
-- @return #table Target Table with #STRATEGO.Target data or nil if none found. -- @return #table Target Table with #STRATEGO.Target data or nil if none found.
function STRATEGO:FindAffordableConsolidationTarget() function STRATEGO:FindAffordableConsolidationTarget()
self:T(self.lid.."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 budget = self.Budget
--local leftover = self.Budget --local leftover = self.Budget
local target = nil -- #STRATEGO.Target local ftarget = nil -- #STRATEGO.Target
local Targets = {} local Targets = {}
for _,_data in pairs(targets) do for _,_data in pairs(Ctargets) do
local data = _data -- #STRATEGO.Target 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 and budget-data.points < leftover then
if data.points <= budget then if data.points <= budget then
--leftover = budget-data.points --leftover = budget-data.points
@ -1291,14 +1292,18 @@ function STRATEGO:FindAffordableConsolidationTarget()
self:T(self.lid.."Affordable consolidation target: "..data.name) self:T(self.lid.."Affordable consolidation target: "..data.name)
end end
end end
if not targets then if #Targets == 0 then
self:T(self.lid.."No suitable target found!") self:T(self.lid.."No suitable target found!")
return nil return nil
end end
target = Targets[math.random(1,#Targets)] if #Targets > 1 then
if target then ftarget = Targets[math.random(1,#Targets)]
self:T(self.lid.."Final affordable consolidation target: "..target.name) else
return target ftarget = Targets[1]
end
if ftarget then
self:T(self.lid.."Final affordable consolidation target: "..ftarget.name)
return ftarget
else else
return nil return nil
end end

View File

@ -9755,7 +9755,7 @@ function AIRBOSS:_Groove( playerData )
local glideslopeError = groovedata.GSE local glideslopeError = groovedata.GSE
local AoA = groovedata.AoA local AoA = groovedata.AoA
if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 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 -- Start time in groove
playerData.TIG0 = timer.getTime() playerData.TIG0 = timer.getTime()

File diff suppressed because it is too large Load Diff

View File

@ -508,7 +508,7 @@ do
-- @field #AWACS -- @field #AWACS
AWACS = { AWACS = {
ClassName = "AWACS", -- #string ClassName = "AWACS", -- #string
version = "0.2.61", -- #string version = "0.2.63", -- #string
lid = "", -- #string lid = "", -- #string
coalition = coalition.side.BLUE, -- #number coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string coalitiontxt = "blue", -- #string
@ -1384,7 +1384,7 @@ end
-- Functions -- 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 #AWACS self
-- @param #number BaseFreq Base Frequency to use, defaults to 130. -- @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. -- @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 -- @return #AWACS self
function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number) function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
self:T(self.lid.."SetTacticalRadios") 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.TacticalMenu = true
self.TacticalBaseFreq = BaseFreq or 130 self.TacticalBaseFreq = BaseFreq or 130
self.TacticalIncrFreq = Increase or 0.5 self.TacticalIncrFreq = Increase or 0.5
@ -1407,7 +1411,7 @@ function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
self.TacticalFrequencies[freq] = freq self.TacticalFrequencies[freq] = freq
end end
if self.AwacsSRS then 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:SetCoalition(self.coalition)
self.TacticalSRS:SetGender(self.Gender) self.TacticalSRS:SetGender(self.Gender)
self.TacticalSRS:SetCulture(self.Culture) self.TacticalSRS:SetCulture(self.Culture)
@ -2085,8 +2089,9 @@ end
-- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest) -- @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 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 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 -- @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:T(self.lid.."SetSRS")
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
self.Gender = Gender or MSRS.gender or "male" 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.PathToGoogleKey = PathToGoogleKey
self.AccessKey = AccessKey self.AccessKey = AccessKey
self.Volume = Volume or 1.0 self.Volume = Volume or 1.0
self.Backend = Backend or MSRS.backend
self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation) BASE:I({backend = self.Backend})
self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend)
self.AwacsSRS:SetCoalition(self.coalition) self.AwacsSRS:SetCoalition(self.coalition)
self.AwacsSRS:SetGender(self.Gender) self.AwacsSRS:SetGender(self.Gender)
self.AwacsSRS:SetCulture(self.Culture) self.AwacsSRS:SetCulture(self.Culture)
@ -2499,13 +2505,22 @@ function AWACS:_CheckMerges()
local cpos = contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate() local cpos = contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate()
local dist = ppos:Get2DDistance(cpos) local dist = ppos:Get2DDistance(cpos)
local distnm = UTILS.Round(UTILS.MetersToNM(dist),0) local distnm = UTILS.Round(UTILS.MetersToNM(dist),0)
if (pilot.IsPlayer or self.debug) and distnm <= 5 and not contact.MergeCallDone 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 "" --local label = contact.EngagementTag or ""
if not contact.MergeCallDone or not string.find(label,pcallsign) then --if not contact.MergeCallDone or not string.find(label,pcallsign) then
self:T(self.lid.."Merged") self:T(self.lid.."Merged")
self:_MergedCall(_id) self:_MergedCall(_id)
contact.MergeCallDone = true --contact.MergeCallDone = true
end --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
end end
) )
@ -3099,7 +3114,7 @@ function AWACS:_BogeyDope(Group,Tactical)
local clean = self.gettext:GetEntry("CLEAN",self.locale) local clean = self.gettext:GetEntry("CLEAN",self.locale)
text = string.format(clean,self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt) 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 else
@ -3141,9 +3156,13 @@ end
function AWACS:_ShowAwacsInfo(Group) function AWACS:_ShowAwacsInfo(Group)
self:T(self.lid.."_ShowAwacsInfo") self:T(self.lid.."_ShowAwacsInfo")
local report = REPORT:New("Info") local report = REPORT:New("Info")
local STN = self.STN
report:Add("====================") report:Add("====================")
report:Add(string.format("AWACS %s",self.callsigntxt)) report:Add(string.format("AWACS %s",self.callsigntxt))
report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation))) 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("Bulls Alias: %s",self.AOName))
report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM())) report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM()))
report:Add("====================") report:Add("====================")
@ -5467,7 +5486,7 @@ function AWACS:_TACRangeCall(GID,Contact)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming local contacttag = Contact.TargetGroupNaming
if contact and not Contact.TACCallDone then if contact then --and not Contact.TACCallDone then
local position = contact.position -- Core.Point#COORDINATE local position = contact.position -- Core.Point#COORDINATE
if position then if position then
local distance = position:Get2DDistance(managedgroup.Group:GetCoordinate()) local distance = position:Get2DDistance(managedgroup.Group:GetCoordinate())
@ -5477,6 +5496,15 @@ function AWACS:_TACRangeCall(GID,Contact)
local text = string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles) local text = string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles)
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING) self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING)
if GID and GID ~= 0 then
--local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
local name = managedgroup.GroupName
if self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end end
end end
return self return self
@ -5495,8 +5523,8 @@ function AWACS:_MeldRangeCall(GID,Contact)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local flightpos = managedgroup.Group:GetCoordinate() local flightpos = managedgroup.Group:GetCoordinate()
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming local contacttag = Contact.TargetGroupNaming or "Bogey"
if contact and not Contact.MeldCallDone then if contact then --and not Contact.MeldCallDone then
local position = contact.position -- Core.Point#COORDINATE local position = contact.position -- Core.Point#COORDINATE
if position then if position then
local BRATExt = "" local BRATExt = ""
@ -5509,6 +5537,15 @@ function AWACS:_MeldRangeCall(GID,Contact)
local text = string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt) local text = string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt)
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING) 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
end end
return self return self
@ -5525,7 +5562,7 @@ function AWACS:_ThreatRangeCall(GID,Contact)
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
local flightpos = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition local flightpos = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
local contacttag = Contact.TargetGroupNaming local contacttag = Contact.TargetGroupNaming or "Bogey"
if contact then if contact then
local position = contact.position or contact.group:GetCoordinate() -- Core.Point#COORDINATE local position = contact.position or contact.group:GetCoordinate() -- Core.Point#COORDINATE
if position then if position then
@ -5539,6 +5576,15 @@ function AWACS:_ThreatRangeCall(GID,Contact)
local thrt = self.gettext:GetEntry("THREAT",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) 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) self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
if GID and GID ~= 0 then
--local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
local name = managedgroup.GroupName
if self.TacticalSubscribers[name] then
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
end
end
end
end end
end end
return self return self
@ -5953,6 +5999,10 @@ function AWACS:_CheckAwacsStatus()
local awacs = nil -- Wrapper.Group#GROUP local awacs = nil -- Wrapper.Group#GROUP
if self.AwacsFG then if self.AwacsFG then
awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP
local unit = awacs:GetUnit(1)
if unit then
self.STN = tostring(unit:GetSTN())
end
end end
local monitoringdata = self.MonitoringData -- #AWACS.MonitoringData local monitoringdata = self.MonitoringData -- #AWACS.MonitoringData
@ -6632,7 +6682,7 @@ function AWACS:onafterCheckTacticalQueue(From,Event,To)
end -- end while end -- end while
if self:Is("Running") then if not self:Is("Stopped") then
self:__CheckTacticalQueue(-self.TacticalInterval) self:__CheckTacticalQueue(-self.TacticalInterval)
end end
return self return self

View File

@ -313,8 +313,8 @@ end
-- --
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path -- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename -- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce() -- local BlueSaveOps = SET_OPSGROUP:New():FilterCoalitions("blue"):FilterCategoryGround():FilterOnce()
-- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename) -- UTILS.SaveSetOfOpsGroups(BlueSaveOps,Path,BlueOpsFilename)
-- --
-- where Path and Filename are strings, as chosen by you. -- 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 -- 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 Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename -- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then -- 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 -- for _,_platoondata in pairs (loadback) do
-- local groupname = _platoondata.groupname -- #string -- local groupname = _platoondata.groupname -- #string
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE -- 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["UH-60L"] = 10
CSAR.AircraftType["AH-64D_BLK_II"] = 2 CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2
CSAR.AircraftType["MH-60R"] = 10
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.19" CSAR.version="1.0.20"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list

View File

@ -24,7 +24,7 @@
-- @module Ops.CTLD -- @module Ops.CTLD
-- @image OPS_CTLD.jpg -- @image OPS_CTLD.jpg
-- Last Update December 2023 -- Last Update February 2024
do do
@ -44,6 +44,7 @@ do
-- @field #number PerCrateMass Mass in kg. -- @field #number PerCrateMass Mass in kg.
-- @field #number Stock Number of builds available, -1 for unlimited. -- @field #number Stock Number of builds available, -1 for unlimited.
-- @field #string Subcategory Sub-category name. -- @field #string Subcategory Sub-category name.
-- @field #boolean DontShowInMenu Show this item in menu or not.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- ---
@ -62,6 +63,7 @@ CTLD_CARGO = {
PerCrateMass = 0, PerCrateMass = 0,
Stock = nil, Stock = nil,
Mark = nil, Mark = nil,
DontShowInMenu = false,
} }
--- Define cargo types. --- Define cargo types.
@ -97,8 +99,9 @@ CTLD_CARGO = {
-- @param #number PerCrateMass Mass in kg -- @param #number PerCrateMass Mass in kg
-- @param #number Stock Number of builds available, nil for unlimited -- @param #number Stock Number of builds available, nil for unlimited
-- @param #string Subcategory Name of subcategory, handy if using > 10 types to load. -- @param #string Subcategory Name of subcategory, handy if using > 10 types to load.
-- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it).
-- @return #CTLD_CARGO self -- @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. -- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped}) self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
@ -115,6 +118,7 @@ CTLD_CARGO = {
self.Stock = Stock or nil --#number self.Stock = Stock or nil --#number
self.Mark = nil self.Mark = nil
self.Subcategory = Subcategory or "Other" self.Subcategory = Subcategory or "Other"
self.DontShowInMenu = DontShowInMenu or false
return self return self
end 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. ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
--Actually it's longer, but the center coord is off-center of the model. --Actually it's longer, but the center coord is off-center of the model.
["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats ["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo ["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450}, ["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
} }
--- CTLD class version. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.45" CTLD.version="1.0.48"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self
@ -1433,7 +1438,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
--- Pseudo Functions --- --- 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 -- @function [parent=#CTLD] Start
-- @param #CTLD self -- @param #CTLD self
@ -3024,9 +3029,10 @@ end
function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template) function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
local Positions = {} local Positions = {}
local template = _DATABASE:GetGroupTemplate(Template) local template = _DATABASE:GetGroupTemplate(Template)
UTILS.PrintTableToLog(template) --UTILS.PrintTableToLog(template)
local numbertroops = #template.units local numbertroops = #template.units
local 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 for i=1,360,math.floor(360/numbertroops) do
local phead = ((Heading+270+i)%360) local phead = ((Heading+270+i)%360)
local post = newcenter:Translate(Radius,phead) local post = newcenter:Translate(Radius,phead)
@ -3038,7 +3044,7 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
} }
table.insert(Positions,p1t) table.insert(Positions,p1t)
end end
UTILS.PrintTableToLog(Positions) --UTILS.PrintTableToLog(Positions)
return Positions return Positions
end end
@ -3700,14 +3706,20 @@ function CTLD:_RefreshF10Menus()
for _,_entry in pairs(self.Cargo_Troops) do for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
menucount = menucount + 1 local noshow = entry.DontShowInMenu
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry) 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 end
else else
for _,_entry in pairs(self.Cargo_Troops) do for _,_entry in pairs(self.Cargo_Troops) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
menucount = menucount + 1 local noshow = entry.DontShowInMenu
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry) 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
end end
local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh() local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh()
@ -3728,33 +3740,45 @@ function CTLD:_RefreshF10Menus()
for _,_entry in pairs(self.Cargo_Crates) do for _,_entry in pairs(self.Cargo_Crates) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
menucount = menucount + 1 local noshow = entry.DontShowInMenu
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if not noshow then
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) 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 end
for _,_entry in pairs(self.Cargo_Statics) do for _,_entry in pairs(self.Cargo_Statics) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
local subcat = entry.Subcategory local subcat = entry.Subcategory
menucount = menucount + 1 local noshow = entry.DontShowInMenu
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if not noshow then
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) 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 end
else else
for _,_entry in pairs(self.Cargo_Crates) do for _,_entry in pairs(self.Cargo_Crates) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
menucount = menucount + 1 local noshow = entry.DontShowInMenu
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if not noshow then
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) 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
for _,_entry in pairs(self.Cargo_Statics) do for _,_entry in pairs(self.Cargo_Statics) do
local entry = _entry -- #CTLD_CARGO local entry = _entry -- #CTLD_CARGO
menucount = menucount + 1 local noshow = entry.DontShowInMenu
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) if not noshow then
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) 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
end end
listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit)
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) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit)
if not self.nobuildmenu then if not self.nobuildmenu then
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)

View File

@ -140,7 +140,7 @@ COMMANDER = {
--- COMMANDER class version. --- COMMANDER class version.
-- @field #string version -- @field #string version
COMMANDER.version="0.1.3" COMMANDER.version="0.1.4"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -675,7 +675,8 @@ function COMMANDER:AddCapZone(Zone, Altitude, Speed, Heading, Leg)
patrolzone.zone=Zone patrolzone.zone=Zone
patrolzone.altitude=Altitude or 12000 patrolzone.altitude=Altitude or 12000
patrolzone.heading=Heading or 270 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.leg=Leg or 30
patrolzone.mission=nil patrolzone.mission=nil
--patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "CAP Zone"):ToCoalition(self:GetCoalition()) --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.zone=Zone
patrolzone.altitude=Altitude or 12000 patrolzone.altitude=Altitude or 12000
patrolzone.heading=Heading or 270 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.leg=Leg or 30
patrolzone.mission=nil patrolzone.mission=nil
--patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "GCICAP Zone"):ToCoalition(self:GetCoalition()) --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.zone=Zone
awacszone.altitude=Altitude or 12000 awacszone.altitude=Altitude or 12000
awacszone.heading=Heading or 270 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.leg=Leg or 30
awacszone.mission=nil awacszone.mission=nil
--awacszone.marker=MARKER:New(awacszone.zone:GetCoordinate(), "AWACS Zone"):ToCoalition(self:GetCoalition()) --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.zone=Zone
tankerzone.altitude=Altitude or 12000 tankerzone.altitude=Altitude or 12000
tankerzone.heading=Heading or 270 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.leg=Leg or 30
tankerzone.refuelsystem=RefuelSystem tankerzone.refuelsystem=RefuelSystem
tankerzone.mission=nil tankerzone.mission=nil

View File

@ -3799,10 +3799,11 @@ function FLIGHTGROUP:_InitGroup(Template)
self.speedMax=group:GetSpeedMax() self.speedMax=group:GetSpeedMax()
-- Is group mobile? -- Is group mobile?
if self.speedMax>3.6 then if self.speedMax and self.speedMax>3.6 then
self.isMobile=true self.isMobile=true
else else
self.isMobile=false self.isMobile=false
self.speedMax = 0
end end
-- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings. -- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings.

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 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 score=score+25
elseif currmission.type==AUFTRAG.Type.NOTHING then elseif currmission.type==AUFTRAG.Type.NOTHING then
score=score+25 score=score+30
end end
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 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. -- 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 score=score-10
else else
-- Combat mission. -- Combat mission.

View File

@ -1800,10 +1800,11 @@ function NAVYGROUP:_InitGroup(Template)
self.speedMax=self.group:GetSpeedMax() self.speedMax=self.group:GetSpeedMax()
-- Is group mobile? -- Is group mobile?
if self.speedMax>3.6 then if self.speedMax and self.speedMax>3.6 then
self.isMobile=true self.isMobile=true
else else
self.isMobile=false self.isMobile=false
self.speedMax = 0
end end
-- Cruise speed: 70% of max speed. -- Cruise speed: 70% of max speed.

View File

@ -508,7 +508,7 @@ OPSGROUP.CargoStatus={
--- OpsGroup version. --- OpsGroup version.
-- @field #string version -- @field #string version
OPSGROUP.version="1.0.0" OPSGROUP.version="1.0.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -535,7 +535,7 @@ OPSGROUP.version="1.0.0"
-- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`. -- @param Wrapper.Group#GROUP group The GROUP object. Can also be given by its group name as `#string`.
-- @return #OPSGROUP self -- @return #OPSGROUP self
function OPSGROUP:New(group) function OPSGROUP:New(group)
-- Inherit everything from FSM class. -- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #OPSGROUP local self=BASE:Inherit(self, FSM:New()) -- #OPSGROUP
@ -554,10 +554,15 @@ function OPSGROUP:New(group)
-- Check if group exists. -- Check if group exists.
if self.group then if self.group then
if not self:IsExist() 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 return nil
end end
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. -- Set the template.
self:_SetTemplate() self:_SetTemplate()
@ -591,33 +596,34 @@ function OPSGROUP:New(group)
if units then if units then
local masterunit=units[1] --Wrapper.Unit#UNIT local masterunit=units[1] --Wrapper.Unit#UNIT
-- Get Descriptors. if unit then
self.descriptors=masterunit:GetDesc() -- Get Descriptors.
self.descriptors=masterunit:GetDesc()
-- Set type name.
self.actype=masterunit:GetTypeName() -- Set type name.
self.actype=masterunit:GetTypeName()
-- Is this a submarine.
self.isSubmarine=masterunit:HasAttribute("Submarines") -- Is this a submarine.
self.isSubmarine=masterunit:HasAttribute("Submarines")
-- Has this a datalink?
self.isEPLRS=masterunit:HasAttribute("Datalink") -- Has this a datalink?
self.isEPLRS=masterunit:HasAttribute("Datalink")
if self:IsFlightgroup() then
if self:IsFlightgroup() then
self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000
self.rangemax=self.descriptors.range and self.descriptors.range*1000 or 500*1000
self.ceiling=self.descriptors.Hmax
self.ceiling=self.descriptors.Hmax
self.tankertype=select(2, masterunit:IsTanker())
self.refueltype=select(2, masterunit:IsRefuelable()) 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)) --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
end end
-- Init set of detected units. -- Init set of detected units.

View File

@ -21,7 +21,7 @@
-- === -- ===
-- @module Ops.PlayerTask -- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg -- @image OPS_PlayerTask.jpg
-- @date Last Update Jan 2024 -- @date Last Update Feb 2024
do do
@ -411,6 +411,15 @@ function PLAYERTASK:IsDone()
return IsDone return IsDone
end 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 --- [User] Get client names assigned as table of #strings
-- @param #PLAYERTASK self -- @param #PLAYERTASK self
-- @return #table clients -- @return #table clients
@ -1552,7 +1561,7 @@ PLAYERTASKCONTROLLER.Messages = {
--- PLAYERTASK class version. --- PLAYERTASK class version.
-- @field #string version -- @field #string version
PLAYERTASKCONTROLLER.version="0.1.64" PLAYERTASKCONTROLLER.version="0.1.65"
--- Create and run a new TASKCONTROLLER instance. --- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self -- @param #PLAYERTASKCONTROLLER self
@ -3173,7 +3182,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale) local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale)
local taskname = string.format(tname,task.Type,task.PlayerTaskNr) local taskname = string.format(tname,task.Type,task.PlayerTaskNr)
local ttstaskname = string.format(ttsname,task.TTSType,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 CoordText = ""
local CoordTextLLDM = nil local CoordTextLLDM = nil
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then

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 -- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
-- --
-- @module Sound.Radio -- @module Sound.Radio

View File

@ -14,7 +14,7 @@
-- --
-- === -- ===
-- --
-- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Sound/MSRS). -- ## Example Missions: [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/MSRS).
-- --
-- === -- ===
-- --

View File

@ -444,10 +444,11 @@ end
--- Print a table to log in a nice format --- Print a table to log in a nice format
-- @param #table table The table to print -- @param #table table The table to print
-- @param #number indent Number of indents -- @param #number indent Number of indents
-- @param #boolean noprint Don't log but return text
-- @return #string text Text created on the fly of the log output -- @return #string text Text created on the fly of the log output
function UTILS.PrintTableToLog(table, indent) function UTILS.PrintTableToLog(table, indent, noprint)
local text = "\n" local text = "\n"
if not table then if not table or type(table) ~= "table" then
env.warning("No table passed!") env.warning("No table passed!")
return nil return nil
end end
@ -455,11 +456,16 @@ function UTILS.PrintTableToLog(table, indent)
for k, v in pairs(table) do for k, v in pairs(table) do
if string.find(k," ") then k='"'..k..'"'end if string.find(k," ") then k='"'..k..'"'end
if type(v) == "table" then if type(v) == "table" then
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 ..string.rep(" ", indent) .. tostring(k) .. " = {\n"
text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\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" text = text .. string.rep(" ", indent) .. "},\n"
elseif type(v) == "function" then
else else
local value local value
if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then
@ -467,7 +473,9 @@ function UTILS.PrintTableToLog(table, indent)
else else
value = '"'..tostring(v)..'"' value = '"'..tostring(v)..'"'
end 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" text = text .. string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n"
end end
end end
@ -2229,6 +2237,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true -- no doors on this one ;) return true -- no doors on this one ;)
end end
if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then
BASE:T(unit_name .. " cargo door is open")
return true
end
return false return false
end -- nil end -- nil
@ -3717,3 +3730,116 @@ end
function UTILS.OctalToDecimal(Number) function UTILS.OctalToDecimal(Number)
return tonumber(Number,8) return tonumber(Number,8)
end 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 -- @image Wrapper_Static.JPG
--- @type STATIC ---
-- @type STATIC
-- @extends Wrapper.Positionable#POSITIONABLE -- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle Static objects. --- Wrapper class to handle Static objects.
@ -236,7 +237,7 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
end 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. -- This is useful to respawn a cargo after it has been destroyed.
-- @param #STATIC self -- @param #STATIC self
-- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently. -- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently.
@ -248,7 +249,7 @@ function STATIC:ReSpawn(CountryID, Delay)
else else
CountryID=CountryID or self:GetCountry() CountryID=CountryID or self:GetCountry()
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID) local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, CountryID)
SpawnStatic:Spawn(nil, self.StaticName) SpawnStatic:Spawn(nil, self.StaticName)
@ -270,8 +271,8 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
if Delay and Delay>0 then if Delay and Delay>0 then
SCHEDULER:New(nil, self.ReSpawnAt, {self, Coordinate, Heading}, Delay) SCHEDULER:New(nil, self.ReSpawnAt, {self, Coordinate, Heading}, Delay)
else else
local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry()) local SpawnStatic=SPAWNSTATIC:NewFromStatic(self.StaticName, self:GetCountry())
SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName) SpawnStatic:SpawnFromCoordinate(Coordinate, Heading, self.StaticName)
@ -280,3 +281,52 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
return self return self
end end
--- Find the first(!) STATIC matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #STATIC self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #STATIC The STATIC.
-- @usage
-- -- Find a static with a partial static name
-- local grp = STATIC:FindByMatching( "Apple" )
-- -- will return e.g. a static named "Apple-1-1"
--
-- -- using a pattern
-- local grp = STATIC:FindByMatching( ".%d.%d$" )
-- -- will return the first static found ending in "-1-1" to "-9-9", but not e.g. "-10-1"
function STATIC:FindByMatching( Pattern )
local GroupFound = nil
for name,static in pairs(_DATABASE.STATICS) do
if string.match(name, Pattern ) then
GroupFound = static
break
end
end
return GroupFound
end
--- Find all STATIC objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #STATIC self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #table Groups Table of matching #STATIC objects found
-- @usage
-- -- Find all static with a partial static name
-- local grptable = STATIC:FindAllByMatching( "Apple" )
-- -- will return all statics with "Apple" in the name
--
-- -- using a pattern
-- local grp = STATIC:FindAllByMatching( ".%d.%d$" )
-- -- will return the all statics found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10"
function STATIC:FindAllByMatching( Pattern )
local GroupsFound = {}
for name,static in pairs(_DATABASE.STATICS) do
if string.match(name, Pattern ) then
GroupsFound[#GroupsFound+1] = static
end
end
return GroupsFound
end

View File

@ -8,7 +8,7 @@
-- --
-- ## Example Missions: -- ## Example Missions:
-- --
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_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 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. -- 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. -- This class allows you to add and remove items to a DCS warehouse, such as aircraft, liquids, weapons and other equipment.
-- --
-- # Constructor -- # 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. -- 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 -- You can get the `STORAGE` object from the
-- --
-- -- Create a STORAGE instance of the Batumi warehouse -- -- Create a STORAGE instance of the Batumi warehouse
-- local storage=STORAGE:FindByName("Batumi") -- local storage=STORAGE:FindByName("Batumi")
-- --
-- An other way to get the `STORAGE` object is to retrieve it from the AIRBASE function `AIRBASE:GetStorage()` -- An other way to get the `STORAGE` object is to retrieve it from the AIRBASE function `AIRBASE:GetStorage()`
-- --
-- -- Get storage instance of Batumi airbase -- -- Get storage instance of Batumi airbase
-- local Batumi=AIRBASE:FindByName("Batumi") -- local Batumi=AIRBASE:FindByName("Batumi")
-- local storage=Batumi:GetStorage() -- local storage=Batumi:GetStorage()
-- --
-- # Aircraft, Weapons and Equipment -- # Aircraft, Weapons and Equipment
-- --
-- ## Adding Items -- ## Adding Items
-- --
-- To add aircraft, weapons and/or othe equipment, you can use the @{#STORAGE.AddItem}() function -- To add aircraft, weapons and/or othe equipment, you can use the @{#STORAGE.AddItem}() function
-- --
-- storage:AddItem("A-10C", 3) -- storage:AddItem("A-10C", 3)
-- storage:AddItem("weapons.missiles.AIM_120C", 10) -- storage:AddItem("weapons.missiles.AIM_120C", 10)
-- --
-- This will add three A-10Cs and ten AIM-120C missiles to the warehouse inventory. -- This will add three A-10Cs and ten AIM-120C missiles to the warehouse inventory.
-- --
-- ## Setting Items -- ## Setting Items
-- --
-- You can also explicitly set, how many items are in the inventory with the @{#STORAGE.SetItem}() function. -- You can also explicitly set, how many items are in the inventory with the @{#STORAGE.SetItem}() function.
-- --
-- ## Removing Items -- ## Removing Items
-- --
-- Items can be removed from the inventory with the @{#STORAGE.RemoveItem}() function. -- Items can be removed from the inventory with the @{#STORAGE.RemoveItem}() function.
-- --
-- ## Getting Amount -- ## Getting Amount
-- --
-- The number of items currently in the inventory can be obtained with the @{#STORAGE.GetItemAmount}() function -- The number of items currently in the inventory can be obtained with the @{#STORAGE.GetItemAmount}() function
-- --
-- local N=storage:GetItemAmount("A-10C") -- local N=storage:GetItemAmount("A-10C")
-- env.info(string.format("We currently have %d A-10Cs available", N)) -- env.info(string.format("We currently have %d A-10Cs available", N))
-- --
-- # Liquids -- # Liquids
-- --
-- Liquids can be added and removed by slightly different functions as described below. Currently there are four types of 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` -- * Jet fuel `STORAGE.Liquid.JETFUEL`
-- * Aircraft gasoline `STORAGE.Liquid.GASOLINE` -- * Aircraft gasoline `STORAGE.Liquid.GASOLINE`
-- * MW 50 `STORAGE.Liquid.MW50` -- * MW 50 `STORAGE.Liquid.MW50`
-- * Diesel `STORAGE.Liquid.DIESEL` -- * Diesel `STORAGE.Liquid.DIESEL`
-- --
-- ## Adding Liquids -- ## Adding Liquids
-- --
-- To add a certain type of liquid, you can use the @{#STORAGE.AddItem}(Type, Amount) function -- 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.JETFUEL, 10000)
-- storage:AddLiquid(STORAGE.Liquid.DIESEL, 20000) -- storage:AddLiquid(STORAGE.Liquid.DIESEL, 20000)
-- --
-- This will add 10,000 kg of jet fuel and 20,000 kg of diesel to the inventory. -- This will add 10,000 kg of jet fuel and 20,000 kg of diesel to the inventory.
-- --
-- ## Setting Liquids -- ## Setting Liquids
-- --
-- You can also explicitly set the amount of liquid with the @{#STORAGE.SetLiquid}(Type, Amount) function. -- You can also explicitly set the amount of liquid with the @{#STORAGE.SetLiquid}(Type, Amount) function.
-- --
-- ## Removing Liquids -- ## Removing Liquids
-- --
-- Liquids can be removed with @{#STORAGE.RemoveLiquid}(Type, Amount) function. -- Liquids can be removed with @{#STORAGE.RemoveLiquid}(Type, Amount) function.
-- --
-- ## Getting Amount -- ## Getting Amount
-- --
-- The current amount of a certain liquid can be obtained with the @{#STORAGE.GetLiquidAmount}(Type) function -- The current amount of a certain liquid can be obtained with the @{#STORAGE.GetLiquidAmount}(Type) function
-- --
-- local N=storage:GetLiquidAmount(STORAGE.Liquid.DIESEL) -- local N=storage:GetLiquidAmount(STORAGE.Liquid.DIESEL)
-- env.info(string.format("We currently have %d kg of Diesel available", N)) -- env.info(string.format("We currently have %d kg of Diesel available", N))
-- --
-- --
-- # Inventory -- # 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: -- 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() -- local aircraft, liquids, weapons=storage:GetInventory()
-- --
-- UTILS.PrintTableToLog(aircraft) -- UTILS.PrintTableToLog(aircraft)
-- UTILS.PrintTableToLog(liquids) -- UTILS.PrintTableToLog(liquids)
-- UTILS.PrintTableToLog(weapons) -- UTILS.PrintTableToLog(weapons)
@ -168,7 +168,7 @@ function STORAGE:New(AirbaseName)
local self=BASE:Inherit(self, BASE:New()) -- #STORAGE local self=BASE:Inherit(self, BASE:New()) -- #STORAGE
self.airbase=Airbase.getByName(AirbaseName) self.airbase=Airbase.getByName(AirbaseName)
if Airbase.getWarehouse then if Airbase.getWarehouse then
self.warehouse=self.airbase:getWarehouse() self.warehouse=self.airbase:getWarehouse()
end end
@ -322,7 +322,7 @@ end
function STORAGE:GetLiquidName(Type) function STORAGE:GetLiquidName(Type)
local name="Unknown" local name="Unknown"
if Type==STORAGE.Liquid.JETFUEL then if Type==STORAGE.Liquid.JETFUEL then
name = "Jet fuel" name = "Jet fuel"
elseif Type==STORAGE.Liquid.GASOLINE then elseif Type==STORAGE.Liquid.GASOLINE then
@ -411,25 +411,25 @@ function STORAGE:IsUnlimited(Type)
-- Get current amount of type. -- Get current amount of type.
local N=self:GetAmount(Type) local N=self:GetAmount(Type)
local unlimited=false local unlimited=false
if N>0 then if N>0 then
-- Remove one item. -- Remove one item.
self:RemoveAmount(Type, 1) self:RemoveAmount(Type, 1)
-- Get amount. -- Get amount.
local n=self:GetAmount(Type) local n=self:GetAmount(Type)
-- If amount did not change, it is unlimited. -- If amount did not change, it is unlimited.
unlimited=n==N unlimited=n==N
-- Add item back. -- Add item back.
if not unlimited then if not unlimited then
self:AddAmount(Type, 1) self:AddAmount(Type, 1)
end end
-- Debug info. -- Debug info.
self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n)) self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n))
end end
@ -523,7 +523,7 @@ end
function STORAGE:GetInventory(Item) function STORAGE:GetInventory(Item)
local inventory=self.warehouse:getInventory(Item) local inventory=self.warehouse:getInventory(Item)
return inventory.aircraft, inventory.liquids, inventory.weapon return inventory.aircraft, inventory.liquids, inventory.weapon
end end

View File

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

View File

@ -14,7 +14,7 @@
-- --
-- ## Additional Material: -- ## Additional Material:
-- --
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Weapon) -- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Weapon)
-- * **YouTube videos:** None -- * **YouTube videos:** None
-- * **Guides:** None -- * **Guides:** None
-- --
@ -69,77 +69,77 @@
-- === -- ===
-- --
-- # The WEAPON Concept -- # The WEAPON Concept
-- --
-- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions. -- 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 -- 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. -- 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()`. -- **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. -- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data.
-- --
-- # Tracking -- # 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 -- 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. -- 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. -- 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. -- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions.
-- --
-- ## Impact Point Marking -- ## Impact Point Marking
-- --
-- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}. -- 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}. -- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}.
-- --
-- ## Callback functions -- ## 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. -- 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 -- ### Callback on Impact
-- --
-- The function called on impact can be set with @{#WEAPON.SetFuncImpact} -- The function called on impact can be set with @{#WEAPON.SetFuncImpact}
-- --
-- ### Callback when Tracking -- ### Callback when Tracking
-- --
-- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack} -- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack}
-- --
-- # Target -- # 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. -- 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. -- 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. -- You can get the target name with the @{#WEAPON.GetTargetName} function.
-- --
-- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function. -- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function.
-- --
-- # Category -- # Category
-- --
-- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function. -- 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} -- * bomb with @{#WEAPON.IsBomb}
-- * rocket with @{#WEAPON.IsRocket} -- * rocket with @{#WEAPON.IsRocket}
-- * missile with @{#WEAPON.IsMissile} -- * missile with @{#WEAPON.IsMissile}
-- * shell with @{#WEAPON.IsShell} -- * shell with @{#WEAPON.IsShell}
-- * torpedo with @{#WEAPON.IsTorpedo} -- * torpedo with @{#WEAPON.IsTorpedo}
-- --
-- # Parameters -- # Parameters
-- --
-- You can get various parameters of the weapon, *e.g.* -- You can get various parameters of the weapon, *e.g.*
-- --
-- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate} -- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate}
-- * speed: @{#WEAPON.GetSpeed} -- * speed: @{#WEAPON.GetSpeed}
-- * coalition: @{#WEAPON.GetCoalition} -- * coalition: @{#WEAPON.GetCoalition}
-- * country: @{#WEAPON.GetCountry} -- * country: @{#WEAPON.GetCountry}
-- --
-- # Dependencies -- # Dependencies
-- --
-- This class is used (at least) in the MOOSE classes: -- This class is used (at least) in the MOOSE classes:
-- --
-- * RANGE (to determine the impact points of bombs and missiles) -- * RANGE (to determine the impact points of bombs and missiles)
-- * ARTY (to destroy and replace shells with smoke or illumination) -- * ARTY (to destroy and replace shells with smoke or illumination)
-- * FOX (to destroy the missile before it hits the target) -- * FOX (to destroy the missile before it hits the target)
@ -181,48 +181,48 @@ function WEAPON:New(WeaponObject)
-- Inherit everything from FSM class. -- Inherit everything from FSM class.
local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON
-- Set DCS weapon object. -- Set DCS weapon object.
self.weapon=WeaponObject self.weapon=WeaponObject
-- Descriptors containing a lot of info. -- Descriptors containing a lot of info.
self.desc=WeaponObject:getDesc() self.desc=WeaponObject:getDesc()
-- This gives the object category which is always Object.Category.WEAPON! -- This gives the object category which is always Object.Category.WEAPON!
--self.category=WeaponObject:getCategory() --self.category=WeaponObject:getCategory()
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
self.category = self.desc.category 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 self.categoryMissile=self.desc.missileCategory
end end
-- Get type name. -- Get type name.
self.typeName=WeaponObject:getTypeName() or "Unknown Type" self.typeName=WeaponObject:getTypeName() or "Unknown Type"
-- Get name of object. Usually a number like "1234567". -- Get name of object. Usually a number like "1234567".
self.name=WeaponObject:getName() self.name=WeaponObject:getName()
-- Get coaliton of weapon. -- Get coaliton of weapon.
self.coalition=WeaponObject:getCoalition() self.coalition=WeaponObject:getCoalition()
-- Get country of weapon. -- Get country of weapon.
self.country=WeaponObject:getCountry() self.country=WeaponObject:getCountry()
-- Get DCS unit of the launcher. -- Get DCS unit of the launcher.
self.launcher=WeaponObject:getLauncher() self.launcher=WeaponObject:getLauncher()
-- Get launcher of weapon. -- Get launcher of weapon.
self.launcherName="Unknown Launcher" self.launcherName="Unknown Launcher"
if self.launcher then if self.launcher then
self.launcherName=self.launcher:getName() self.launcherName=self.launcher:getName()
self.launcherUnit=UNIT:Find(self.launcher) self.launcherUnit=UNIT:Find(self.launcher)
end end
-- Init the coordinate of the weapon from that of the launcher. -- Init the coordinate of the weapon from that of the launcher.
self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
-- Set log ID. -- Set log ID.
self.lid=string.format("[%s] %s | ", self.typeName, self.name) self.lid=string.format("[%s] %s | ", self.typeName, self.name)
@ -237,12 +237,12 @@ function WEAPON:New(WeaponObject)
-- Set default parameters -- Set default parameters
self:SetTimeStepTrack() self:SetTimeStepTrack()
self:SetDistanceInterceptPoint() self:SetDistanceInterceptPoint()
-- Debug info. -- 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.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName)
self:T(self.lid..text) self:T(self.lid..text)
-- Descriptors. -- Descriptors.
self:T2(self.desc) self:T2(self.desc)
@ -312,13 +312,13 @@ function WEAPON:SetSmokeImpact(Switch, SmokeColor)
else else
self.impactSmoke=true self.impactSmoke=true
end end
self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red
return self return self
end 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. -- Note that this can be called many times per second. So be careful for performance reasons.
-- @param #WEAPON self -- @param #WEAPON self
-- @param #function FuncTrack Function called during tracking. -- @param #function FuncTrack Function called during tracking.
@ -335,19 +335,19 @@ end
-- @param #function FuncImpact Function called once the weapon impacted. -- @param #function FuncImpact Function called once the weapon impacted.
-- @param ... Optional function arguments. -- @param ... Optional function arguments.
-- @return #WEAPON self -- @return #WEAPON self
-- --
-- @usage -- @usage
-- -- Function called on impact. -- -- Function called on impact.
-- local function OnImpact(Weapon) -- local function OnImpact(Weapon)
-- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon") -- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon")
-- end -- end
-- --
-- -- Set which function to call. -- -- Set which function to call.
-- myweapon:SetFuncImpact(OnImpact) -- myweapon:SetFuncImpact(OnImpact)
-- --
-- -- Start tracking. -- -- Start tracking.
-- myweapon:Track() -- myweapon:Track()
-- --
function WEAPON:SetFuncImpact(FuncImpact, ...) function WEAPON:SetFuncImpact(FuncImpact, ...)
self.impactFunc=FuncImpact self.impactFunc=FuncImpact
self.impactArg=arg or {} self.impactArg=arg or {}
@ -368,37 +368,37 @@ end
function WEAPON:GetTarget() function WEAPON:GetTarget()
local target=nil --Wrapper.Object#OBJECT local target=nil --Wrapper.Object#OBJECT
if self.weapon then if self.weapon then
-- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase. -- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase.
local object=self.weapon:getTarget() local object=self.weapon:getTarget()
if object then if object then
-- Get object category. -- Get object category.
local category=Object.getCategory(object) local category=Object.getCategory(object)
--Target name --Target name
local name=object:getName() local name=object:getName()
-- Debug info. -- 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 if category==Object.Category.UNIT then
target=UNIT:FindByName(name) target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then elseif category==Object.Category.STATIC then
target=STATIC:FindByName(name, false) target=STATIC:FindByName(name, false)
elseif category==Object.Category.SCENERY then elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
end end
end end
end end
@ -413,25 +413,25 @@ function WEAPON:GetTargetDistance(ConversionFunction)
-- Get the target of the weapon. -- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT local target=self:GetTarget() --Wrapper.Unit#UNIT
local distance=nil local distance=nil
if target then if target then
-- Current position of target. -- Current position of target.
local tv3=target:GetVec3() local tv3=target:GetVec3()
-- Current position of weapon. -- Current position of weapon.
local wv3=self:GetVec3() local wv3=self:GetVec3()
if tv3 and wv3 then if tv3 and wv3 then
distance=UTILS.VecDist3D(tv3, wv3) distance=UTILS.VecDist3D(tv3, wv3)
if ConversionFunction then if ConversionFunction then
distance=ConversionFunction(distance) distance=ConversionFunction(distance)
end end
end end
end end
return distance return distance
@ -445,10 +445,10 @@ function WEAPON:GetTargetName()
-- Get the target of the weapon. -- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT local target=self:GetTarget() --Wrapper.Unit#UNIT
local name="None" local name="None"
if target then if target then
name=target:GetName() name=target:GetName()
end end
return name return name
@ -476,13 +476,13 @@ function WEAPON:GetSpeed(ConversionFunction)
if self.weapon then if self.weapon then
local v=self:GetVelocityVec3() local v=self:GetVelocityVec3()
speed=UTILS.VecNorm(v) speed=UTILS.VecNorm(v)
if ConversionFunction then if ConversionFunction then
speed=ConversionFunction(speed) speed=ConversionFunction(speed)
end end
end end
return speed return speed
@ -508,11 +508,11 @@ end
function WEAPON:GetVec2() function WEAPON:GetVec2()
local vec3=self:GetVec3() local vec3=self:GetVec3()
if vec3 then if vec3 then
local vec2={x=vec3.x, y=vec3.z} local vec2={x=vec3.x, y=vec3.z}
return vec2 return vec2
end end
@ -521,28 +521,28 @@ end
--- Get type name. --- Get type name.
-- @param #WEAPON self -- @param #WEAPON self
-- @return #string The type name. -- @return #string The type name.
function WEAPON:GetTypeName() function WEAPON:GetTypeName()
return self.typeName return self.typeName
end end
--- Get coalition. --- Get coalition.
-- @param #WEAPON self -- @param #WEAPON self
-- @return #number Coalition ID. -- @return #number Coalition ID.
function WEAPON:GetCoalition() function WEAPON:GetCoalition()
return self.coalition return self.coalition
end end
--- Get country. --- Get country.
-- @param #WEAPON self -- @param #WEAPON self
-- @return #number Country ID. -- @return #number Country ID.
function WEAPON:GetCountry() function WEAPON:GetCountry()
return self.country return self.country
end end
--- Get DCS object. --- Get DCS object.
-- @param #WEAPON self -- @param #WEAPON self
-- @return DCS#Weapon The weapon object. -- @return DCS#Weapon The weapon object.
function WEAPON:GetDCSObject() function WEAPON:GetDCSObject()
-- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE -- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE
return self.weapon return self.weapon
@ -675,23 +675,23 @@ end
function WEAPON:Destroy(Delay) function WEAPON:Destroy(Delay)
if Delay and Delay>0 then if Delay and Delay>0 then
self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0) self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0)
else else
if self.weapon then if self.weapon then
self:T(self.lid.."Destroying Weapon NOW!") self:T(self.lid.."Destroying Weapon NOW!")
self:StopTrack() self:StopTrack()
self.weapon:destroy() self.weapon:destroy()
end end
end end
return self return self
end end
--- Start tracking the weapon until it impacts or is destroyed otherwise. --- 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 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 (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. -- 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. -- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec.
-- @return #WEAPON self -- @return #WEAPON self
function WEAPON:StartTrack(Delay) function WEAPON:StartTrack(Delay)
@ -700,8 +700,8 @@ function WEAPON:StartTrack(Delay)
Delay=math.max(Delay or 0.001, 0.001) Delay=math.max(Delay or 0.001, 0.001)
-- Debug info. -- 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. -- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds.
self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay) self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay)
@ -710,7 +710,7 @@ end
--- Stop tracking the weapon by removing the scheduler function. --- 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. -- @param #number Delay (Optional) Delay in seconds before the tracking is stopped.
-- @return #WEAPON self -- @return #WEAPON self
function WEAPON:StopTrack(Delay) function WEAPON:StopTrack(Delay)
@ -719,13 +719,13 @@ function WEAPON:StopTrack(Delay)
-- Delayed call. -- Delayed call.
self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0) self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0)
else else
if self.trackScheduleID then if self.trackScheduleID then
timer.removeFunction(self.trackScheduleID) timer.removeFunction(self.trackScheduleID)
end end
end end
return self return self
@ -762,10 +762,10 @@ function WEAPON:_TrackWeapon(time)
-- Update last known position. -- Update last known position.
self.pos3 = pos3 self.pos3 = pos3
-- Update last known vec3. -- Update last known vec3.
self.vec3 = UTILS.DeepCopy(self.pos3.p) self.vec3 = UTILS.DeepCopy(self.pos3.p)
-- Update coordinate. -- Update coordinate.
self.coordinate:UpdateFromVec3(self.vec3) self.coordinate:UpdateFromVec3(self.vec3)
@ -774,70 +774,70 @@ function WEAPON:_TrackWeapon(time)
-- Keep on tracking by returning the next time below. -- Keep on tracking by returning the next time below.
self.tracking=true self.tracking=true
-- Callback function. -- Callback function.
if self.trackFunc then if self.trackFunc then
self.trackFunc(self, unpack(self.trackArg)) self.trackFunc(self, unpack(self.trackArg))
end end
-- Verbose output. -- Verbose output.
if self.verbose>=5 then if self.verbose>=5 then
-- Get vec2 of current position. -- Get vec2 of current position.
local vec2={x=self.vec3.x, y=self.vec3.z} local vec2={x=self.vec3.x, y=self.vec3.z}
-- Land hight. -- Land hight.
local height=land.getHeight(vec2) local height=land.getHeight(vec2)
-- Current height above ground level. -- Current height above ground level.
local agl=self.vec3.y-height local agl=self.vec3.y-height
-- Estimated IP (if any) -- Estimated IP (if any)
local ip=self:_GetIP(self.distIP) local ip=self:_GetIP(self.distIP)
-- Distance between positon and estimated impact. -- Distance between positon and estimated impact.
local d=0 local d=0
if ip then if ip then
d=UTILS.VecDist3D(self.vec3, ip) d=UTILS.VecDist3D(self.vec3, ip)
end end
-- Output. -- Output.
self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d)) self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d))
end end
else else
--------------------------- ---------------------------
-- Weapon does NOT exist -- -- Weapon does NOT exist --
--------------------------- ---------------------------
-- Get intercept point from position (p) and direction (x) in 50 meters. -- Get intercept point from position (p) and direction (x) in 50 meters.
local ip = self:_GetIP(self.distIP) local ip = self:_GetIP(self.distIP)
if self.verbose>=10 and ip then if self.verbose>=10 and ip then
-- Output. -- Output.
self:I(self.lid.."Got intercept point!") self:I(self.lid.."Got intercept point!")
-- Coordinate of the impact point. -- Coordinate of the impact point.
local coord=COORDINATE:NewFromVec3(ip) local coord=COORDINATE:NewFromVec3(ip)
-- Mark coordinate. -- Mark coordinate.
coord:MarkToAll("Intercept point") coord:MarkToAll("Intercept point")
coord:SmokeBlue() coord:SmokeBlue()
-- Distance to last known pos. -- Distance to last known pos.
local d=UTILS.VecDist3D(ip, self.vec3) local d=UTILS.VecDist3D(ip, self.vec3)
-- Output. -- Output.
self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d)) self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d))
end end
-- Safe impact vec3. -- Safe impact vec3.
self.impactVec3=ip or self.vec3 self.impactVec3=ip or self.vec3
-- Safe impact coordinate. -- Safe impact coordinate.
self.impactCoord=COORDINATE:NewFromVec3(self.vec3) self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
@ -848,22 +848,22 @@ function WEAPON:_TrackWeapon(time)
if self.impactMark then if self.impactMark then
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName)) self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName))
end end
-- Smoke on impact point. -- Smoke on impact point.
if self.impactSmoke then if self.impactSmoke then
self.impactCoord:Smoke(self.impactSmokeColor) self.impactCoord:Smoke(self.impactSmokeColor)
end end
-- Call callback function. -- Call callback function.
if self.impactFunc then if self.impactFunc then
self.impactFunc(self, unpack(self.impactArg or {})) self.impactFunc(self, unpack(self.impactArg or {}))
end end
-- Stop tracking by returning nil below. -- Stop tracking by returning nil below.
self.tracking=false self.tracking=false
end end
-- Return next time the function is called or nil to stop the scheduler. -- Return next time the function is called or nil to stop the scheduler.
if self.tracking then if self.tracking then
if self.dtTrack and self.dtTrack>=0.00001 then if self.dtTrack and self.dtTrack>=0.00001 then
@ -885,12 +885,12 @@ function WEAPON:_GetIP(Distance)
Distance=Distance or 50 Distance=Distance or 50
local ip=nil --DCS#Vec3 local ip=nil --DCS#Vec3
if Distance>0 and self.pos3 then if Distance>0 and self.pos3 then
-- Get intercept point from position (p) and direction (x) in 20 meters. -- 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 ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3
end end
return ip 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 title: De-Sanitize DCS
parent: Advanced parent: Advanced
nav_order: 2 nav_order: 98
--- ---
# De-Sanitize the DCS scripting environment # De-Sanitize the DCS scripting environment
{: .no_toc } {: .no_toc }

View File

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

View File

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

View File

@ -35,16 +35,24 @@ Please remember when posting a question:
- Before posting anything follow the [troubleshooting steps]. - Before posting anything follow the [troubleshooting steps].
- **Read your logs**. - **Read your logs**.
### Formulate a good description
A post should contain the following: A post should contain the following:
1. A describtion what you expected to happen and what actually happened. - 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. - Do not use vague words this stuff is hard to help with! Be specific.
2. Describe what happens instead. - Describe what happens instead.
- The less detail you offer, the less chance you can be helped. - The less detail you offer, the less chance you can be helped.
- Dont say it doesnt work. Or is it broken. Say what it actually does. - 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: - 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) ![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. - Some complex problems need the mission (.miz file) also.
- But post your mission only when requested. - But post your mission only when requested.
- Try to simplify your mission if it is complex! - Try to simplify your mission if it is complex!
- Try to avoid or delete MODs, because could prevent people from helping you.
There are people in the Discord and in the forum, who spend their free time to There are people in the Discord and in the forum, who spend their free time to
help you. <br /> help you. <br />

View File

@ -11,10 +11,14 @@ nav_order: 05
## Something went wrong ## 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. 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 ## 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 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. 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 1. Open the file `dcs.log` in the `Logs` subfolder in your DCS [Saved Games folder].
[Saved Games folder].
1. Search for the following line: `*** MOOSE INCLUDE END ***` 1. Search for the following line: `*** MOOSE INCLUDE END ***`
- If it is included in the log, Moose was loaded. - 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