mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
commit
1253e241ff
10
.github/workflows/gh-pages.yml
vendored
10
.github/workflows/gh-pages.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: docs/
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v3
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Build with Jekyll
|
||||
# Outputs to the './_site' directory by default
|
||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||
@ -52,7 +52,7 @@ jobs:
|
||||
working-directory: docs/
|
||||
- name: Upload artifact
|
||||
# Automatically uploads an artifact from the './_site' directory by default
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
@ -66,13 +66,13 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy
|
||||
steps:
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
- run: npm install linkinator
|
||||
- run: npx linkinator https://flightcontrol-master.github.io/MOOSE/ --verbosity error --timeout 5000 --recurse --skip "(java.com)" --retry-errors --retry-errors-count 3 --retry-errors-jitter
|
||||
|
||||
@ -1144,6 +1144,19 @@ function BASE:TraceClassMethod( Class, Method )
|
||||
self:I( "Tracing method " .. Method .. " of class " .. Class )
|
||||
end
|
||||
|
||||
--- (Internal) Serialize arguments
|
||||
-- @param #BASE self
|
||||
-- @param #table Arguments
|
||||
-- @return #string Text
|
||||
function BASE:_Serialize(Arguments)
|
||||
local text = UTILS.PrintTableToLog({Arguments}, 0, true)
|
||||
text = string.gsub(text,"\n","")
|
||||
text = string.gsub(text,"%(%(","%(")
|
||||
text = string.gsub(text,"%)%)","%)")
|
||||
text = string.gsub(text,"(%s+)","")
|
||||
return text
|
||||
end
|
||||
|
||||
--- Trace a function call. This function is private.
|
||||
-- @param #BASE self
|
||||
-- @param Arguments A #table or any field.
|
||||
@ -1168,7 +1181,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, BASE:_Serialize(Arguments) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1242,7 +1255,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
|
||||
if DebugInfoFrom then
|
||||
LineFrom = DebugInfoFrom.currentline
|
||||
end
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s", LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1314,7 +1327,7 @@ function BASE:E( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "E", self.ClassName, self.ClassID, BASE:_Serialize(Arguments) ) )
|
||||
end
|
||||
|
||||
end
|
||||
@ -1341,39 +1354,8 @@ function BASE:I( Arguments )
|
||||
|
||||
env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)", LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, UTILS.BasicSerialize( Arguments ) ) )
|
||||
else
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, UTILS.BasicSerialize( Arguments ) ) )
|
||||
env.info( string.format( "%1s:%30s%05d(%s)", "I", self.ClassName, self.ClassID, BASE:_Serialize(Arguments)) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- old stuff
|
||||
|
||||
-- function BASE:_Destructor()
|
||||
-- --self:E("_Destructor")
|
||||
--
|
||||
-- --self:EventRemoveAll()
|
||||
-- end
|
||||
|
||||
-- THIS IS WHY WE NEED LUA 5.2 ...
|
||||
-- function BASE:_SetDestructor()
|
||||
--
|
||||
-- -- TODO: Okay, this is really technical...
|
||||
-- -- When you set a proxy to a table to catch __gc, weak tables don't behave like weak...
|
||||
-- -- Therefore, I am parking this logic until I've properly discussed all this with the community.
|
||||
--
|
||||
-- local proxy = newproxy(true)
|
||||
-- local proxyMeta = getmetatable(proxy)
|
||||
--
|
||||
-- proxyMeta.__gc = function ()
|
||||
-- env.info("In __gc for " .. self:GetClassNameAndID() )
|
||||
-- if self._Destructor then
|
||||
-- self:_Destructor()
|
||||
-- end
|
||||
-- end
|
||||
--
|
||||
-- -- keep the userdata from newproxy reachable until the object
|
||||
-- -- table is about to be garbage-collected - then the __gc hook
|
||||
-- -- will be invoked and the destructor called
|
||||
-- rawset( self, '__proxy', proxy )
|
||||
--
|
||||
-- end
|
||||
|
||||
@ -8,6 +8,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Beacon)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Core.Beacon
|
||||
@ -286,6 +290,7 @@ end
|
||||
-- myBeacon:AATACAN(20, "TEXACO", true) -- Activate the beacon
|
||||
function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
|
||||
self:F({TACANChannel, Message, Bearing, BeaconDuration})
|
||||
self:E("This method is DEPRECATED! Please use ActivateTACAN() instead.")
|
||||
|
||||
local IsValid = true
|
||||
|
||||
|
||||
@ -37,6 +37,8 @@
|
||||
-- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID.
|
||||
-- @field #table CLIENTS Clients.
|
||||
-- @field #table STORAGES DCS warehouse storages.
|
||||
-- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes.
|
||||
-- @field #table SADL Used Link16 octal numbers for A10/C-II planes.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator.
|
||||
@ -93,6 +95,8 @@ DATABASE = {
|
||||
OPSZONES = {},
|
||||
PATHLINES = {},
|
||||
STORAGES = {},
|
||||
STNS={},
|
||||
SADL={},
|
||||
}
|
||||
|
||||
local _DATABASECoalition =
|
||||
@ -928,7 +932,7 @@ function DATABASE:Spawn( SpawnTemplate )
|
||||
SpawnTemplate.CountryID = nil
|
||||
SpawnTemplate.CategoryID = nil
|
||||
|
||||
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID )
|
||||
self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name )
|
||||
|
||||
self:T3( SpawnTemplate )
|
||||
coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
|
||||
@ -1030,6 +1034,27 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
|
||||
end
|
||||
|
||||
if UnitTemplate.AddPropAircraft then
|
||||
if UnitTemplate.AddPropAircraft.STN_L16 then
|
||||
local stn = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16)
|
||||
if stn == nil or stn < 1 then
|
||||
self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
|
||||
else
|
||||
self.STNS[stn] = UnitTemplate.name
|
||||
self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name)
|
||||
end
|
||||
end
|
||||
if UnitTemplate.AddPropAircraft.SADL_TN then
|
||||
local sadl = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN)
|
||||
if sadl == nil or sadl < 1 then
|
||||
self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
|
||||
else
|
||||
self.SADL[sadl] = UnitTemplate.name
|
||||
self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
|
||||
end
|
||||
|
||||
@ -1043,6 +1068,80 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
|
||||
)
|
||||
end
|
||||
|
||||
--- Get next (consecutive) free STN as octal number.
|
||||
-- @param #DATABASE self
|
||||
-- @param #number octal Starting octal.
|
||||
-- @param #string unitname Name of the associated unit.
|
||||
-- @return #number Octal
|
||||
function DATABASE:GetNextSTN(octal,unitname)
|
||||
local first = UTILS.OctalToDecimal(octal)
|
||||
if self.STNS[first] == unitname then return octal end
|
||||
local nextoctal = 77777
|
||||
local found = false
|
||||
if 32767-first < 10 then
|
||||
first = 0
|
||||
end
|
||||
for i=first+1,32767 do
|
||||
if self.STNS[i] == nil then
|
||||
found = true
|
||||
nextoctal = UTILS.DecimalToOctal(i)
|
||||
self.STNS[i] = unitname
|
||||
self:T("Register STN "..tostring(nextoctal).." for ".. unitname)
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
self:E(string.format("WARNING: No next free STN past %05d found!",octal))
|
||||
-- cleanup
|
||||
local NewSTNS = {}
|
||||
for _id,_name in pairs(self.STNS) do
|
||||
if self.UNITS[_name] ~= nil then
|
||||
NewSTNS[_id] = _name
|
||||
end
|
||||
end
|
||||
self.STNS = nil
|
||||
self.STNS = NewSTNS
|
||||
end
|
||||
return nextoctal
|
||||
end
|
||||
|
||||
--- Get next (consecutive) free SADL as octal number.
|
||||
-- @param #DATABASE self
|
||||
-- @param #number octal Starting octal.
|
||||
-- @param #string unitname Name of the associated unit.
|
||||
-- @return #number Octal
|
||||
function DATABASE:GetNextSADL(octal,unitname)
|
||||
local first = UTILS.OctalToDecimal(octal)
|
||||
if self.SADL[first] == unitname then return octal end
|
||||
local nextoctal = 7777
|
||||
local found = false
|
||||
if 4095-first < 10 then
|
||||
first = 0
|
||||
end
|
||||
for i=first+1,4095 do
|
||||
if self.STNS[i] == nil then
|
||||
found = true
|
||||
nextoctal = UTILS.DecimalToOctal(i)
|
||||
self.SADL[i] = unitname
|
||||
self:T("Register SADL "..tostring(nextoctal).." for ".. unitname)
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
self:E(string.format("WARNING: No next free SADL past %04d found!",octal))
|
||||
-- cleanup
|
||||
local NewSTNS = {}
|
||||
for _id,_name in pairs(self.SADL) do
|
||||
if self.UNITS[_name] ~= nil then
|
||||
NewSTNS[_id] = _name
|
||||
end
|
||||
end
|
||||
self.SADL = nil
|
||||
self.SADL = NewSTNS
|
||||
end
|
||||
return nextoctal
|
||||
end
|
||||
|
||||
--- Get group template.
|
||||
-- @param #DATABASE self
|
||||
-- @param #string GroupName Group name.
|
||||
|
||||
@ -1378,6 +1378,7 @@ function EVENT:onEvent( Event )
|
||||
Event.MarkCoordinate=COORDINATE:NewFromVec3(Event.pos)
|
||||
Event.MarkText=Event.text
|
||||
Event.MarkCoalition=Event.coalition
|
||||
Event.IniCoalition=Event.coalition
|
||||
Event.MarkGroupID = Event.groupID
|
||||
end
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
-- ### Author: **Applevangelist**
|
||||
--
|
||||
-- Date: 5 May 2021
|
||||
-- Last Update: Feb 2023
|
||||
-- Last Update: Mar 2023
|
||||
--
|
||||
-- ===
|
||||
---
|
||||
@ -50,7 +50,7 @@ MARKEROPS_BASE = {
|
||||
ClassName = "MARKEROPS",
|
||||
Tag = "mytag",
|
||||
Keywords = {},
|
||||
version = "0.1.1",
|
||||
version = "0.1.3",
|
||||
debug = false,
|
||||
Casesensitive = true,
|
||||
}
|
||||
@ -114,6 +114,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
|
||||
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
|
||||
@ -124,7 +126,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
-- @param #number idx DCS Marker ID
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
|
||||
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
|
||||
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
|
||||
@ -155,29 +158,30 @@ function MARKEROPS_BASE:OnEventMark(Event)
|
||||
local text = tostring(Event.text)
|
||||
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
|
||||
end
|
||||
local coalition = Event.MarkCoalition
|
||||
-- decision
|
||||
if Event.id==world.event.S_EVENT_MARK_ADDED then
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_ADDED", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
-- Handle event
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkAdded(Eventtext,matchtable,coord)
|
||||
self:MarkAdded(Eventtext,matchtable,coord,Event.idx,coalition)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_CHANGE", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
-- Handle event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
if self:_MatchTag(Eventtext) then
|
||||
local matchtable = self:_MatchKeywords(Eventtext)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
|
||||
self:MarkChanged(Eventtext,matchtable,coord,Event.idx,coalition)
|
||||
end
|
||||
end
|
||||
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
|
||||
self:T({event="S_EVENT_MARK_REMOVED", carrier=Event.IniGroupName, vec3=Event.pos})
|
||||
-- Hande event.
|
||||
local Eventtext = tostring(Event.text)
|
||||
if Eventtext~=nil then
|
||||
@ -230,8 +234,10 @@ end
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
|
||||
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
@ -242,8 +248,10 @@ end
|
||||
-- @param #string To The To state
|
||||
-- @param #string Text The text on the marker
|
||||
-- @param #table Keywords Table of matching keywords found in the Event text
|
||||
-- @param #number MarkerID Id of this marker
|
||||
-- @param #number CoalitionNumber Coalition of the marker creator
|
||||
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
|
||||
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord,MarkerID,CoalitionNumber)
|
||||
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
|
||||
end
|
||||
|
||||
|
||||
@ -1097,6 +1097,7 @@ do
|
||||
GroupPrefixes = nil,
|
||||
Zones = nil,
|
||||
Functions = nil,
|
||||
Alive = nil,
|
||||
},
|
||||
FilterMeta = {
|
||||
Coalitions = {
|
||||
@ -1470,7 +1471,7 @@ do
|
||||
|
||||
end
|
||||
|
||||
--- Builds a set of groups that are only active.
|
||||
--- Builds a set of groups that are active, ie in the mission but not yet activated (false) or actived (true).
|
||||
-- Only the groups that are active will be included within the set.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #boolean Active (Optional) Include only active groups to the set.
|
||||
@ -1496,6 +1497,14 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- Build a set of groups that are alive.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterAlive()
|
||||
self.Filter.Alive = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- Starts the filtering.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #SET_GROUP self
|
||||
@ -1994,6 +2003,15 @@ do
|
||||
self:F2( MGroup )
|
||||
local MGroupInclude = true
|
||||
|
||||
if self.Filter.Alive == true then
|
||||
local MGroupAlive = false
|
||||
self:F( { Active = self.Filter.Active } )
|
||||
if MGroup and MGroup:IsAlive() then
|
||||
MGroupAlive = true
|
||||
end
|
||||
MGroupInclude = MGroupInclude and MGroupAlive
|
||||
end
|
||||
|
||||
if self.Filter.Active ~= nil then
|
||||
local MGroupActive = false
|
||||
self:F( { Active = self.Filter.Active } )
|
||||
@ -2997,7 +3015,7 @@ do -- SET_UNIT
|
||||
local velocity = self:GetVelocity() or 0
|
||||
Coordinate:SetHeading( heading )
|
||||
Coordinate:SetVelocity( velocity )
|
||||
self:I(UTILS.PrintTableToLog(Coordinate))
|
||||
self:T(UTILS.PrintTableToLog(Coordinate))
|
||||
end
|
||||
|
||||
return Coordinate
|
||||
@ -4521,7 +4539,7 @@ do -- SET_CLIENT
|
||||
if Event.IniObjectCategory == Object.Category.UNIT and Event.IniGroup and Event.IniGroup:IsGround() then
|
||||
-- CA Slot entered
|
||||
local ObjectName, Object = self:AddInDatabase( Event )
|
||||
self:I( ObjectName, UTILS.PrintTableToLog(Object) )
|
||||
self:T( ObjectName, UTILS.PrintTableToLog(Object) )
|
||||
if Object and self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
end
|
||||
|
||||
@ -200,6 +200,22 @@
|
||||
-- * @{#SPAWN.InitRepeat}() or @{#SPAWN.InitRepeatOnLanding}(): This method is used to re-spawn automatically the same group after it has landed.
|
||||
-- * @{#SPAWN.InitRepeatOnEngineShutDown}(): This method is used to re-spawn automatically the same group after it has landed and it shuts down the engines at the ramp.
|
||||
--
|
||||
-- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only)
|
||||
--
|
||||
-- *{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
|
||||
-- *{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
|
||||
--
|
||||
-- ### Callsigns
|
||||
--
|
||||
-- *{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
|
||||
-- *{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
|
||||
--
|
||||
-- ### Speed
|
||||
--
|
||||
-- *{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
|
||||
-- *{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
|
||||
-- *{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
|
||||
--
|
||||
-- ## SPAWN **Spawn** methods
|
||||
--
|
||||
-- Groups can be spawned at different times and methods:
|
||||
@ -520,7 +536,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
|
||||
end
|
||||
|
||||
if SpawnTemplate then
|
||||
self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
|
||||
self.SpawnTemplate = UTILS.DeepCopy(SpawnTemplate) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
|
||||
self.SpawnTemplatePrefix = SpawnTemplatePrefix
|
||||
self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix
|
||||
self.SpawnTemplate.name = SpawnTemplatePrefix
|
||||
@ -724,7 +740,7 @@ end
|
||||
-- @param #number Country Country id as number or enumerator:
|
||||
--
|
||||
-- * @{DCS#country.id.RUSSIA}
|
||||
-- * @{DCS#county.id.USA}
|
||||
-- * @{DCS#country.id.USA}
|
||||
--
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitCountry( Country )
|
||||
@ -780,6 +796,82 @@ function SPAWN:InitSkill( Skill )
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Airplane - F15/16/18/AWACS/B1B/Tanker only] Set the STN Link16 starting number of the Group; each unit of the spawned group will have a consecutive STN set.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number Octal The octal number (digits 1..7, max 5 digits, i.e. 1..77777) to set the STN to. Every STN needs to be unique!
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitSTN(Octal)
|
||||
self:F( { Octal = Octal } )
|
||||
self.SpawnInitSTN = Octal or 77777
|
||||
local num = UTILS.OctalToDecimal(Octal)
|
||||
if num == nil or num < 1 then
|
||||
self:E("WARNING - STN "..tostring(Octal).." is not valid!")
|
||||
return self
|
||||
end
|
||||
if _DATABASE.STNS[num] ~= nil then
|
||||
self:E("WARNING - STN already assigned: "..tostring(Octal).." is used for ".._DATABASE.STNS[Octal])
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Airplane - A10-C II only] Set the SADL TN starting number of the Group; each unit of the spawned group will have a consecutive SADL set.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number Octal The octal number (digits 1..7, max 4 digits, i.e. 1..7777) to set the SADL to. Every SADL needs to be unique!
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitSADL(Octal)
|
||||
self:F( { Octal = Octal } )
|
||||
self.SpawnInitSADL = Octal or 7777
|
||||
local num = UTILS.OctalToDecimal(Octal)
|
||||
if num == nil or num < 1 then
|
||||
self:E("WARNING - SADL "..tostring(Octal).." is not valid!")
|
||||
return self
|
||||
end
|
||||
if _DATABASE.SADL[num] ~= nil then
|
||||
self:E("WARNING - SADL already assigned: "..tostring(Octal).." is used for ".._DATABASE.SADL[Octal])
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Airplane] Set the initial speed on spawning in meters per second. Useful when spawning in-air only.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number MPS The speed in MPS to use.
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitSpeedMps(MPS)
|
||||
self:F( { MPS = MPS } )
|
||||
if MPS == nil or tonumber(MPS)<0 then
|
||||
MPS=125
|
||||
end
|
||||
self.InitSpeed = MPS
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Airplane] Set the initial speed on spawning in knots. Useful when spawning in-air only.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number Knots The speed in knots to use.
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitSpeedKnots(Knots)
|
||||
self:F( { Knots = Knots } )
|
||||
if Knots == nil or tonumber(Knots)<0 then
|
||||
Knots=300
|
||||
end
|
||||
self.InitSpeed = UTILS.KnotsToMps(Knots)
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Airplane] Set the initial speed on spawning in kilometers per hour. Useful when spawning in-air only.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number KPH The speed in KPH to use.
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitSpeedKph(KPH)
|
||||
self:F( { KPH = KPH } )
|
||||
if KPH == nil or tonumber(KPH)<0 then
|
||||
KPH=UTILS.KnotsToKmph(300)
|
||||
end
|
||||
self.InitSpeed = UTILS.KmphToMps(KPH)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Sets the radio communication on or off. Same as checking/unchecking the COMM box in the mission editor.
|
||||
-- @param #SPAWN self
|
||||
-- @param #number switch If true (or nil), enables the radio communication. If false, disables the radio for the spawned group.
|
||||
@ -1699,6 +1791,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
end
|
||||
|
||||
self.SpawnGroups[self.SpawnIndex].Spawned = true
|
||||
self.SpawnGroups[self.SpawnIndex].Group.TemplateDonor = self.SpawnTemplatePrefix
|
||||
return self.SpawnGroups[self.SpawnIndex].Group
|
||||
else
|
||||
-- self:E( { self.SpawnTemplatePrefix, "No more Groups to Spawn:", SpawnIndex, self.SpawnMaxGroups } )
|
||||
@ -3311,9 +3404,17 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
end
|
||||
else
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
local UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
|
||||
local SpawnInitKeepUnitIFF = false
|
||||
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
|
||||
SpawnInitKeepUnitIFF = true
|
||||
end
|
||||
local UnitPrefix, Rest
|
||||
if SpawnInitKeepUnitIFF == false then
|
||||
UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
|
||||
self:T( { UnitPrefix, Rest } )
|
||||
|
||||
else
|
||||
UnitPrefix=SpawnTemplate.units[UnitID].name
|
||||
end
|
||||
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
|
||||
SpawnTemplate.units[UnitID].unitId = nil
|
||||
end
|
||||
@ -3395,36 +3496,58 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex
|
||||
end
|
||||
end
|
||||
-- Speed
|
||||
if self.InitSpeed then
|
||||
SpawnTemplate.units[UnitID].speed = self.InitSpeed
|
||||
end
|
||||
-- Link16
|
||||
local AddProps = SpawnTemplate.units[UnitID].AddPropAircraft
|
||||
if AddProps then
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 then
|
||||
-- 4 digit octal with leading 0
|
||||
if self.SpawnInitSTN then
|
||||
local octal = self.SpawnInitSTN
|
||||
if UnitID > 1 then
|
||||
octal = _DATABASE:GetNextSTN(self.SpawnInitSTN,SpawnTemplate.units[UnitID].name)
|
||||
end
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",octal)
|
||||
else
|
||||
-- 5 digit octal with leading 0
|
||||
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16) ~= nil then
|
||||
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16
|
||||
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",UTILS.DecimalToOctal(decimal))
|
||||
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 STN = math.floor(UTILS.RandomGaussian(4088/2,nil,1000,4088))
|
||||
STN = STN+UnitID-1
|
||||
local OSTN = UTILS.DecimalToOctal(STN)
|
||||
local OSTN = _DATABASE:GetNextSTN(1,SpawnTemplate.units[UnitID].name)
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.STN_L16 = string.format("%05d",OSTN)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- A10CII
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN then
|
||||
-- 3 digit octal with leading 0
|
||||
-- 4 digit octal with leading 0
|
||||
if self.SpawnInitSADL then
|
||||
local octal = self.SpawnInitSADL
|
||||
if UnitID > 1 then
|
||||
octal = _DATABASE:GetNextSADL(self.SpawnInitSADL,SpawnTemplate.units[UnitID].name)
|
||||
end
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",octal)
|
||||
else
|
||||
if tonumber(SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN) ~= nil then
|
||||
local octal = SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN
|
||||
local decimal = UTILS.OctalToDecimal(octal)+UnitID-1
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",UTILS.DecimalToOctal(decimal))
|
||||
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 STN = math.floor(UTILS.RandomGaussian(504/2,nil,100,504))
|
||||
STN = STN+UnitID-1
|
||||
local OSTN = UTILS.DecimalToOctal(STN)
|
||||
local OSTN = _DATABASE:GetNextSADL(1,SpawnTemplate.units[UnitID].name)
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.SADL_TN = string.format("%04d",OSTN)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- VoiceCallsignNumber
|
||||
if SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber and type( Callsign ) ~= "number" then
|
||||
SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignNumber = SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Core/SpawnStatic)
|
||||
-- ## [SPAWNSTATIC Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/SpawnStatic)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
|
||||
@ -46,6 +46,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Core/Zone)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit**
|
||||
--
|
||||
@ -326,14 +330,14 @@ function ZONE_BASE:GetRandomVec2()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone.
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
|
||||
function ZONE_BASE:GetRandomPointVec2()
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Define a random @{Core.Point#POINT_VEC3} within the zone.
|
||||
--- Define a random @{Core.Point#POINT_VEC3} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table.
|
||||
-- @param #ZONE_BASE self
|
||||
-- @return Core.Point#POINT_VEC3 The PointVec3 coordinates.
|
||||
function ZONE_BASE:GetRandomPointVec3()
|
||||
@ -899,6 +903,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
|
||||
|
||||
local Point = {}
|
||||
local Vec2 = self:GetVec2()
|
||||
local countryID = CountryID or country.id.USA
|
||||
|
||||
Points = Points and Points or 360
|
||||
|
||||
@ -910,7 +915,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
|
||||
Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius()
|
||||
Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius()
|
||||
|
||||
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
|
||||
local CountryName = _DATABASE.COUNTRY_NAME[countryID]
|
||||
|
||||
local Tire = {
|
||||
["country"] = CountryName,
|
||||
@ -925,7 +930,7 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound )
|
||||
["heading"] = 0,
|
||||
} -- end of ["group"]
|
||||
|
||||
local Group = coalition.addStaticObject( CountryID, Tire )
|
||||
local Group = coalition.addStaticObject( countryID, Tire )
|
||||
if UnBound and UnBound == true then
|
||||
Group:destroy()
|
||||
end
|
||||
@ -1175,7 +1180,7 @@ function ZONE_RADIUS:RemoveJunk()
|
||||
return n
|
||||
end
|
||||
|
||||
--- Count the number of different coalitions inside the zone.
|
||||
--- Get a table of scanned units.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return #table Table of DCS units and DCS statics inside the zone.
|
||||
function ZONE_RADIUS:GetScannedUnits()
|
||||
@ -1210,7 +1215,7 @@ function ZONE_RADIUS:GetScannedSetUnit()
|
||||
return SetUnit
|
||||
end
|
||||
|
||||
--- Get a set of scanned units.
|
||||
--- Get a set of scanned groups.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @return Core.Set#SET_GROUP Set of groups.
|
||||
function ZONE_RADIUS:GetScannedSetGroup()
|
||||
@ -1510,7 +1515,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
|
||||
return point
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
@ -1541,7 +1546,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer )
|
||||
end
|
||||
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone.
|
||||
--- Returns a @{Core.Point#POINT_VEC3} object reflecting a random 3D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table.
|
||||
-- @param #ZONE_RADIUS self
|
||||
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
@ -1985,7 +1990,7 @@ function ZONE_GROUP:GetRandomVec2()
|
||||
return Point
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_GROUP self
|
||||
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
@ -2829,7 +2834,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2()
|
||||
end
|
||||
end
|
||||
|
||||
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone.
|
||||
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return @{Core.Point#POINT_VEC2}
|
||||
function ZONE_POLYGON_BASE:GetRandomPointVec2()
|
||||
@ -2842,7 +2847,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2()
|
||||
return PointVec2
|
||||
end
|
||||
|
||||
--- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone.
|
||||
--- Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table.
|
||||
-- @param #ZONE_POLYGON_BASE self
|
||||
-- @return @{Core.Point#POINT_VEC3}
|
||||
function ZONE_POLYGON_BASE:GetRandomPointVec3()
|
||||
@ -3830,18 +3835,18 @@ function ZONE_OVAL:GetRandomVec2()
|
||||
return {x=rx, y=ry}
|
||||
end
|
||||
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone.
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_OVAL self
|
||||
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
|
||||
function ZONE_OVAL:GetRandomPointVec2()
|
||||
return POINT_VEC2:NewFromVec2(self:GetRandomVec2())
|
||||
end
|
||||
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone.
|
||||
--- Define a random @{Core.Point#POINT_VEC2} within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec3 table.
|
||||
-- @param #ZONE_OVAL self
|
||||
-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates.
|
||||
function ZONE_OVAL:GetRandomPointVec3()
|
||||
return POINT_VEC2:NewFromVec3(self:GetRandomVec2())
|
||||
return POINT_VEC3:NewFromVec3(self:GetRandomVec2())
|
||||
end
|
||||
|
||||
--- Draw the zone on the F10 map.
|
||||
@ -3981,7 +3986,7 @@ do -- ZONE_AIRBASE
|
||||
return ZoneVec2
|
||||
end
|
||||
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
|
||||
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. Note that this is actually a @{Core.Point#COORDINATE} type object, and not a simple Vec2 table.
|
||||
-- @param #ZONE_AIRBASE self
|
||||
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
|
||||
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Functional/FOX)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **funkyfranky**
|
||||
-- @module Functional.Fox
|
||||
-- @image Functional_FOX.png
|
||||
@ -437,7 +441,7 @@ function FOX:SetProtectedGroupSet(groupset)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a group to the protected set.
|
||||
--- Add a group to the protected set. Works only with AI!
|
||||
-- @param #FOX self
|
||||
-- @param Wrapper.Group#GROUP group Protected group.
|
||||
-- @return #FOX self
|
||||
|
||||
@ -79,6 +79,7 @@
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Applevangelist**: Additional functionality, fixes.
|
||||
-- * **Wingthor (TAW)**: Testing & Advice.
|
||||
-- * **Dutch-Baron (TAW)**: Testing & Advice.
|
||||
-- * **Whisper**: Testing and Advice.
|
||||
@ -116,11 +117,13 @@
|
||||
-- Special targets can be set that will give extra scores to the players when these are destroyed.
|
||||
-- Use the methods @{#SCORING.AddUnitScore}() and @{#SCORING.RemoveUnitScore}() to specify a special additional score for a specific @{Wrapper.Unit}s.
|
||||
-- Use the methods @{#SCORING.AddStaticScore}() and @{#SCORING.RemoveStaticScore}() to specify a special additional score for a specific @{Wrapper.Static}s.
|
||||
-- Use the method @{#SCORING.SetGroupGroup}() to specify a special additional score for a specific @{Wrapper.Group}s.
|
||||
-- Use the method @{#SCORING.AddScoreSetGroup}() to specify a special additional score for a specific @{Wrapper.Group}s gathered in a @{Core.Set#SET_GROUP}.
|
||||
--
|
||||
-- local Scoring = SCORING:New( "Scoring File" )
|
||||
-- Scoring:AddUnitScore( UNIT:FindByName( "Unit #001" ), 200 )
|
||||
-- Scoring:AddStaticScore( STATIC:FindByName( "Static #1" ), 100 )
|
||||
-- local GroupSet = SET_GROUP:New():FilterPrefixes("RAT"):FilterStart()
|
||||
-- Scoring:AddScoreSetGroup( GroupSet, 100)
|
||||
--
|
||||
-- The above grants an additional score of 200 points for Unit #001 and an additional 100 points of Static #1 if these are destroyed.
|
||||
-- Note that later in the mission, one can remove these scores set, for example, when the a goal achievement time limit is over.
|
||||
@ -226,7 +229,7 @@ SCORING = {
|
||||
ClassID = 0,
|
||||
Players = {},
|
||||
AutoSave = true,
|
||||
version = "1.17.1"
|
||||
version = "1.18.2"
|
||||
}
|
||||
|
||||
local _SCORINGCoalition = {
|
||||
@ -428,6 +431,31 @@ function SCORING:AddScoreGroup( ScoreGroup, Score )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Specify a special additional score for a @{Core.Set#SET_GROUP}.
|
||||
-- @param #SCORING self
|
||||
-- @param Core.Set#SET_GROUP Set The @{Core.Set#SET_GROUP} for which each @{Wrapper.Unit} in each Group a Score is given.
|
||||
-- @param #number Score The Score value.
|
||||
-- @return #SCORING
|
||||
function SCORING:AddScoreSetGroup(Set, Score)
|
||||
local set = Set:GetSetObjects()
|
||||
|
||||
for _,_group in pairs (set) do
|
||||
if _group and _group:IsAlive() then
|
||||
self:AddScoreGroup(_group,Score)
|
||||
end
|
||||
end
|
||||
|
||||
local function AddScore(group)
|
||||
self:AddScoreGroup(group,Score)
|
||||
end
|
||||
|
||||
function Set:OnAfterAdded(From,Event,To,ObjectName,Object)
|
||||
AddScore(Object)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add a @{Core.Zone} to define additional scoring when any object is destroyed in that zone.
|
||||
-- Note that if a @{Core.Zone} with the same name is already within the scoring added, the @{Core.Zone} (type) and Score will be replaced!
|
||||
-- This allows for a dynamic destruction zone evolution within your mission.
|
||||
@ -1030,7 +1058,7 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
@ -1141,7 +1169,7 @@ function SCORING:_EventOnHit( Event )
|
||||
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
|
||||
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
|
||||
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
|
||||
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
|
||||
-- After an instant kill we can't compute the threat level anymore. To fix this we compute at OnEventBirth
|
||||
if PlayerHit.UNIT.ThreatType == nil then
|
||||
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
|
||||
-- if this fails for some reason, set a good default value
|
||||
@ -1288,17 +1316,17 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
|
||||
|
||||
|
||||
self:OnKillPvP(Player, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
--self:OnKillPvP(PlayerName, TargetPlayerName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
|
||||
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
|
||||
self:OnKillPvP(Player, TargetPlayerName, true)
|
||||
self:OnKillPvP(PlayerName, TargetPlayerName, true)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
self:OnKillPvE(Player, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
self:OnKillPvE(PlayerName, TargetUnitName, true, TargetThreatLevel, Player.ThreatLevel, ThreatPenalty)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
@ -1326,14 +1354,14 @@ function SCORING:_EventOnDeadOrCrash( Event )
|
||||
else
|
||||
Player.PlayerKills = 1
|
||||
end
|
||||
self:OnKillPvP(Player, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
self:OnKillPvP(PlayerName, TargetPlayerName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
|
||||
else
|
||||
self:OnKillPvE(Player, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
self:OnKillPvE(PlayerName, TargetUnitName, false, TargetThreatLevel, Player.ThreatLevel, ThreatScore)
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
|
||||
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Information )
|
||||
@ -1935,23 +1963,23 @@ end
|
||||
|
||||
--- Handles the event when one player kill another player
|
||||
-- @param #SCORING self
|
||||
-- @param #PLAYER Player the ataching player
|
||||
-- @param #string TargetPlayerName the name of the killed player
|
||||
-- @param #bool IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Thread level of the target
|
||||
-- @param #number PlayerThreatLevelThread level of the player
|
||||
-- @param #string PlayerName The attacking player
|
||||
-- @param #string TargetPlayerName The name of the killed player
|
||||
-- @param #boolean IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Threat level of the target
|
||||
-- @param #number PlayerThreatLevel Threat level of the player
|
||||
-- @param #number Score The score based on both threat levels
|
||||
function SCORING:OnKillPvP(Player, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
function SCORING:OnKillPvP(PlayerName, TargetPlayerName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
|
||||
end
|
||||
--- Handles the event when one player kill another player
|
||||
-- @param #SCORING self
|
||||
-- @param #PLAYER Player the ataching player
|
||||
-- @param #string PlayerName The attacking player
|
||||
-- @param #string TargetUnitName the name of the killed unit
|
||||
-- @param #bool IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Thread level of the target
|
||||
-- @param #number PlayerThreatLevelThread level of the player
|
||||
-- @param #boolean IsTeamKill true if this kill was a team kill
|
||||
-- @param #number TargetThreatLevel Threat level of the target
|
||||
-- @param #number PlayerThreatLevel Threat level of the player
|
||||
-- @param #number Score The score based on both threat levels
|
||||
function SCORING:OnKillPvE(Player, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
function SCORING:OnKillPvE(PlayerName, TargetUnitName, IsTeamKill, TargetThreatLevel, PlayerThreatLevel, Score)
|
||||
|
||||
end
|
||||
@ -320,9 +320,6 @@ function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADG
|
||||
end
|
||||
|
||||
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
|
||||
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtgrp = seadset:GetRandom()
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
|
||||
@ -41,6 +41,7 @@
|
||||
-- @field #boolean usebudget
|
||||
-- @field #number CaptureUnits
|
||||
-- @field #number CaptureThreatlevel
|
||||
-- @field #boolean ExcludeShips
|
||||
-- @extends Core.Base#BASE
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
@ -176,7 +177,7 @@ STRATEGO = {
|
||||
debug = false,
|
||||
drawzone = false,
|
||||
markzone = false,
|
||||
version = "0.2.4",
|
||||
version = "0.2.5",
|
||||
portweight = 3,
|
||||
POIweight = 1,
|
||||
maxrunways = 3,
|
||||
@ -195,6 +196,7 @@ STRATEGO = {
|
||||
usebudget = false,
|
||||
CaptureUnits = 3,
|
||||
CaptureThreatlevel = 1,
|
||||
ExcludeShips = true,
|
||||
}
|
||||
|
||||
---
|
||||
@ -256,6 +258,7 @@ function STRATEGO:New(Name,Coalition,MaxDist)
|
||||
self.maxdist = MaxDist or 150 -- km
|
||||
self.disttable = {}
|
||||
self.routexists = {}
|
||||
self.ExcludeShips = true
|
||||
|
||||
self.lid = string.format("STRATEGO %s %s | ",self.name,self.version)
|
||||
|
||||
@ -427,6 +430,7 @@ function STRATEGO:AnalyseBases()
|
||||
self.bases:ForEach(
|
||||
function(afb)
|
||||
local ab = afb -- Wrapper.Airbase#AIRBASE
|
||||
if self.ExcludeShips and ab:IsShip() then return end
|
||||
local abname = ab:GetName()
|
||||
local runways = ab:GetRunways()
|
||||
local numrwys = #runways
|
||||
@ -438,14 +442,14 @@ function STRATEGO:AnalyseBases()
|
||||
local coa = ab:GetCoalition()
|
||||
if coa == nil then return end -- Spawned FARPS issue - these have no tangible data
|
||||
coa = coa+1
|
||||
local abtype = "AIRBASE"
|
||||
local abtype = STRATEGO.Type.AIRBASE
|
||||
if ab:IsShip() then
|
||||
numrwys = 1
|
||||
abtype = "SHIP"
|
||||
abtype = STRATEGO.Type.SHIP
|
||||
end
|
||||
if ab:IsHelipad() then
|
||||
numrwys = 1
|
||||
abtype = "FARP"
|
||||
abtype = STRATEGO.Type.FARP
|
||||
end
|
||||
local coord = ab:GetCoordinate()
|
||||
if debug then
|
||||
@ -481,10 +485,10 @@ function STRATEGO:UpdateNodeCoalitions()
|
||||
local newtable = {}
|
||||
for _id,_data in pairs(self.airbasetable) do
|
||||
local data = _data -- #STRATEGO.Data
|
||||
if data.type == "AIRBASE" or data.type == "FARP" then
|
||||
data.coalition = AIRBASE:FindByName(data.name):GetCoalition()
|
||||
if data.type == STRATEGO.Type.AIRBASE or data.type == STRATEGO.Type.FARP or data.type == STRATEGO.Type.SHIP then
|
||||
data.coalition = AIRBASE:FindByName(data.name):GetCoalition() or 0
|
||||
else
|
||||
data.coalition = data.opszone:GetOwner()
|
||||
data.coalition = data.opszone:GetOwner() or 0
|
||||
end
|
||||
newtable[_id] = _data
|
||||
end
|
||||
@ -937,11 +941,13 @@ function STRATEGO:FindClosestConsolidationTarget(Startpoint,BaseWeight)
|
||||
local cname = self.easynames[tname]
|
||||
local targetweight = self.airbasetable[cname].baseweight
|
||||
coa = self.airbasetable[cname].coalition
|
||||
--self:T("Start -> End: "..startpoint.." -> "..cname)
|
||||
if (dist < shortest) and (coa ~= self.coalition) and (BaseWeight >= targetweight) then
|
||||
self:T("Found Consolidation Target: "..cname)
|
||||
shortest = dist
|
||||
target = cname
|
||||
weight = self.airbasetable[cname].weight
|
||||
coa = self.airbasetable[cname].coalition
|
||||
coa = coa
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -974,8 +980,9 @@ function STRATEGO:FindClosestStrategicTarget(Startpoint,Weight)
|
||||
local coa = self.airbasetable[cname].coalition
|
||||
local tweight = self.airbasetable[cname].baseweight
|
||||
local ttweight = self.airbasetable[cname].weight
|
||||
self:T("Start -> End: "..startpoint.." -> "..cname)
|
||||
--self:T("Start -> End: "..startpoint.." -> "..cname)
|
||||
if (dist < shortest) and (coa ~= self.coalition) and (tweight >= Weight) then
|
||||
self:T("Found Strategic Target: "..cname)
|
||||
shortest = dist
|
||||
target = cname
|
||||
weight = self.airbasetable[cname].weight
|
||||
@ -996,38 +1003,31 @@ function STRATEGO:FindStrategicTargets()
|
||||
local data = _data -- #STRATEGO.Data
|
||||
if data.coalition == self.coalition then
|
||||
local dist, name, points, coa = self:FindClosestStrategicTarget(data.name,data.weight)
|
||||
if coa == coalition.side.NEUTRAL and points ~= 0 then
|
||||
local fpoints = points + self.NeutralBenefit
|
||||
local tries = 1
|
||||
while targets[fpoints] or tries < 100 do
|
||||
fpoints = points + (self.NeutralBenefit+math.random(1,100))
|
||||
tries = tries + 1
|
||||
end
|
||||
targets[fpoints] = {
|
||||
name = name,
|
||||
dist = dist,
|
||||
points = fpoints,
|
||||
coalition = coa,
|
||||
coalitionname = UTILS.GetCoalitionName(coa),
|
||||
coordinate = self.airbasetable[name].coord,
|
||||
}
|
||||
if points > 0 then
|
||||
self:T({dist=dist, name=name, points=points, coa=coa})
|
||||
end
|
||||
if points ~= 0 then
|
||||
local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
||||
if coa == enemycoa and points ~= 0 then
|
||||
local fpoints = points
|
||||
local tries = 1
|
||||
while targets[fpoints] or tries < 100 do
|
||||
fpoints = points + (math.random(1,100))
|
||||
tries = tries + 1
|
||||
self:T("Enemycoa = "..enemycoa)
|
||||
if coa == coalition.side.NEUTRAL then
|
||||
local tdata = {}
|
||||
tdata.name = name
|
||||
tdata.dist = dist
|
||||
tdata.points = points + self.NeutralBenefit
|
||||
tdata.coalition = coa
|
||||
tdata.coalitionname = UTILS.GetCoalitionName(coa)
|
||||
tdata.coordinate = self.airbasetable[name].coord
|
||||
table.insert(targets,tdata)
|
||||
else
|
||||
local tdata = {}
|
||||
tdata.name = name
|
||||
tdata.dist = dist
|
||||
tdata.points = points
|
||||
tdata.coalition = coa
|
||||
tdata.coalitionname = UTILS.GetCoalitionName(coa)
|
||||
tdata.coordinate = self.airbasetable[name].coord
|
||||
table.insert(targets,tdata)
|
||||
end
|
||||
targets[fpoints] = {
|
||||
name = name,
|
||||
dist = dist,
|
||||
points = fpoints,
|
||||
coalition = coa,
|
||||
coalitionname = UTILS.GetCoalitionName(coa),
|
||||
coordinate = self.airbasetable[name].coord,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1044,38 +1044,31 @@ function STRATEGO:FindConsolidationTargets()
|
||||
local data = _data -- #STRATEGO.Data
|
||||
if data.coalition == self.coalition then
|
||||
local dist, name, points, coa = self:FindClosestConsolidationTarget(data.name,self.maxrunways-1)
|
||||
if coa == coalition.side.NEUTRAL and points ~= 0 then
|
||||
local fpoints = points + self.NeutralBenefit
|
||||
local tries = 1
|
||||
while targets[fpoints] or tries < 100 do
|
||||
fpoints = points - (self.NeutralBenefit+math.random(1,100))
|
||||
tries = tries + 1
|
||||
end
|
||||
targets[fpoints] = {
|
||||
name = name,
|
||||
dist = dist,
|
||||
points = fpoints,
|
||||
coalition = coa,
|
||||
coalitionname = UTILS.GetCoalitionName(coa),
|
||||
coordinate = self.airbasetable[name].coord,
|
||||
}
|
||||
if points > 0 then
|
||||
self:T({dist=dist, name=name, points=points, coa=coa})
|
||||
end
|
||||
if points ~= 0 then
|
||||
local enemycoa = self.coalition == coalition.side.BLUE and coalition.side.RED or coalition.side.BLUE
|
||||
if coa == enemycoa and points ~= 0 then
|
||||
local fpoints = points
|
||||
local tries = 1
|
||||
while targets[fpoints] or tries < 100 do
|
||||
fpoints = points - (math.random(1,100))
|
||||
tries = tries + 1
|
||||
self:T("Enemycoa = "..enemycoa)
|
||||
if coa == coalition.side.NEUTRAL then
|
||||
local tdata = {}
|
||||
tdata.name = name
|
||||
tdata.dist = dist
|
||||
tdata.points = points + self.NeutralBenefit
|
||||
tdata.coalition = coa
|
||||
tdata.coalitionname = UTILS.GetCoalitionName(coa)
|
||||
tdata.coordinate = self.airbasetable[name].coord
|
||||
table.insert(targets,tdata)
|
||||
else
|
||||
local tdata = {}
|
||||
tdata.name = name
|
||||
tdata.dist = dist
|
||||
tdata.points = points
|
||||
tdata.coalition = coa
|
||||
tdata.coalitionname = UTILS.GetCoalitionName(coa)
|
||||
tdata.coordinate = self.airbasetable[name].coord
|
||||
table.insert(targets,tdata)
|
||||
end
|
||||
targets[fpoints] = {
|
||||
name = name,
|
||||
dist = dist,
|
||||
points = fpoints,
|
||||
coalition = coa,
|
||||
coalitionname = UTILS.GetCoalitionName(coa),
|
||||
coordinate = self.airbasetable[name].coord,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -1245,13 +1238,15 @@ end
|
||||
-- @return #table Target Table with #STRATEGO.Target data or nil if none found.
|
||||
function STRATEGO:FindAffordableStrategicTarget()
|
||||
self:T(self.lid.."FindAffordableStrategicTarget")
|
||||
local targets = self:FindStrategicTargets() -- #table of #STRATEGO.Target
|
||||
local Stargets = self:FindStrategicTargets() -- #table of #STRATEGO.Target
|
||||
--UTILS.PrintTableToLog(Stargets,1)
|
||||
local budget = self.Budget
|
||||
--local leftover = self.Budget
|
||||
local target = nil -- #STRATEGO.Target
|
||||
local ftarget = nil -- #STRATEGO.Target
|
||||
local Targets = {}
|
||||
for _,_data in pairs(targets) do
|
||||
for _,_data in pairs(Stargets) do
|
||||
local data = _data -- #STRATEGO.Target
|
||||
self:T("Considering Strategic Target "..data.name)
|
||||
--if data.points <= budget and budget-data.points < leftover then
|
||||
if data.points <= budget then
|
||||
--leftover = budget-data.points
|
||||
@ -1259,14 +1254,18 @@ function STRATEGO:FindAffordableStrategicTarget()
|
||||
self:T(self.lid.."Affordable strategic target: "..data.name)
|
||||
end
|
||||
end
|
||||
if not targets then
|
||||
if #Targets == 0 then
|
||||
self:T(self.lid.."No suitable target found!")
|
||||
return nil
|
||||
end
|
||||
target = Targets[math.random(1,#Targets)]
|
||||
if target then
|
||||
self:T(self.lid.."Final affordable strategic target: "..target.name)
|
||||
return target
|
||||
if #Targets > 1 then
|
||||
ftarget = Targets[math.random(1,#Targets)]
|
||||
else
|
||||
ftarget = Targets[1]
|
||||
end
|
||||
if ftarget then
|
||||
self:T(self.lid.."Final affordable strategic target: "..ftarget.name)
|
||||
return ftarget
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@ -1277,13 +1276,15 @@ end
|
||||
-- @return #table Target Table with #STRATEGO.Target data or nil if none found.
|
||||
function STRATEGO:FindAffordableConsolidationTarget()
|
||||
self:T(self.lid.."FindAffordableConsolidationTarget")
|
||||
local targets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target
|
||||
local Ctargets = self:FindConsolidationTargets() -- #table of #STRATEGO.Target
|
||||
--UTILS.PrintTableToLog(Ctargets,1)
|
||||
local budget = self.Budget
|
||||
--local leftover = self.Budget
|
||||
local target = nil -- #STRATEGO.Target
|
||||
local ftarget = nil -- #STRATEGO.Target
|
||||
local Targets = {}
|
||||
for _,_data in pairs(targets) do
|
||||
for _,_data in pairs(Ctargets) do
|
||||
local data = _data -- #STRATEGO.Target
|
||||
self:T("Considering Consolidation Target "..data.name)
|
||||
--if data.points <= budget and budget-data.points < leftover then
|
||||
if data.points <= budget then
|
||||
--leftover = budget-data.points
|
||||
@ -1291,14 +1292,18 @@ function STRATEGO:FindAffordableConsolidationTarget()
|
||||
self:T(self.lid.."Affordable consolidation target: "..data.name)
|
||||
end
|
||||
end
|
||||
if not targets then
|
||||
if #Targets == 0 then
|
||||
self:T(self.lid.."No suitable target found!")
|
||||
return nil
|
||||
end
|
||||
target = Targets[math.random(1,#Targets)]
|
||||
if target then
|
||||
self:T(self.lid.."Final affordable consolidation target: "..target.name)
|
||||
return target
|
||||
if #Targets > 1 then
|
||||
ftarget = Targets[math.random(1,#Targets)]
|
||||
else
|
||||
ftarget = Targets[1]
|
||||
end
|
||||
if ftarget then
|
||||
self:T(self.lid.."Final affordable consolidation target: "..ftarget.name)
|
||||
return ftarget
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -9755,7 +9755,7 @@ function AIRBOSS:_Groove( playerData )
|
||||
local glideslopeError = groovedata.GSE
|
||||
local AoA = groovedata.AoA
|
||||
|
||||
if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 or playerData.unit:IsInZone( self:_GetZoneLineup() )) then
|
||||
if rho <= RXX and playerData.step == AIRBOSS.PatternStep.GROOVE_XX and (math.abs( groovedata.Roll ) <= 4.0 and playerData.unit:IsInZone( self:_GetZoneLineup() )) then
|
||||
|
||||
-- Start time in groove
|
||||
playerData.TIG0 = timer.getTime()
|
||||
|
||||
@ -2051,6 +2051,54 @@ function ARMYGROUP:_InitGroup(Template, Delay)
|
||||
if Delay and Delay>0 then
|
||||
self:ScheduleOnce(Delay, ARMYGROUP._InitGroup, self, Template, 0)
|
||||
else
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
self:T(self.lid.."WARNING: Group was already initialized! Will NOT do it again!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Get template of group.
|
||||
local template=Template or self:_GetTemplate()
|
||||
|
||||
-- Ground are always AI.
|
||||
self.isAI=true
|
||||
|
||||
-- Is (template) group late activated.
|
||||
self.isLateActivated=template.lateActivation
|
||||
|
||||
-- Ground groups cannot be uncontrolled.
|
||||
self.isUncontrolled=false
|
||||
|
||||
-- Max speed in km/h.
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed in km/h
|
||||
self.speedCruise=self.speedMax*0.7
|
||||
|
||||
-- Group ammo.
|
||||
self.ammo=self:GetAmmoTot()
|
||||
|
||||
-- Radio parameters from template.
|
||||
self.radio.On=false -- Radio is always OFF for ground.
|
||||
self.radio.Freq=133
|
||||
self.radio.Modu=radio.modulation.AM
|
||||
|
||||
-- Set default radio.
|
||||
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On)
|
||||
|
||||
-- Get current formation from first waypoint.
|
||||
self.option.Formation=template.route.points[1].action
|
||||
|
||||
-- Set default formation to "on road".
|
||||
self.optionDefault.Formation=ENUMS.Formation.Vehicle.OnRoad
|
||||
|
||||
-- First check if group was already initialized.
|
||||
if self.groupinitialized then
|
||||
|
||||
@ -508,7 +508,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.61", -- #string
|
||||
version = "0.2.63", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@ -1384,7 +1384,7 @@ end
|
||||
-- Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [User] Set the tactical information option, create 10 radio channels groups can subscribe and get Bogey Dope on a specific frequency automatically.
|
||||
--- [User] Set the tactical information option, create 10 radio channels groups can subscribe and get Bogey Dope on a specific frequency automatically. You **need** to set up SRS first before using this!
|
||||
-- @param #AWACS self
|
||||
-- @param #number BaseFreq Base Frequency to use, defaults to 130.
|
||||
-- @param #number Increase Increase to use, defaults to 0.5, thus channels created are 130, 130.5, 131 .. etc.
|
||||
@ -1394,6 +1394,10 @@ end
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
|
||||
self:T(self.lid.."SetTacticalRadios")
|
||||
if not self.AwacsSRS then
|
||||
MESSAGE:New("AWACS: Setup SRS in your code BEFORE trying to add tac radios please!",30,"ERROR",true):ToLog():ToAll()
|
||||
return self
|
||||
end
|
||||
self.TacticalMenu = true
|
||||
self.TacticalBaseFreq = BaseFreq or 130
|
||||
self.TacticalIncrFreq = Increase or 0.5
|
||||
@ -1407,7 +1411,7 @@ function AWACS:SetTacticalRadios(BaseFreq,Increase,Modulation,Interval,Number)
|
||||
self.TacticalFrequencies[freq] = freq
|
||||
end
|
||||
if self.AwacsSRS then
|
||||
self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation)
|
||||
self.TacticalSRS = MSRS:New(self.PathToSRS,self.TacticalBaseFreq,self.TacticalModulation,self.Backend)
|
||||
self.TacticalSRS:SetCoalition(self.coalition)
|
||||
self.TacticalSRS:SetGender(self.Gender)
|
||||
self.TacticalSRS:SetCulture(self.Culture)
|
||||
@ -2085,8 +2089,9 @@ end
|
||||
-- @param #number Volume Volume - between 0.0 (silent) and 1.0 (loudest)
|
||||
-- @param #string PathToGoogleKey (Optional) Path to your google key if you want to use google TTS; if you use a config file for MSRS, hand in nil here.
|
||||
-- @param #string AccessKey (Optional) Your Google API access key. This is necessary if DCS-gRPC is used as backend; if you use a config file for MSRS, hand in nil here.
|
||||
-- @param #string Backend (Optional) Your MSRS Backend if different from your config file settings, e.g. MSRS.Backend.SRSEXE or MSRS.Backend.GRPC
|
||||
-- @return #AWACS self
|
||||
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey)
|
||||
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey,AccessKey,Backend)
|
||||
self:T(self.lid.."SetSRS")
|
||||
self.PathToSRS = PathToSRS or MSRS.path or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
||||
self.Gender = Gender or MSRS.gender or "male"
|
||||
@ -2096,8 +2101,9 @@ function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice,Volume,PathToGoogleKey
|
||||
self.PathToGoogleKey = PathToGoogleKey
|
||||
self.AccessKey = AccessKey
|
||||
self.Volume = Volume or 1.0
|
||||
|
||||
self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation)
|
||||
self.Backend = Backend or MSRS.backend
|
||||
BASE:I({backend = self.Backend})
|
||||
self.AwacsSRS = MSRS:New(self.PathToSRS,self.MultiFrequency,self.MultiModulation,self.Backend)
|
||||
self.AwacsSRS:SetCoalition(self.coalition)
|
||||
self.AwacsSRS:SetGender(self.Gender)
|
||||
self.AwacsSRS:SetCulture(self.Culture)
|
||||
@ -2499,13 +2505,22 @@ function AWACS:_CheckMerges()
|
||||
local cpos = contact.Cluster.coordinate or contact.Contact.position or contact.Contact.group:GetCoordinate()
|
||||
local dist = ppos:Get2DDistance(cpos)
|
||||
local distnm = UTILS.Round(UTILS.MetersToNM(dist),0)
|
||||
if (pilot.IsPlayer or self.debug) and distnm <= 5 and not contact.MergeCallDone then
|
||||
local label = contact.EngagementTag or ""
|
||||
if not contact.MergeCallDone or not string.find(label,pcallsign) then
|
||||
if (pilot.IsPlayer or self.debug) and distnm <= 5 then --and ((not contact.MergeCallDone) or (timer.getTime() - contact.MergeCallDone > 30)) then
|
||||
--local label = contact.EngagementTag or ""
|
||||
--if not contact.MergeCallDone or not string.find(label,pcallsign) then
|
||||
self:T(self.lid.."Merged")
|
||||
self:_MergedCall(_id)
|
||||
contact.MergeCallDone = true
|
||||
--contact.MergeCallDone = true
|
||||
--end
|
||||
end
|
||||
if (pilot.IsPlayer or self.debug) and distnm >5 and distnm <= self.ThreatDistance then
|
||||
self:_ThreatRangeCall(_id,Contact)
|
||||
end
|
||||
if (pilot.IsPlayer or self.debug) and distnm > self.ThreatDistance and distnm <= self.MeldDistance then
|
||||
self:_MeldRangeCall(_id,Contact)
|
||||
end
|
||||
if (pilot.IsPlayer or self.debug) and distnm > self.MeldDistance and distnm <= self.TacDistance then
|
||||
self:_TACRangeCall(_id,Contact)
|
||||
end
|
||||
end
|
||||
)
|
||||
@ -3099,7 +3114,7 @@ function AWACS:_BogeyDope(Group,Tactical)
|
||||
local clean = self.gettext:GetEntry("CLEAN",self.locale)
|
||||
text = string.format(clean,self:_GetCallSign(Group,GID) or "Ghost 1", self.callsigntxt)
|
||||
|
||||
self:_NewRadioEntry(text,textScreen,GID,Outcome,Outcome,true,false,true,Tactical)
|
||||
self:_NewRadioEntry(text,text,GID,Outcome,Outcome,true,false,true,Tactical)
|
||||
|
||||
else
|
||||
|
||||
@ -3141,9 +3156,13 @@ end
|
||||
function AWACS:_ShowAwacsInfo(Group)
|
||||
self:T(self.lid.."_ShowAwacsInfo")
|
||||
local report = REPORT:New("Info")
|
||||
local STN = self.STN
|
||||
report:Add("====================")
|
||||
report:Add(string.format("AWACS %s",self.callsigntxt))
|
||||
report:Add(string.format("Radio: %.3f %s",self.Frequency,UTILS.GetModulationName(self.Modulation)))
|
||||
if STN then
|
||||
report:Add(string.format("Link-16 STN: %s",STN))
|
||||
end
|
||||
report:Add(string.format("Bulls Alias: %s",self.AOName))
|
||||
report:Add(string.format("Coordinate: %s",self.AOCoordinate:ToStringLLDDM()))
|
||||
report:Add("====================")
|
||||
@ -5467,7 +5486,7 @@ function AWACS:_TACRangeCall(GID,Contact)
|
||||
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
|
||||
local contacttag = Contact.TargetGroupNaming
|
||||
if contact and not Contact.TACCallDone then
|
||||
if contact then --and not Contact.TACCallDone then
|
||||
local position = contact.position -- Core.Point#COORDINATE
|
||||
if position then
|
||||
local distance = position:Get2DDistance(managedgroup.Group:GetCoordinate())
|
||||
@ -5477,6 +5496,15 @@ function AWACS:_TACRangeCall(GID,Contact)
|
||||
local text = string.format("%s. %s. %s %s, %d %s.",self.callsigntxt,pilotcallsign,contacttag,grptxt,distance,miles)
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
|
||||
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,false,AWACS.TaskStatus.EXECUTING)
|
||||
if GID and GID ~= 0 then
|
||||
--local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
|
||||
local name = managedgroup.GroupName
|
||||
if self.TacticalSubscribers[name] then
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
@ -5495,8 +5523,8 @@ function AWACS:_MeldRangeCall(GID,Contact)
|
||||
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
local flightpos = managedgroup.Group:GetCoordinate()
|
||||
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
|
||||
local contacttag = Contact.TargetGroupNaming
|
||||
if contact and not Contact.MeldCallDone then
|
||||
local contacttag = Contact.TargetGroupNaming or "Bogey"
|
||||
if contact then --and not Contact.MeldCallDone then
|
||||
local position = contact.position -- Core.Point#COORDINATE
|
||||
if position then
|
||||
local BRATExt = ""
|
||||
@ -5509,6 +5537,15 @@ function AWACS:_MeldRangeCall(GID,Contact)
|
||||
local text = string.format("%s. %s. %s %s, %s",self.callsigntxt,pilotcallsign,contacttag,grptxt,BRATExt)
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
|
||||
self:_UpdateContactEngagementTag(Contact.CID,Contact.EngagementTag,true,true,AWACS.TaskStatus.EXECUTING)
|
||||
if GID and GID ~= 0 then
|
||||
--local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
|
||||
local name = managedgroup.GroupName
|
||||
if self.TacticalSubscribers[name] then
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
@ -5525,7 +5562,7 @@ function AWACS:_ThreatRangeCall(GID,Contact)
|
||||
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
local flightpos = managedgroup.Group:GetCoordinate() or managedgroup.LastKnownPosition
|
||||
local contact = Contact.Contact -- Ops.Intel#INTEL.Contact
|
||||
local contacttag = Contact.TargetGroupNaming
|
||||
local contacttag = Contact.TargetGroupNaming or "Bogey"
|
||||
if contact then
|
||||
local position = contact.position or contact.group:GetCoordinate() -- Core.Point#COORDINATE
|
||||
if position then
|
||||
@ -5539,6 +5576,15 @@ function AWACS:_ThreatRangeCall(GID,Contact)
|
||||
local thrt = self.gettext:GetEntry("THREAT",self.locale)
|
||||
local text = string.format("%s. %s. %s %s, %s. %s",self.callsigntxt,pilotcallsign,contacttag,grptxt, thrt, BRATExt)
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true)
|
||||
if GID and GID ~= 0 then
|
||||
--local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
||||
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
|
||||
local name = managedgroup.GroupName
|
||||
if self.TacticalSubscribers[name] then
|
||||
self:_NewRadioEntry(text,text,GID,true,self.debug,true,false,true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
@ -5953,6 +5999,10 @@ function AWACS:_CheckAwacsStatus()
|
||||
local awacs = nil -- Wrapper.Group#GROUP
|
||||
if self.AwacsFG then
|
||||
awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP
|
||||
local unit = awacs:GetUnit(1)
|
||||
if unit then
|
||||
self.STN = tostring(unit:GetSTN())
|
||||
end
|
||||
end
|
||||
|
||||
local monitoringdata = self.MonitoringData -- #AWACS.MonitoringData
|
||||
@ -6632,7 +6682,7 @@ function AWACS:onafterCheckTacticalQueue(From,Event,To)
|
||||
|
||||
end -- end while
|
||||
|
||||
if self:Is("Running") then
|
||||
if not self:Is("Stopped") then
|
||||
self:__CheckTacticalQueue(-self.TacticalInterval)
|
||||
end
|
||||
return self
|
||||
|
||||
@ -313,8 +313,8 @@ end
|
||||
--
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce()
|
||||
-- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename)
|
||||
-- local BlueSaveOps = SET_OPSGROUP:New():FilterCoalitions("blue"):FilterCategoryGround():FilterOnce()
|
||||
-- UTILS.SaveSetOfOpsGroups(BlueSaveOps,Path,BlueOpsFilename)
|
||||
--
|
||||
-- where Path and Filename are strings, as chosen by you.
|
||||
-- You can then load back the assets at the start of your next mission run. Be aware that it takes a couple of seconds for the
|
||||
@ -324,7 +324,7 @@ end
|
||||
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
|
||||
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
|
||||
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then
|
||||
-- local loadback = UTILS.LoadSetOfGroups(Path,BlueOpsFilename,false)
|
||||
-- local loadback = UTILS.LoadSetOfOpsGroups(Path,BlueOpsFilename,false)
|
||||
-- for _,_platoondata in pairs (loadback) do
|
||||
-- local groupname = _platoondata.groupname -- #string
|
||||
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE
|
||||
|
||||
@ -290,10 +290,11 @@ CSAR.AircraftType["Bell-47"] = 2
|
||||
CSAR.AircraftType["UH-60L"] = 10
|
||||
CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
CSAR.AircraftType["Bronco-OV-10A"] = 2
|
||||
CSAR.AircraftType["MH-60R"] = 10
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.19"
|
||||
CSAR.version="1.0.20"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
-- @module Ops.CTLD
|
||||
-- @image OPS_CTLD.jpg
|
||||
|
||||
-- Last Update December 2023
|
||||
-- Last Update February 2024
|
||||
|
||||
do
|
||||
|
||||
@ -44,6 +44,7 @@ do
|
||||
-- @field #number PerCrateMass Mass in kg.
|
||||
-- @field #number Stock Number of builds available, -1 for unlimited.
|
||||
-- @field #string Subcategory Sub-category name.
|
||||
-- @field #boolean DontShowInMenu Show this item in menu or not.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
---
|
||||
@ -62,6 +63,7 @@ CTLD_CARGO = {
|
||||
PerCrateMass = 0,
|
||||
Stock = nil,
|
||||
Mark = nil,
|
||||
DontShowInMenu = false,
|
||||
}
|
||||
|
||||
--- Define cargo types.
|
||||
@ -97,8 +99,9 @@ CTLD_CARGO = {
|
||||
-- @param #number PerCrateMass Mass in kg
|
||||
-- @param #number Stock Number of builds available, nil for unlimited
|
||||
-- @param #string Subcategory Name of subcategory, handy if using > 10 types to load.
|
||||
-- @param #boolean DontShowInMenu Show this item in menu or not (default: false == show it).
|
||||
-- @return #CTLD_CARGO self
|
||||
function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
|
||||
function CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory,DontShowInMenu)
|
||||
-- Inherit everything from BASE class.
|
||||
local self=BASE:Inherit(self, BASE:New()) -- #CTLD_CARGO
|
||||
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
|
||||
@ -115,6 +118,7 @@ CTLD_CARGO = {
|
||||
self.Stock = Stock or nil --#number
|
||||
self.Mark = nil
|
||||
self.Subcategory = Subcategory or "Other"
|
||||
self.DontShowInMenu = DontShowInMenu or false
|
||||
return self
|
||||
end
|
||||
|
||||
@ -1222,13 +1226,14 @@ CTLD.UnitTypeCapabilities = {
|
||||
["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
|
||||
--Actually it's longer, but the center coord is off-center of the model.
|
||||
["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
||||
["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
||||
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
|
||||
["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
|
||||
}
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.45"
|
||||
CTLD.version="1.0.48"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@ -3024,9 +3029,10 @@ end
|
||||
function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
|
||||
local Positions = {}
|
||||
local template = _DATABASE:GetGroupTemplate(Template)
|
||||
UTILS.PrintTableToLog(template)
|
||||
--UTILS.PrintTableToLog(template)
|
||||
local numbertroops = #template.units
|
||||
local newcenter = Coordinate:Translate(Radius,((Heading+270)%360))
|
||||
local slightshift = math.abs(math.random(0,200)/100)
|
||||
local newcenter = Coordinate:Translate(Radius+slightshift,((Heading+270)%360))
|
||||
for i=1,360,math.floor(360/numbertroops) do
|
||||
local phead = ((Heading+270+i)%360)
|
||||
local post = newcenter:Translate(Radius,phead)
|
||||
@ -3038,7 +3044,7 @@ function CTLD:_GetUnitPositions(Coordinate,Radius,Heading,Template)
|
||||
}
|
||||
table.insert(Positions,p1t)
|
||||
end
|
||||
UTILS.PrintTableToLog(Positions)
|
||||
--UTILS.PrintTableToLog(Positions)
|
||||
return Positions
|
||||
end
|
||||
|
||||
@ -3700,16 +3706,22 @@ function CTLD:_RefreshF10Menus()
|
||||
for _,_entry in pairs(self.Cargo_Troops) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local subcat = entry.Subcategory
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,subcatmenus[subcat],self._LoadTroops, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
else
|
||||
for _,_entry in pairs(self.Cargo_Troops) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,entry.Name,troopsmenu,self._LoadTroops, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
local unloadmenu1 = MENU_GROUP_COMMAND:New(_group,"Drop troops",toptroops, self._UnloadTroops, self, _group, _unit):Refresh()
|
||||
local extractMenu1 = MENU_GROUP_COMMAND:New(_group, "Extract troops", toptroops, self._ExtractTroops, self, _group, _unit):Refresh()
|
||||
end
|
||||
@ -3728,33 +3740,45 @@ function CTLD:_RefreshF10Menus()
|
||||
for _,_entry in pairs(self.Cargo_Crates) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local subcat = entry.Subcategory
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local subcat = entry.Subcategory
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
else
|
||||
for _,_entry in pairs(self.Cargo_Crates) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local noshow = entry.DontShowInMenu
|
||||
if not noshow then
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit)
|
||||
removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit)
|
||||
local removecrates = MENU_GROUP_COMMAND:New(_group,"Remove crates nearby",removecratesmenu, self._RemoveCratesNearby, self, _group, _unit)
|
||||
local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit)
|
||||
if not self.nobuildmenu then
|
||||
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
|
||||
|
||||
@ -140,7 +140,7 @@ COMMANDER = {
|
||||
|
||||
--- COMMANDER class version.
|
||||
-- @field #string version
|
||||
COMMANDER.version="0.1.3"
|
||||
COMMANDER.version="0.1.4"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -675,7 +675,8 @@ function COMMANDER:AddCapZone(Zone, Altitude, Speed, Heading, Leg)
|
||||
patrolzone.zone=Zone
|
||||
patrolzone.altitude=Altitude or 12000
|
||||
patrolzone.heading=Heading or 270
|
||||
patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude)
|
||||
--patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude)
|
||||
patrolzone.speed=Speed or 350
|
||||
patrolzone.leg=Leg or 30
|
||||
patrolzone.mission=nil
|
||||
--patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "CAP Zone"):ToCoalition(self:GetCoalition())
|
||||
@ -700,7 +701,8 @@ function COMMANDER:AddGciCapZone(Zone, Altitude, Speed, Heading, Leg)
|
||||
patrolzone.zone=Zone
|
||||
patrolzone.altitude=Altitude or 12000
|
||||
patrolzone.heading=Heading or 270
|
||||
patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude)
|
||||
--patrolzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, patrolzone.altitude)
|
||||
patrolzone.speed=Speed or 350
|
||||
patrolzone.leg=Leg or 30
|
||||
patrolzone.mission=nil
|
||||
--patrolzone.marker=MARKER:New(patrolzone.zone:GetCoordinate(), "GCICAP Zone"):ToCoalition(self:GetCoalition())
|
||||
@ -745,7 +747,9 @@ function COMMANDER:AddAwacsZone(Zone, Altitude, Speed, Heading, Leg)
|
||||
awacszone.zone=Zone
|
||||
awacszone.altitude=Altitude or 12000
|
||||
awacszone.heading=Heading or 270
|
||||
awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude)
|
||||
--awacszone.speed=UTILS.KnotsToAltKIAS(Speed or 350, awacszone.altitude)
|
||||
awacszone.speed=Speed or 350
|
||||
awacszone.speed=Speed or 350
|
||||
awacszone.leg=Leg or 30
|
||||
awacszone.mission=nil
|
||||
--awacszone.marker=MARKER:New(awacszone.zone:GetCoordinate(), "AWACS Zone"):ToCoalition(self:GetCoalition())
|
||||
@ -791,7 +795,8 @@ function COMMANDER:AddTankerZone(Zone, Altitude, Speed, Heading, Leg, RefuelSyst
|
||||
tankerzone.zone=Zone
|
||||
tankerzone.altitude=Altitude or 12000
|
||||
tankerzone.heading=Heading or 270
|
||||
tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, tankerzone.altitude)
|
||||
--tankerzone.speed=UTILS.KnotsToAltKIAS(Speed or 350, tankerzone.altitude) -- speed translation to alt will be done by AUFTRAG anyhow
|
||||
tankerzone.speed = Speed or 350
|
||||
tankerzone.leg=Leg or 30
|
||||
tankerzone.refuelsystem=RefuelSystem
|
||||
tankerzone.mission=nil
|
||||
|
||||
@ -3799,10 +3799,11 @@ function FLIGHTGROUP:_InitGroup(Template)
|
||||
self.speedMax=group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax>3.6 then
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed limit 380 kts for fixed and 110 knots for rotary wings.
|
||||
|
||||
@ -3190,14 +3190,14 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu
|
||||
elseif (currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then
|
||||
score=score+25
|
||||
elseif currmission.type==AUFTRAG.Type.NOTHING then
|
||||
score=score+25
|
||||
score=score+30
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if MissionType==AUFTRAG.Type.OPSTRANSPORT or MissionType==AUFTRAG.Type.AMMOSUPPLY or MissionType==AUFTRAG.Type.AWACS or MissionType==AUFTRAG.Type.FUELSUPPLY or MissionType==AUFTRAG.Type.TANKER then
|
||||
-- TODO: need to check for missions that do not require ammo like transport, recon, awacs, tanker etc.
|
||||
-- We better take a fresh asset. Sometimes spawned assets to something else, which is difficult to check.
|
||||
-- We better take a fresh asset. Sometimes spawned assets do something else, which is difficult to check.
|
||||
score=score-10
|
||||
else
|
||||
-- Combat mission.
|
||||
|
||||
@ -1800,10 +1800,11 @@ function NAVYGROUP:_InitGroup(Template)
|
||||
self.speedMax=self.group:GetSpeedMax()
|
||||
|
||||
-- Is group mobile?
|
||||
if self.speedMax>3.6 then
|
||||
if self.speedMax and self.speedMax>3.6 then
|
||||
self.isMobile=true
|
||||
else
|
||||
self.isMobile=false
|
||||
self.speedMax = 0
|
||||
end
|
||||
|
||||
-- Cruise speed: 70% of max speed.
|
||||
|
||||
@ -508,7 +508,7 @@ OPSGROUP.CargoStatus={
|
||||
|
||||
--- OpsGroup version.
|
||||
-- @field #string version
|
||||
OPSGROUP.version="1.0.0"
|
||||
OPSGROUP.version="1.0.1"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -554,11 +554,16 @@ function OPSGROUP:New(group)
|
||||
-- Check if group exists.
|
||||
if self.group then
|
||||
if not self:IsExist() then
|
||||
self:T(self.lid.."ERROR: GROUP does not exist! Returning nil")
|
||||
self:E(self.lid.."ERROR: GROUP does not exist! Returning nil")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if UTILS.IsInstanceOf(group,"OPSGROUP") then
|
||||
self:E(self.lid.."ERROR: GROUP is already an OPSGROUP: "..tostring(self.groupname).."!")
|
||||
return group
|
||||
end
|
||||
|
||||
-- Set the template.
|
||||
self:_SetTemplate()
|
||||
|
||||
@ -592,6 +597,7 @@ function OPSGROUP:New(group)
|
||||
if units then
|
||||
local masterunit=units[1] --Wrapper.Unit#UNIT
|
||||
|
||||
if unit then
|
||||
-- Get Descriptors.
|
||||
self.descriptors=masterunit:GetDesc()
|
||||
|
||||
@ -617,7 +623,7 @@ function OPSGROUP:New(group)
|
||||
--env.info("DCS Unit PROBE_AND_DROGUE="..tostring(Unit.RefuelingSystem.PROBE_AND_DROGUE))
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Init set of detected units.
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
-- ===
|
||||
-- @module Ops.PlayerTask
|
||||
-- @image OPS_PlayerTask.jpg
|
||||
-- @date Last Update Jan 2024
|
||||
-- @date Last Update Feb 2024
|
||||
|
||||
|
||||
do
|
||||
@ -411,6 +411,15 @@ function PLAYERTASK:IsDone()
|
||||
return IsDone
|
||||
end
|
||||
|
||||
--- [User] Check if PLAYERTASK has clients assigned to it.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return #boolean hasclients
|
||||
function PLAYERTASK:HasClients()
|
||||
self:T(self.lid.."HasClients?")
|
||||
local hasclients = self:CountClients() > 0 and true or false
|
||||
return hasclients
|
||||
end
|
||||
|
||||
--- [User] Get client names assigned as table of #strings
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return #table clients
|
||||
@ -1552,7 +1561,7 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASKCONTROLLER.version="0.1.64"
|
||||
PLAYERTASKCONTROLLER.version="0.1.65"
|
||||
|
||||
--- Create and run a new TASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
@ -3173,7 +3182,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Task, Group, Client)
|
||||
local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale)
|
||||
local taskname = string.format(tname,task.Type,task.PlayerTaskNr)
|
||||
local ttstaskname = string.format(ttsname,task.TTSType,task.PlayerTaskNr)
|
||||
local Coordinate = task.Target:GetCoordinate()
|
||||
local Coordinate = task.Target:GetCoordinate() or COORDINATE:New(0,0,0)
|
||||
local CoordText = ""
|
||||
local CoordTextLLDM = nil
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
|
||||
@ -30,6 +30,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Sound/Radio)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Authors: Hugues "Grey_Echo" Bousquet, funkyfranky
|
||||
--
|
||||
-- @module Sound.Radio
|
||||
|
||||
@ -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).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@ -444,10 +444,11 @@ end
|
||||
--- Print a table to log in a nice format
|
||||
-- @param #table table The table to print
|
||||
-- @param #number indent Number of indents
|
||||
-- @param #boolean noprint Don't log but return text
|
||||
-- @return #string text Text created on the fly of the log output
|
||||
function UTILS.PrintTableToLog(table, indent)
|
||||
function UTILS.PrintTableToLog(table, indent, noprint)
|
||||
local text = "\n"
|
||||
if not table then
|
||||
if not table or type(table) ~= "table" then
|
||||
env.warning("No table passed!")
|
||||
return nil
|
||||
end
|
||||
@ -455,11 +456,16 @@ function UTILS.PrintTableToLog(table, indent)
|
||||
for k, v in pairs(table) do
|
||||
if string.find(k," ") then k='"'..k..'"'end
|
||||
if type(v) == "table" then
|
||||
if not noprint then
|
||||
env.info(string.rep(" ", indent) .. tostring(k) .. " = {")
|
||||
end
|
||||
text = text ..string.rep(" ", indent) .. tostring(k) .. " = {\n"
|
||||
text = text .. tostring(UTILS.PrintTableToLog(v, indent + 1)).."\n"
|
||||
if not noprint then
|
||||
env.info(string.rep(" ", indent) .. "},")
|
||||
end
|
||||
text = text .. string.rep(" ", indent) .. "},\n"
|
||||
elseif type(v) == "function" then
|
||||
else
|
||||
local value
|
||||
if tostring(v) == "true" or tostring(v) == "false" or tonumber(v) ~= nil then
|
||||
@ -467,7 +473,9 @@ function UTILS.PrintTableToLog(table, indent)
|
||||
else
|
||||
value = '"'..tostring(v)..'"'
|
||||
end
|
||||
if not noprint then
|
||||
env.info(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n")
|
||||
end
|
||||
text = text .. string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(value)..",\n"
|
||||
end
|
||||
end
|
||||
@ -2229,6 +2237,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
return true -- no doors on this one ;)
|
||||
end
|
||||
|
||||
if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then
|
||||
BASE:T(unit_name .. " cargo door is open")
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end -- nil
|
||||
@ -3717,3 +3730,116 @@ end
|
||||
function UTILS.OctalToDecimal(Number)
|
||||
return tonumber(Number,8)
|
||||
end
|
||||
|
||||
--- Function to save the position of a set of #OPSGROUP (ARMYGROUP) objects.
|
||||
-- @param Core.Set#SET_OPSGROUP Set of ops objects to save
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
function UTILS.SaveSetOfOpsGroups(Set,Path,Filename,Structured)
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local data = "--Save SET of groups: (name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata) "..Filename .."\n"
|
||||
local List = Set:GetSetObjects()
|
||||
for _,_group in pairs (List) do
|
||||
local group = _group:GetGroup() -- Wrapper.Group#GROUP
|
||||
if group and group:IsAlive() then
|
||||
local name = group:GetName()
|
||||
local template = string.gsub(name,"(.AID.%d+$","")
|
||||
if string.find(template,"#") then
|
||||
template = string.gsub(name,"#(%d+)$","")
|
||||
end
|
||||
local alttemplate = _group.templatename or "none"
|
||||
local legiono = _group.legion -- Ops.Legion#LEGION
|
||||
local legion = "none"
|
||||
if legiono and type(legiono) == "table" and legiono.ClassName then
|
||||
legion = legiono:GetName()
|
||||
local asset = legiono:GetAssetByName(name) -- Functional.Warehouse#WAREHOUSE.Assetitem
|
||||
alttemplate=asset.templatename
|
||||
end
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
if Structured then
|
||||
local structure = UTILS.GetCountPerTypeName(group)
|
||||
local strucdata = ""
|
||||
for typen,anzahl in pairs (structure) do
|
||||
strucdata = strucdata .. typen .. "=="..anzahl..";"
|
||||
end
|
||||
data = string.format("%s%s,%s,%s,%s,%d,%d,%d,%d,%s\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%s,%s,%s,%d,%d,%d,%d\n",data,name,legion,template,alttemplate,units,position.x,position.y,position.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- save the data
|
||||
local outcome = UTILS.SaveToFile(Path,Filename,data)
|
||||
return outcome
|
||||
end
|
||||
|
||||
--- Load back a #OPSGROUP (ARMYGROUP) data from file for use with @{Ops.Brigade#BRIGADE.LoadBackAssetInPosition}()
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @return #table Returns a table of data entries: `{ groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure, legion=legion, alttemplate=alttemplate }`
|
||||
-- Returns nil when the file cannot be read.
|
||||
function UTILS.LoadSetOfOpsGroups(Path,Filename)
|
||||
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local datatable = {}
|
||||
|
||||
if UTILS.CheckFileExists(Path,filename) then
|
||||
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
|
||||
-- remove header
|
||||
table.remove(loadeddata, 1)
|
||||
for _id,_entry in pairs (loadeddata) do
|
||||
local dataset = UTILS.Split(_entry,",")
|
||||
-- 1name,2legion,3template,4alttemplate,5units,6position.x,7position.y,8position.z,9strucdata
|
||||
local groupname = dataset[1]
|
||||
local legion = dataset[2]
|
||||
local template = dataset[3]
|
||||
local alttemplate = dataset[4]
|
||||
local size = tonumber(dataset[5])
|
||||
local posx = tonumber(dataset[6])
|
||||
local posy = tonumber(dataset[7])
|
||||
local posz = tonumber(dataset[8])
|
||||
local structure = dataset[9]
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
if size > 0 then
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure, legion=legion, alttemplate=alttemplate }
|
||||
table.insert(datatable,data)
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
return datatable
|
||||
end
|
||||
|
||||
--- Get the clock position from a relative heading
|
||||
-- @param #number refHdg The heading of the reference object (such as a Wrapper.UNIT) in 0-360
|
||||
-- @param #number tgtHdg The absolute heading from the reference object to the target object/point in 0-360
|
||||
-- @return #string text Text in clock heading such as "4 O'CLOCK"
|
||||
-- @usage Display the range and clock distance of a BTR in relation to REAPER 1-1's heading:
|
||||
--
|
||||
-- myUnit = UNIT:FindByName( "REAPER 1-1" )
|
||||
-- myTarget = GROUP:FindByName( "BTR-1" )
|
||||
--
|
||||
-- coordUnit = myUnit:GetCoordinate()
|
||||
-- coordTarget = myTarget:GetCoordinate()
|
||||
--
|
||||
-- hdgUnit = myUnit:GetHeading()
|
||||
-- hdgTarget = coordUnit:HeadingTo( coordTarget )
|
||||
-- distTarget = coordUnit:Get3DDistance( coordTarget )
|
||||
--
|
||||
-- clockString = UTILS.ClockHeadingString( hdgUnit, hdgTarget )
|
||||
--
|
||||
-- -- Will show this message to REAPER 1-1 in-game: Contact BTR at 3 o'clock for 1134m!
|
||||
-- MESSAGE:New("Contact BTR at " .. clockString .. " for " .. distTarget .. "m!):ToUnit( myUnit )
|
||||
function UTILS.ClockHeadingString(refHdg,tgtHdg)
|
||||
local relativeAngle = tgtHdg - refHdg
|
||||
if relativeAngle < 0 then
|
||||
relativeAngle = relativeAngle + 360
|
||||
end
|
||||
local clockPos = math.ceil((relativeAngle % 360) / 30)
|
||||
return clockPos.." o'clock"
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -99,7 +99,7 @@
|
||||
-- This method can also be used to **embed a function call when a certain waypoint has been reached**.
|
||||
-- See below the **Tasks at Waypoints** section.
|
||||
--
|
||||
-- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/Wrapper/Group/GRP-502%20-%20Route%20at%20waypoint%20to%20random%20point)
|
||||
-- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Group/502-Route-at-waypoint-to-random-point)
|
||||
--
|
||||
-- ## 2.5) Tasks at Waypoints
|
||||
--
|
||||
@ -2173,7 +2173,7 @@ do -- Patrol methods
|
||||
local Waypoint = Waypoints[#Waypoints]
|
||||
PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
|
||||
|
||||
PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
PatrolGroup:Route( Waypoints, 2 ) -- Move after a random seconds to the Route. See the Route method for details.
|
||||
end
|
||||
end
|
||||
|
||||
@ -3871,6 +3871,10 @@ end
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #table WayPoints If WayPoints is given, then use the route.
|
||||
-- @return #CONTROLLABLE self
|
||||
-- @usage Intended Workflow is:
|
||||
-- mygroup:WayPointInitialize()
|
||||
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
|
||||
-- mygroup:WayPointExecute()
|
||||
function CONTROLLABLE:WayPointInitialize( WayPoints )
|
||||
self:F( { WayPoints } )
|
||||
|
||||
@ -3902,9 +3906,15 @@ end
|
||||
-- @param #number WayPointIndex When defining multiple WayPoint functions for one WayPoint, use WayPointIndex to set the sequence of actions.
|
||||
-- @param #function WayPointFunction The waypoint function to be called when the controllable moves over the waypoint. The waypoint function takes variable parameters.
|
||||
-- @return #CONTROLLABLE self
|
||||
-- @usage Intended Workflow is:
|
||||
-- mygroup:WayPointInitialize()
|
||||
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
|
||||
-- mygroup:WayPointExecute()
|
||||
function CONTROLLABLE:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
|
||||
self:F2( { WayPoint, WayPointIndex, WayPointFunction } )
|
||||
|
||||
if not self.WayPoints then
|
||||
self:WayPointInitialize()
|
||||
end
|
||||
table.insert( self.WayPoints[WayPoint].task.params.tasks, WayPointIndex )
|
||||
self.WayPoints[WayPoint].task.params.tasks[WayPointIndex] = self:TaskFunction( WayPointFunction, arg )
|
||||
return self
|
||||
@ -3917,6 +3927,10 @@ end
|
||||
-- @param #number WayPoint The WayPoint from where to execute the mission.
|
||||
-- @param #number WaitTime The amount seconds to wait before initiating the mission.
|
||||
-- @return #CONTROLLABLE self
|
||||
-- @usage Intended Workflow is:
|
||||
-- mygroup:WayPointInitialize()
|
||||
-- mygroup:WayPointFunction( WayPoint, WayPointIndex, WayPointFunction, ... )
|
||||
-- mygroup:WayPointExecute()
|
||||
function CONTROLLABLE:WayPointExecute( WayPoint, WaitTime )
|
||||
self:F( { WayPoint, WaitTime } )
|
||||
|
||||
@ -4102,6 +4116,74 @@ function CONTROLLABLE:SetOptionRadarUsingForContinousSearch()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set if the AI is reporting passing of waypoints
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #boolean OnOff If true or nil, AI will report passing waypoints, if false, it will not.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:SetOptionWaypointPassReport(OnOff)
|
||||
self:F2( { self.ControllableName } )
|
||||
local onoff = (OnOff == nil or OnOff == true) and false or true
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.PROHIBIT_WP_PASS_REPORT,onoff)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set the AI to not report anything over the radio - radio silence
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #boolean OnOff If true or nil, radio is set to silence, if false radio silence is lifted.
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:SetOptionRadioSilence(OnOff)
|
||||
local onoff = (OnOff == true or OnOff == nil) and true or false
|
||||
self:F2( { self.ControllableName } )
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.SILENCE,onoff)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set the AI to report contact for certain types of objects.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}. See [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:SetOptionRadioContact(Objects)
|
||||
self:F2( { self.ControllableName } )
|
||||
if not Objects then Objects = {"Air"} end
|
||||
if type(Objects) ~= "table" then Objects = {Objects} end
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_CONTACT,Objects)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set the AI to report engaging certain types of objects.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}, see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:SetOptionRadioEngage(Objects)
|
||||
self:F2( { self.ControllableName } )
|
||||
if not Objects then Objects = {"Air"} end
|
||||
if type(Objects) ~= "table" then Objects = {Objects} end
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_ENGAGE,Objects)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [AIR] Set the AI to report killing certain types of objects.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #table Objects Table of attribute names for which AI reports contact. Defaults to {"Air"}, see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_attributes)
|
||||
-- @return #CONTROLLABLE self
|
||||
function CONTROLLABLE:SetOptionRadioKill(Objects)
|
||||
self:F2( { self.ControllableName } )
|
||||
if not Objects then Objects = {"Air"} end
|
||||
if type(Objects) ~= "table" then Objects = {Objects} end
|
||||
if self:IsAir() then
|
||||
self:SetOption(AI.Option.Air.id.OPTION_RADIO_USAGE_KILL,Objects)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
|
||||
-- @param #CONTROLLABLE self
|
||||
-- @param #number speed Speed of the controllable, default 20
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Group)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **FlightControl**
|
||||
--
|
||||
-- ### Contributions:
|
||||
@ -424,7 +428,7 @@ function GROUP:IsActive()
|
||||
|
||||
local DCSGroup = self:GetDCSObject() -- DCS#Group
|
||||
|
||||
if DCSGroup then
|
||||
if DCSGroup and DCSGroup:isExist() then
|
||||
local unit = DCSGroup:getUnit(1)
|
||||
if unit then
|
||||
local GroupIsActive = unit:isActive()
|
||||
@ -1832,6 +1836,7 @@ end
|
||||
|
||||
--- Randomize the positions of the units of the respawned group within the @{Core.Zone}.
|
||||
-- When a Respawn happens, the units of the group will be placed at random positions within the Zone (selected).
|
||||
-- NOTE: InitRandomizePositionZone will not ensure, that every unit is placed within the zone!
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean PositionZone true will randomize the positions within the Zone.
|
||||
-- @return #GROUP self
|
||||
@ -2782,6 +2787,14 @@ end
|
||||
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetCommandInvisible(switch)
|
||||
return self:CommandSetInvisible(switch)
|
||||
end
|
||||
|
||||
--- Switch on/off invisible flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, Invisible is enabled. If false, Invisible is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:CommandSetInvisible(switch)
|
||||
self:F2( self.GroupName )
|
||||
if switch==nil then
|
||||
switch=false
|
||||
@ -2796,6 +2809,14 @@ end
|
||||
-- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:SetCommandImmortal(switch)
|
||||
return self:CommandSetImmortal(switch)
|
||||
end
|
||||
|
||||
--- Switch on/off immortal flag for the group.
|
||||
-- @param #GROUP self
|
||||
-- @param #boolean switch If true, Immortal is enabled. If false, Immortal is disabled.
|
||||
-- @return #GROUP self
|
||||
function GROUP:CommandSetImmortal(switch)
|
||||
self:F2( self.GroupName )
|
||||
if switch==nil then
|
||||
switch=false
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
-- @image Wrapper_Static.JPG
|
||||
|
||||
|
||||
--- @type STATIC
|
||||
---
|
||||
-- @type STATIC
|
||||
-- @extends Wrapper.Positionable#POSITIONABLE
|
||||
|
||||
--- Wrapper class to handle Static objects.
|
||||
@ -236,7 +237,7 @@ function STATIC:SpawnAt(Coordinate, Heading, Delay)
|
||||
end
|
||||
|
||||
|
||||
--- Respawn the @{Wrapper.Unit} at the same location with the same properties.
|
||||
--- Respawn the @{Wrapper.Static} at the same location with the same properties.
|
||||
-- This is useful to respawn a cargo after it has been destroyed.
|
||||
-- @param #STATIC self
|
||||
-- @param DCS#country.id CountryID (Optional) The country ID used for spawning the new static. Default is same as currently.
|
||||
@ -280,3 +281,52 @@ function STATIC:ReSpawnAt(Coordinate, Heading, Delay)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Find the first(!) STATIC matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
|
||||
-- @param #STATIC self
|
||||
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
|
||||
-- @return #STATIC The STATIC.
|
||||
-- @usage
|
||||
-- -- Find a static with a partial static name
|
||||
-- local grp = STATIC:FindByMatching( "Apple" )
|
||||
-- -- will return e.g. a static named "Apple-1-1"
|
||||
--
|
||||
-- -- using a pattern
|
||||
-- local grp = STATIC:FindByMatching( ".%d.%d$" )
|
||||
-- -- will return the first static found ending in "-1-1" to "-9-9", but not e.g. "-10-1"
|
||||
function STATIC:FindByMatching( Pattern )
|
||||
local GroupFound = nil
|
||||
|
||||
for name,static in pairs(_DATABASE.STATICS) do
|
||||
if string.match(name, Pattern ) then
|
||||
GroupFound = static
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return GroupFound
|
||||
end
|
||||
|
||||
--- Find all STATIC objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
|
||||
-- @param #STATIC self
|
||||
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
|
||||
-- @return #table Groups Table of matching #STATIC objects found
|
||||
-- @usage
|
||||
-- -- Find all static with a partial static name
|
||||
-- local grptable = STATIC:FindAllByMatching( "Apple" )
|
||||
-- -- will return all statics with "Apple" in the name
|
||||
--
|
||||
-- -- using a pattern
|
||||
-- local grp = STATIC:FindAllByMatching( ".%d.%d$" )
|
||||
-- -- will return the all statics found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10"
|
||||
function STATIC:FindAllByMatching( Pattern )
|
||||
local GroupsFound = {}
|
||||
|
||||
for name,static in pairs(_DATABASE.STATICS) do
|
||||
if string.match(name, Pattern ) then
|
||||
GroupsFound[#GroupsFound+1] = static
|
||||
end
|
||||
end
|
||||
|
||||
return GroupsFound
|
||||
end
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
--
|
||||
-- ## Example Missions:
|
||||
--
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Storage).
|
||||
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Storage).
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@ -1244,7 +1244,9 @@ function UNIT:GetThreatLevel()
|
||||
|
||||
if Attributes["Fighters"] then ThreatLevel = 10
|
||||
elseif Attributes["Multirole fighters"] then ThreatLevel = 9
|
||||
elseif Attributes["Interceptors"] then ThreatLevel = 9
|
||||
elseif Attributes["Battleplanes"] then ThreatLevel = 8
|
||||
elseif Attributes["Battle airplanes"] then ThreatLevel = 8
|
||||
elseif Attributes["Attack helicopters"] then ThreatLevel = 7
|
||||
elseif Attributes["Strategic bombers"] then ThreatLevel = 6
|
||||
elseif Attributes["Bombers"] then ThreatLevel = 5
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
--
|
||||
-- ## Additional Material:
|
||||
--
|
||||
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper/Weapon)
|
||||
-- * **Demo Missions:** [GitHub](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/Wrapper/Weapon)
|
||||
-- * **YouTube videos:** None
|
||||
-- * **Guides:** None
|
||||
--
|
||||
|
||||
263
docs/advanced/concepts.md
Normal file
263
docs/advanced/concepts.md
Normal 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/
|
||||
8
docs/advanced/debugger.md
Normal file
8
docs/advanced/debugger.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Debugger
|
||||
parent: Advanced
|
||||
nav_order: 100
|
||||
---
|
||||
|
||||
{: .warning }
|
||||
> THIS DOCUMENT IS STILL WORK IN PROGRESS!
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: De-Sanitize DCS
|
||||
parent: Advanced
|
||||
nav_order: 2
|
||||
nav_order: 98
|
||||
---
|
||||
# De-Sanitize the DCS scripting environment
|
||||
{: .no_toc }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
parent: Advanced
|
||||
nav_order: 1
|
||||
nav_order: 97
|
||||
---
|
||||
# Eclipse Installation
|
||||
{: .no_toc }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
parent: Advanced
|
||||
nav_order: 2
|
||||
nav_order: 99
|
||||
---
|
||||
# Text to Speech
|
||||
{: .no_toc }
|
||||
|
||||
@ -35,16 +35,24 @@ Please remember when posting a question:
|
||||
- Before posting anything follow the [troubleshooting steps].
|
||||
- **Read your logs**.
|
||||
|
||||
### Formulate a good description
|
||||
|
||||
A post should contain the following:
|
||||
|
||||
1. A describtion what you expected to happen and what actually happened.
|
||||
- A description what you expected to happen and what actually happened.
|
||||
- Do not use vague words this stuff is hard to help with! Be specific.
|
||||
|
||||
2. Describe what happens instead.
|
||||
- Describe what happens instead.
|
||||
- The less detail you offer, the less chance you can be helped.
|
||||
- Don’t say it doesn’t work. Or is it broken. Say what it actually does.
|
||||
- Don't say it doesn't work. Or is it broken. Say what it actually does.
|
||||
|
||||
3. Post your code in Discord as formatted code:
|
||||
### Format your code
|
||||
|
||||
The easier your code is to read, the more likely you are to get a helpful answer. If your code is hard to read, some
|
||||
people who could help you may not even bother to read your code. Syntax Highlighting makes the code much clearer and
|
||||
easier to understand. Therefore:
|
||||
|
||||
- Post your code in Discord as formatted code:
|
||||
|
||||
- Wrap a single line of code in backticks \` like this:
|
||||
|
||||
@ -54,14 +62,31 @@ A post should contain the following:
|
||||
|
||||

|
||||
|
||||
- Post your log lines with the error or warning messages. Format them like this:
|
||||
### Do not post a screenshot of your code
|
||||
|
||||

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

|
||||
|
||||
### Send your mission when requested
|
||||
|
||||
Please don't just send your mission file. You have to manually extract the script from the file.
|
||||
It is better to send your script code and log lines beforehand.
|
||||
If this does not help, you may be asked to send your mission.
|
||||
|
||||
- Some complex problems need the mission (.miz file) also.
|
||||
|
||||
- But post your mission only when requested.
|
||||
- Try to simplify your mission if it is complex!
|
||||
- Try to avoid or delete MODs, because could prevent people from helping you.
|
||||
|
||||
There are people in the Discord and in the forum, who spend their free time to
|
||||
help you. <br />
|
||||
|
||||
@ -11,10 +11,14 @@ nav_order: 05
|
||||
|
||||
## Something went wrong
|
||||
|
||||
If the mission shows not the expected behaviour do the following steps:
|
||||
If the mission shows not the expected behavior do the following steps:
|
||||
|
||||
1. Double check if you added the changed mission script to the mission again!
|
||||
1. Check if the triggers are configured as requested in the last sections.
|
||||
1. Check if the triggers are configured as requested in the last sections:
|
||||
- To load MOOSE: `4 MISSION START`, nothing on `CONDITIONS`, `DO SCRIPT FILE` to load `Moose_.lua`.
|
||||
- To load mission script(s): `1 ONCE`, in `CONDITIONS` add `TIME MORE` = 1, `DO SCRIPT FILE` to load `yourscript.lua`.
|
||||
1. Double check if you have the right version of MOOSE (some classes need the develop branch).
|
||||
1. Try the newest version of MOOSE.
|
||||
|
||||
## Read the logs
|
||||
|
||||
@ -22,8 +26,7 @@ The DCS log is a super important and useful log for the entire of DCS World.
|
||||
All scripting and other errors are recorded here. It is the one stop shop for
|
||||
things that occurred in your mission. It will tell you if there was a mistake.
|
||||
|
||||
1. Open the file `dcs.log` in the `Logs` subfolder in your DCS
|
||||
[Saved Games folder].
|
||||
1. Open the file `dcs.log` in the `Logs` subfolder in your DCS [Saved Games folder].
|
||||
|
||||
1. Search for the following line: `*** MOOSE INCLUDE END ***`
|
||||
- If it is included in the log, Moose was loaded.
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
Loading…
x
Reference in New Issue
Block a user