Merge branch 'master' into FF/MasterDevel

This commit is contained in:
Frank 2023-01-28 11:09:08 +01:00
commit 50ee1ae922
31 changed files with 2636 additions and 653 deletions

View File

@ -3940,11 +3940,7 @@ do
--
-- # Demo Missions
--
-- ### [AI\_A2A\_GCICAP for Caucasus](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-200%20-%20AI_A2A%20-%20GCICAP%20Demonstration)
-- ### [AI\_A2A\_GCICAP for NTTR](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-210%20-%20NTTR%20AI_A2A_GCICAP%20Demonstration)
-- ### [AI\_A2A\_GCICAP for Normandy](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-220%20-%20NORMANDY%20AI_A2A_GCICAP%20Demonstration)
--
-- ### [AI\_A2A\_GCICAP for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching)
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-A2A%20-%20AI%20A2A%20Dispatching)
--
-- ===
--

View File

@ -23,11 +23,15 @@
-- @type CONDITION
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the condition.
-- @field #boolean isAny General functions are evaluated as any condition.
-- @field #boolean negateResult Negeate result of evaluation.
-- @field #boolean negateResult Negate result of evaluation.
-- @field #boolean noneResult Boolean that is returned if no condition functions at all were specified.
-- @field #table functionsGen General condition functions.
-- @field #table functionsAny Any condition functions.
-- @field #table functionsAll All condition functions.
-- @field #number functionCounter Running number to determine the unique ID of condition functions.
-- @field #boolean defaultPersist Default persistence of condition functions.
--
-- @extends Core.Base#BASE
@ -41,27 +45,34 @@
--
-- @field #CONDITION
CONDITION = {
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
functionCounter = 0,
defaultPersist = false,
}
--- Condition function.
-- @type CONDITION.Function
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
-- @field #number uid Unique ID of the condition function.
-- @field #string type Type of the condition function: "gen", "any", "all".
-- @field #boolean persistence If `true`, this is persistent.
-- @field #function func Callback function to check for a condition. Must return a `#boolean`.
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
--- CONDITION class version.
-- @field #string version
CONDITION.version="0.1.0"
CONDITION.version="0.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Make FSM.
-- TODO: Make FSM. No sure if really necessary.
-- DONE: Option to remove condition functions.
-- DONE: Persistence option for condition functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@ -78,6 +89,8 @@ function CONDITION:New(Name)
self.name=Name or "Condition X"
self:SetNoneResult(false)
self.lid=string.format("%s | ", self.name)
return self
@ -101,6 +114,28 @@ function CONDITION:SetNegateResult(Negate)
return self
end
--- Set whether `true` or `false` is returned, if no conditions at all were specified. By default `false` is returned.
-- @param #CONDITION self
-- @param #boolean ReturnValue Returns this boolean.
-- @return #CONDITION self
function CONDITION:SetNoneResult(ReturnValue)
if not ReturnValue then
self.noneResult=false
else
self.noneResult=true
end
return self
end
--- Set whether condition functions are persistent, *i.e.* are removed.
-- @param #CONDITION self
-- @param #boolean IsPersistent If `true`, condition functions are persistent.
-- @return #CONDITION self
function CONDITION:SetDefaultPersistence(IsPersistent)
self.defaultPersist=IsPersistent
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
@ -113,47 +148,109 @@ end
--
-- myCondition:AddFunction(isAequalB, a, b)
--
-- @return #CONDITION self
-- @return #CONDITION.Function Condition function table.
function CONDITION:AddFunction(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
local condition=self:_CreateCondition(0, Function, ...)
-- Add to table.
table.insert(self.functionsGen, condition)
return self
return condition
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
-- @return #CONDITION.Function Condition function table.
function CONDITION:AddFunctionAny(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
local condition=self:_CreateCondition(1, Function, ...)
-- Add to table.
table.insert(self.functionsAny, condition)
return self
return condition
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
-- @return #CONDITION.Function Condition function table.
function CONDITION:AddFunctionAll(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
local condition=self:_CreateCondition(2, Function, ...)
-- Add to table.
table.insert(self.functionsAll, condition)
return condition
end
--- Remove a condition function.
-- @param #CONDITION self
-- @param #CONDITION.Function ConditionFunction The condition function to be removed.
-- @return #CONDITION self
function CONDITION:RemoveFunction(ConditionFunction)
if ConditionFunction then
local data=nil
if ConditionFunction.type==0 then
data=self.functionsGen
elseif ConditionFunction.type==1 then
data=self.functionsAny
elseif ConditionFunction.type==2 then
data=self.functionsAll
end
if data then
for i=#data,1,-1 do
local cf=data[i] --#CONDITION.Function
if cf.uid==ConditionFunction.uid then
self:T(self.lid..string.format("Removed ConditionFunction UID=%d", cf.uid))
table.remove(data, i)
return self
end
end
end
end
return self
end
--- Remove all non-persistant condition functions.
-- @param #CONDITION self
-- @return #CONDITION self
function CONDITION:RemoveNonPersistant()
for i=#self.functionsGen,1,-1 do
local cf=self.functionsGen[i] --#CONDITION.Function
if not cf.persistence then
table.remove(self.functionsGen, i)
end
end
for i=#self.functionsAll,1,-1 do
local cf=self.functionsAll[i] --#CONDITION.Function
if not cf.persistence then
table.remove(self.functionsAll, i)
end
end
for i=#self.functionsAny,1,-1 do
local cf=self.functionsAny[i] --#CONDITION.Function
if not cf.persistence then
table.remove(self.functionsAny, i)
end
end
return self
end
@ -166,11 +263,7 @@ function CONDITION:Evaluate(AnyTrue)
-- Check if at least one function was given.
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
if self.negateResult then
return true
else
return false
end
return self.noneResult
end
-- Any condition for gen.
@ -206,6 +299,10 @@ function CONDITION:Evaluate(AnyTrue)
return result
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check if all given condition are true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
@ -275,13 +372,20 @@ end
--- Create conditon function object.
-- @param #CONDITION self
-- @param #number Ftype Function type: 0=Gen, 1=All, 2=Any.
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION.Function Condition function.
function CONDITION:_CreateCondition(Function, ...)
function CONDITION:_CreateCondition(Ftype, Function, ...)
-- Increase counter.
self.functionCounter=self.functionCounter+1
local condition={} --#CONDITION.Function
condition.uid=self.functionCounter
condition.type=Ftype or 0
condition.persistence=self.defaultPersist
condition.func=Function
condition.arg={}
if arg then
@ -290,6 +394,71 @@ function CONDITION:_CreateCondition(Function, ...)
return condition
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Global Condition Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Condition to check if time is greater than a given threshold time.
-- @param #number Time Time in seconds.
-- @param #boolean Absolute If `true`, abs. mission time from `timer.getAbsTime()` is checked. Default is relative mission time from `timer.getTime()`.
-- @return #boolean Returns `true` if time is greater than give the time.
function CONDITION.IsTimeGreater(Time, Absolute)
local Tnow=nil
if Absolute then
Tnow=timer.getAbsTime()
else
Tnow=timer.getTime()
end
if Tnow>Time then
return true
else
return false
end
return nil
end
--- Function that returns `true` (success) with a certain probability. For example, if you specify `Probability=80` there is an 80% chance that `true` is returned.
-- Technically, a random number between 0 and 100 is created. If the given success probability is less then this number, `true` is returned.
-- @param #number Probability Success probability in percent. Default 50 %.
-- @return #boolean Returns `true` for success and `false` otherwise.
function CONDITION.IsRandomSuccess(Probability)
Probability=Probability or 50
-- Create some randomness.
math.random()
math.random()
math.random()
-- Number between 0 and 100.
local N=math.random()*100
if N<Probability then
return true
else
return false
end
end
--- Function that returns always `true`
-- @return #boolean Returns `true` unconditionally.
function CONDITION.ReturnTrue()
return true
end
--- Function that returns always `false`
-- @return #boolean Returns `false` unconditionally.
function CONDITION.ReturnFalse()
return false
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -1027,11 +1027,26 @@ function DATABASE:_RegisterAirbases()
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
self:_RegisterAirbase(DCSAirbase)
end
return self
end
--- Register a DCS airbase.
-- @param #DATABASE self
-- @param DCS#Airbase airbase Airbase.
-- @return #DATABASE self
function DATABASE:_RegisterAirbase(airbase)
if airbase then
-- Get the airbase name.
local DCSAirbaseName = DCSAirbase:getName()
local DCSAirbaseName = airbase:getName()
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=DCSAirbase:getID()
local airbaseID=airbase:getID()
-- Add and register airbase.
local airbase=self:AddAirbase( DCSAirbaseName )
@ -1065,20 +1080,23 @@ function DATABASE:_EventOnBirth( Event )
if Event.IniDCSUnit then
if Event.IniObjectCategory == 3 then
if Event.IniObjectCategory == Object.Category.STATIC then
-- Add static object to DB.
self:AddStatic( Event.IniDCSUnitName )
else
if Event.IniObjectCategory == 1 then
if Event.IniObjectCategory == Object.Category.UNIT then
-- Add unit and group to DB.
self:AddUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName )
-- Add airbase if it was spawned later in the mission.
-- A unit can also be an airbase (e.g. ships).
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
-- Add airbase if it was spawned later in the mission.
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
@ -1086,7 +1104,7 @@ function DATABASE:_EventOnBirth( Event )
end
end
if Event.IniObjectCategory == 1 then
if Event.IniObjectCategory == Object.Category.UNIT then
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )

View File

@ -1130,7 +1130,7 @@ function EVENT:onEvent( Event )
Event.IniUnitName = Event.IniDCSUnitName
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
if not Event.IniUnit then
-- Unit can be a CLIENT. Most likely this will be the case ...
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
@ -1165,8 +1165,7 @@ function EVENT:onEvent( Event )
if Event.IniObjectCategory == Object.Category.SCENERY then
---
-- Scenery
---
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
@ -1186,6 +1185,12 @@ function EVENT:onEvent( Event )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
-- If the airbase does not exist in the DB, we add it (e.g. when FARPS are spawned).
if not Event.IniUnit then
_DATABASE:_RegisterAirbase(Event.initiator)
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
end
end
end
@ -1219,7 +1224,7 @@ function EVENT:onEvent( Event )
if Event.TgtObjectCategory == Object.Category.STATIC then
-- get base data
Event.TgtDCSUnit = Event.target
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
if Event.target:isExist() and Event.id ~= 33 and not Event.TgtObjectCategory == Object.Category.COORDINATE then -- leave out ejected seat object
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
Event.TgtUnitName = Event.TgtDCSUnitName
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
@ -1275,9 +1280,11 @@ function EVENT:onEvent( Event )
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
--Event.Place=UNIT:Find(Event.place)
else
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
else
if Event.place:isExist() and Event.place:getCategory() ~= Object.Category.SCENERY then
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
end
end
end

View File

@ -5,6 +5,12 @@
-- * Create an easy way to tap into markers added to the F10 map by users.
-- * Recognize own tag and list of keywords.
-- * Matched keywords are handed down to functions.
-- ##Listen for your tag
-- myMarker = MARKEROPS_BASE:New("tag", {}, false)
-- function myMarker:OnAfterMarkChanged(From, Event, To, Text, Keywords, Coord, idx)
--
-- end
-- Make sure to use the "MarkChanged" event as "MarkAdded" comes in right after the user places a blank marker and your callback will never be called.
--
-- ===
--

View File

@ -98,7 +98,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub( -1 ) ~= "\n" then
self.MessageCategory = MessageCategory .. ": "
@ -204,14 +204,14 @@ function MESSAGE:ToClient( Client, Settings )
local Unit = Client:GetClient()
if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
end
end
return self
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
end
end
return self
end
--- Sends a MESSAGE to a Group.
@ -433,3 +433,24 @@ function MESSAGE:ToAllIf( Condition )
return self
end
--- Sends a MESSAGE to DCS log file.
-- @param #MESSAGE self
-- @return #MESSAGE self
function MESSAGE:ToLog()
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
return self
end
--- Sends a MESSAGE to DCS log file if the given Condition is true.
-- @param #MESSAGE self
-- @return #MESSAGE self
function MESSAGE:ToLogIf( Condition )
if Condition and Condition == true then
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
end
return self
end

View File

@ -406,6 +406,42 @@ do -- COORDINATE
return self
end
--- Returns the magnetic declination at the given coordinate.
-- NOTE that this needs `require` to be available so you need to desanitize the `MissionScripting.lua` file in your DCS/Scrips folder.
-- If `require` is not available, a constant value for the whole map.
-- @param #COORDINATE self
-- @param #number Month (Optional) The month at which the declination is calculated. Default is the mission month.
-- @param #number Year (Optional) The year at which the declination is calculated. Default is the mission year.
-- @return #number Magnetic declination in degrees.
function COORDINATE:GetMagneticDeclination(Month, Year)
local decl=UTILS.GetMagneticDeclination()
if require then
local magvar = require('magvar')
if magvar then
local date, year, month, day=UTILS.GetDCSMissionDate()
magvar.init(Month or month, Year or year)
local lat, lon=self:GetLLDDM()
decl=magvar.get_mag_decl(lat, lon)
if decl then
decl=math.deg(decl)
end
end
else
self:T("The require package is not available. Using constant value for magnetic declination")
end
return decl
end
--- Returns the coordinate from the latitude and longitude given in decimal degrees.
-- @param #COORDINATE self

File diff suppressed because it is too large Load Diff

View File

@ -389,7 +389,7 @@ end
-- @param #SPAWN self
-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure!
-- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn.
-- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime.
-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
-- @return #SPAWN
-- @usage
-- -- Create a new SPAWN object based on a Group Template defined from scratch.
@ -403,7 +403,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
local self = BASE:Inherit( self, BASE:New() )
self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } )
if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then
BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" )
BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" )
return nil
end
@ -3038,6 +3038,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
if type( Callsign ) ~= "number" then -- blue callsign
Callsign[2] = ((SpawnIndex - 1) % 10) + 1
local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string
CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers
local CallsignLen = CallsignName:len()
SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]
else

View File

@ -275,7 +275,7 @@ end
--- Initialize as dead.
-- @param #SPAWNSTATIC self
-- @param #boolean IsCargo If true, this static is dead.
-- @param #boolean IsDead If true, this static is dead.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitDead(IsDead)
self.InitStaticDead=IsDead
@ -467,7 +467,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
self:T(Template)
-- Add static to the game.
local Static=nil
local Static=nil --DCS#StaticObject
if self.InitFarp then
@ -487,6 +487,17 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
-- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
-- Currently DCS 2.8 does not trigger birth events if FAPRS are spawned!
-- We create such an event. The airbase is registered in Core.Event
local Event = {
id = EVENTS.Birth,
time = timer.getTime(),
initiator = Static
}
-- Create BIRTH event.
world.onEvent(Event)
else
self:T("Spawning Static")
self:T2({Template=Template})

View File

@ -155,7 +155,7 @@ function TIMER:New(Function, ...)
return self
end
--- Create a new TIMER object.
--- Start TIMER object.
-- @param #TIMER self
-- @param #number Tstart Relative start time in seconds.
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
@ -192,6 +192,20 @@ function TIMER:Start(Tstart, dT, Duration)
return self
end
--- Start TIMER object if a condition is met. Useful for e.g. debugging.
-- @param #TIMER self
-- @param #boolean Condition Must be true for the TIMER to start
-- @param #number Tstart Relative start time in seconds.
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
-- @return #TIMER self
function TIMER:StartIf(Condition,Tstart, dT, Duration)
if Condition then
self:Start(Tstart, dT, Duration)
end
return self
end
--- Stop the timer by removing the timer function.
-- @param #TIMER self
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.

View File

@ -1387,7 +1387,7 @@ end
-- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 100 times to find the right type!
-- @return Core.Point#COORDINATE The random coordinate.
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
@ -2084,6 +2084,52 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
return self
end
--- Get the smallest circular zone encompassing all points points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
local center=self:GetVec2()
local radius=0
for _,_vec2 in pairs(self._.Polygon) do
local vec2=_vec2 --DCS#Vec2
local r=UTILS.VecDist2D(center, vec2)
if r>radius then
radius=r
end
end
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone)
return zone
end
--- Get the smallest rectangular zone encompassing all points points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_POLYGON The rectangular zone.
function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone)
local vec1, vec3=self:GetBoundingVec2()
local vec2={x=vec1.x, y=vec3.y}
local vec4={x=vec3.x, y=vec1.y}
local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4})
return zone
end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
@ -2286,6 +2332,32 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
end
--- Get the bounding 2D vectors of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 Coordinates of western-southern-lower vertex of the box.
-- @return DCS#Vec2 Coordinates of eastern-northern-upper vertex of the box.
function ZONE_POLYGON_BASE:GetBoundingVec2()
local x1 = self._.Polygon[1].x
local y1 = self._.Polygon[1].y
local x2 = self._.Polygon[1].x
local y2 = self._.Polygon[1].y
for i = 2, #self._.Polygon do
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2
end
local vec1={x=x1, y=y1}
local vec2={x=x2, y=y2}
return vec1, vec2
end
--- Draw a frontier on the F10 map with small filled circles.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.

View File

@ -341,9 +341,23 @@ do -- coalition
-- @field RED
-- @field BLUE
--- @function [parent=#coalition] getCountryCoalition
-- @param #number countryId
-- @return #number coalitionId
--- Get country coalition.
-- @function [parent=#coalition] getCountryCoalition
-- @param #number countryId Country ID.
-- @return #number coalitionId Coalition ID.
--- Dynamically spawns a group. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
-- @function [parent=#coalition] addGroup
-- @param #number countryId Id of the country.
-- @param #number groupCategory Group category. Set -1 for spawning FARPS.
-- @param #table groupData Group data table.
-- @return DCS#Group The spawned Group object.
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
-- @function [parent=#coalition] addStaticObject
-- @param #number countryId Id of the country.
-- @param #table groupData Group data table.
-- @return DCS#Static The spawned static object.
coalition = {} -- #coalition
@ -1294,6 +1308,42 @@ do -- Group
end -- Group
do -- StaticObject
--- Represents a static object.
-- @type StaticObject
-- @extends DCS#Object
--- Returns the static object.
-- @function [parent=#StaticObject] getByName
-- @param #string name Name of the static object.
-- @return #StaticObject
StaticObject = {} --#StaticObject
end
do --Event
--- Event structure. Note that present fields depend on type of event.
-- @type Event
-- @field #number id Event ID.
-- @field #number time Mission time in seconds.
-- @field DCS#Unit initiator Unit initiating the event.
-- @field DCS#Unit target Target unit.
-- @field DCS#Airbase place Airbase.
-- @field number subPlace Subplace. Unknown and often just 0.
-- @field #string weapon_name Weapoin name.
-- @field #number idx Mark ID.
-- @field #number coalition Coalition ID.
-- @field #number groupID Group ID, *e.g.* of group that added mark point.
-- @field #string text Text, *e.g.* of mark point.
-- @field DCS#Vec3 pos Position vector, *e.g.* of mark point.
-- @field #string comment Comment, *e.g.* LSO score.
Event={} --#Event
end
do -- AI

View File

@ -45,6 +45,7 @@
-- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec.
-- @field #boolean chatty Display some messages on events like take-off and touchdown.
-- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler.
-- @field #boolean reportplayername If true, use playername not callsign on callouts
-- @extends Core.Base#BASE
--- Adds some rudimentary ATC functionality via the radio menu.
@ -88,6 +89,7 @@ PSEUDOATC={
talt=3,
chatty=true,
eventsmoose=true,
reportplayername = false,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -98,7 +100,7 @@ PSEUDOATC.id="PseudoATC | "
--- PSEUDOATC version.
-- @field #number version
PSEUDOATC.version="0.9.2"
PSEUDOATC.version="0.9.5"
-----------------------------------------------------------------------------------------------------------------------------------------
@ -183,6 +185,13 @@ function PSEUDOATC:SetMessageDuration(duration)
self.mdur=duration or 30
end
--- Use player name, not call sign, in callouts
-- @param #PSEUDOATC self
function PSEUDOATC:SetReportPlayername()
self.reportplayername = true
return self
end
--- Set time interval after which the F10 radio menu is refreshed.
-- @param #PSEUDOATC self
-- @param #number interval Interval in seconds. Default is every 120 sec.
@ -441,14 +450,18 @@ function PSEUDOATC:PlayerLanded(unit, place)
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
--local PlayerName=self.group[GID].player[UID].playername
--local UnitName=self.group[GID].player[UID].unitname
--local GroupName=self.group[GID].player[UID].groupname
local PlayerName = unit:GetPlayerName() or "Ghost"
local UnitName = unit:GetName() or "Ghostplane"
local GroupName = group:GetName() or "Ghostgroup"
if self.Debug then
-- Debug message.
local text=string.format("Player %s in unit %s of group %s landed at %s.", PlayerName, UnitName, GroupName, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
end
-- Stop altitude reporting timer if its activated.
self:AltitudeTimerStop(GID,UID)
@ -470,21 +483,28 @@ function PSEUDOATC:PlayerTakeOff(unit, place)
-- Gather some information.
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
--local GID=group:GetID()
--local UID=unit:GetDCSObject():getID()
--local PlayerName=self.group[GID].player[UID].playername
--local CallSign=self.group[GID].player[UID].callsign
--local UnitName=self.group[GID].player[UID].unitname
--local GroupName=self.group[GID].player[UID].groupname
local PlayerName = unit:GetPlayerName() or "Ghost"
local UnitName = unit:GetName() or "Ghostplane"
local GroupName = group:GetName() or "Ghostgroup"
local CallSign = unit:GetCallsign() or "Ghost11"
if self.Debug then
-- Debug message.
local text=string.format("Player %s in unit %s of group %s took off at %s.", PlayerName, UnitName, GroupName, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
end
-- Bye-Bye message.
if place and self.chatty then
local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign)
if self.reportplayername then
text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName)
end
MESSAGE:New(text, self.mdur):ToGroup(group)
end
@ -501,7 +521,7 @@ function PSEUDOATC:PlayerLeft(unit)
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
if self.group[GID].player[UID] then
if self.group[GID] and self.group[GID].player and self.group[GID].player[UID] then
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
@ -687,7 +707,9 @@ function PSEUDOATC:MenuWaypoints(GID, UID)
-- Position of Waypoint
local pos=COORDINATE:New(wp.x, wp.alt, wp.y)
local name=string.format("Waypoint %d", i-1)
if wp.name and wp.name ~= "" then
name = string.format("Waypoint %s",wp.name)
end
-- "F10/PseudoATC/Waypoints/Waypoint X"
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints)
@ -844,7 +866,8 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
local position=unit:GetCoordinate()
local height=get_AGL(position)
local callsign=unit:GetCallsign()
local PlayerName=self.group[GID].player[UID].playername
-- Settings.
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
@ -856,7 +879,9 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
-- Message text.
local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs)
if self.reportplayername then
_text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs)
end
-- Append flight level.
if _clear==false then
_text=_text..string.format(" FL%03d.", position.y/30.48)

View File

@ -3492,7 +3492,7 @@ function RAT:Status(message, forID)
local fuel=group:GetFuel()*100.0
local airborne=group:InAir()
local coords=group:GetCoordinate()
local alt=coords.y
local alt=coords.y or 1000
--local vel=group:GetVelocityKMH()
local departure=ratcraft.departure:GetName()
local destination=ratcraft.destination:GetName()
@ -5671,6 +5671,9 @@ function RAT:_ATCClearForLanding(airport, flight)
-- Debug message.
local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).", airport, flight, flagvalue)
if string.find(flight,"#") then
flight = string.match(flight,"^(.+)#")
end
local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight)
BASE:T( RAT.id..text1)
MESSAGE:New(text2, 10):ToAllIf(RAT.ATC.messages)
@ -5713,6 +5716,9 @@ function RAT:_ATCFlightLanded(name)
local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60)
local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal)
local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour)
if string.find(name,"#") then
name = string.match(name,"^(.+)#")
end
local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest)
BASE:T(RAT.id..text1)
BASE:T(RAT.id..text2)

View File

@ -578,7 +578,7 @@ RANGE.MenuF10Root = nil
--- Range script version.
-- @field #string version
RANGE.version = "2.5.0"
RANGE.version = "2.5.1"
-- TODO list:
-- TODO: Verbosity level for messages.
@ -1207,13 +1207,18 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.controlmsrs:SetLabel("RANGEC")
self.controlsrsQ = MSRSQUEUE:New("CONTROL")
self.instructmsrs=MSRS:New(PathToSRS, Frequency or 305, Modulation or radio.modulation.AM, Volume or 1.0)
self.instructmsrs:SetPort(Port)
self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE)
self.instructmsrs:SetLabel("RANGEI")
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then
self.instructmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
@ -2570,7 +2575,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
self:F( _unitName )
-- Get player unit and name
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName )
if _unit and _playername then
@ -2622,7 +2627,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
end
-- Send message to group.
self:_DisplayMessageToGroup( _unit, _message, nil, true, true )
self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer )
end
end
@ -2633,7 +2638,7 @@ function RANGE:_DisplayStrafePitResults( _unitName )
self:F( _unitName )
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName )
-- Check if we have a unit which is a player.
if _unit and _playername then
@ -2680,7 +2685,7 @@ function RANGE:_DisplayStrafePitResults( _unitName )
end
-- Send message.
self:_DisplayMessageToGroup( _unit, _message, nil, true, true )
self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer )
end
end
@ -2691,7 +2696,7 @@ function RANGE:_DisplayMyBombingResults( _unitName )
self:F( _unitName )
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName )
if _unit and _playername then
@ -2737,7 +2742,7 @@ function RANGE:_DisplayMyBombingResults( _unitName )
end
-- Send message.
self:_DisplayMessageToGroup( _unit, _message, nil, true, true )
self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer )
end
end
@ -2751,7 +2756,7 @@ function RANGE:_DisplayBombingResults( _unitName )
local _playerResults = {}
-- Get player unit and name.
local _unit, _player = self:_GetPlayerUnitAndName( _unitName )
local _unit, _player, _multiplayer = self:_GetPlayerUnitAndName( _unitName )
-- Check if we have a unit with a player.
if _unit and _player then
@ -2795,7 +2800,7 @@ function RANGE:_DisplayBombingResults( _unitName )
end
-- Send message.
self:_DisplayMessageToGroup( _unit, _message, nil, true, true )
self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer )
end
end
@ -2806,7 +2811,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
self:F( _unitname )
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName( _unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname )
-- Check if we have a player.
if unit and playername then
@ -2901,7 +2906,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
text = text .. textdelay
-- Send message to player group.
self:_DisplayMessageToGroup( unit, text, nil, true, true )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output.
self:T2( self.id .. text )
@ -2916,7 +2921,7 @@ function RANGE:_DisplayBombTargets( _unitname )
self:F( _unitname )
-- Get player unit and player name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitname )
local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname )
-- Check if we have a player.
if _unit and _playername then
@ -2948,7 +2953,7 @@ function RANGE:_DisplayBombTargets( _unitname )
end
end
self:_DisplayMessageToGroup( _unit, _text, 120, true, true )
self:_DisplayMessageToGroup( _unit, _text, 120, true, true, _multiplayer )
end
end
@ -2959,7 +2964,7 @@ function RANGE:_DisplayStrafePits( _unitname )
self:F( _unitname )
-- Get player unit and player name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitname )
local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname )
-- Check if we have a player.
if _unit and _playername then
@ -2988,7 +2993,7 @@ function RANGE:_DisplayStrafePits( _unitname )
_text = _text .. string.format( "\n- %s: heading %03d°\n%s", _strafepit.name, heading, mycoord )
end
self:_DisplayMessageToGroup( _unit, _text, nil, true, true )
self:_DisplayMessageToGroup( _unit, _text, nil, true, true, _multiplayer )
end
end
@ -2999,7 +3004,7 @@ function RANGE:_DisplayRangeWeather( _unitname )
self:F( _unitname )
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName( _unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname )
-- Check if we have a player.
if unit and playername then
@ -3048,7 +3053,7 @@ function RANGE:_DisplayRangeWeather( _unitname )
end
-- Send message to player group.
self:_DisplayMessageToGroup( unit, text, nil, true, true )
self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer )
-- Debug output.
self:T2( self.id .. text )
@ -3666,7 +3671,8 @@ end
-- @param #number _time Duration how long the message is displayed.
-- @param #boolean _clear Clear up old messages.
-- @param #boolean display If true, display message regardless of player setting "Messages Off".
function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
-- @param #boolean _togroup If true, display the message to the group in any case
function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display, _togroup )
self:F( { unit = _unit, text = _text, time = _time, clear = _clear } )
-- Defaults
@ -3694,8 +3700,13 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
local playermessage = self.PlayerSettings[playername].messages
-- Send message to player if messages enabled and not only for the examiner.
if _gid and (playermessage == true or display) and (not self.examinerexclusive) then
local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
if _togroup and _grp then
local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp)
else
local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
end
end
-- Send message to examiner.
@ -3715,7 +3726,7 @@ end
function RANGE:_SmokeBombImpactOnOff( unitname )
self:F( unitname )
local unit, playername = self:_GetPlayerUnitAndName( unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname )
if unit and playername then
local text
if self.PlayerSettings[playername].smokebombimpact == true then
@ -3736,7 +3747,7 @@ end
function RANGE:_SmokeBombDelayOnOff( unitname )
self:F( unitname )
local unit, playername = self:_GetPlayerUnitAndName( unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname )
if unit and playername then
local text
if self.PlayerSettings[playername].delaysmoke == true then
@ -3757,7 +3768,7 @@ end
function RANGE:_MessagesToPlayerOnOff( unitname )
self:F( unitname )
local unit, playername = self:_GetPlayerUnitAndName( unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname )
if unit and playername then
local text
if self.PlayerSettings[playername].messages == true then
@ -3778,7 +3789,7 @@ function RANGE:_TargetsheetOnOff( _unitname )
self:F2( _unitname )
-- Get player unit and player name.
local unit, playername = self:_GetPlayerUnitAndName( _unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname )
-- Check if we have a player.
if unit and playername then
@ -3820,7 +3831,7 @@ end
function RANGE:_FlareDirectHitsOnOff( unitname )
self:F( unitname )
local unit, playername = self:_GetPlayerUnitAndName( unitname )
local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname )
if unit and playername then
local text
if self.PlayerSettings[playername].flaredirecthits == true then
@ -4039,12 +4050,14 @@ end
-- @param #string _unitName Name of the player unit.
-- @return Wrapper.Unit#UNIT Unit of player.
-- @return #string Name of the player.
-- @return nil If player does not exist.
-- @return #boolean If true, group has > 1 player in it
function RANGE:_GetPlayerUnitAndName( _unitName )
self:F2( _unitName )
if _unitName ~= nil then
local multiplayer = false
-- Get DCS unit from its name.
local DCSunit = Unit.getByName( _unitName )
@ -4056,7 +4069,11 @@ function RANGE:_GetPlayerUnitAndName( _unitName )
self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } )
if DCSunit and unit and playername then
self:F2(playername)
return unit, playername
local grp = unit:GetGroup()
if grp and grp:CountAliveUnits() > 1 then
multiplayer = true
end
return unit, playername, multiplayer
end
end
@ -4064,7 +4081,7 @@ function RANGE:_GetPlayerUnitAndName( _unitName )
end
-- Return nil if we could not find a player.
return nil, nil
return nil, nil, nil
end
--- Returns a string which consists of the player name.

View File

@ -85,6 +85,7 @@
-- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true.
-- @field Core.Zone#ZONE BattleZone
-- @field #boolean AutoEngage
-- @field #table waypoints Waypoints of the group as defined in the ME.
-- @extends Core.Fsm#FSM_CONTROLLABLE
--
@ -265,6 +266,7 @@ SUPPRESSION={
DefaultAlarmState = "Auto",
DefaultROE = "Weapon Free",
eventmoose = true,
waypoints = {},
}
--- Enumerator of possible rules of engagement.
@ -295,7 +297,7 @@ SUPPRESSION.MenuF10=nil
--- PSEUDOATC version.
-- @field #number version
SUPPRESSION.version="0.9.3"
SUPPRESSION.version="0.9.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -309,7 +311,7 @@ SUPPRESSION.version="0.9.3"
--- Creates a new AI_suppression object.
-- @param #SUPPRESSION self
-- @param Wrapper.Group#GROUP group The GROUP object for which suppression should be applied.
-- @return #SUPPRESSION SUPPRESSION object or *nil* if group does not exist or is not a ground group.
-- @return #SUPPRESSION self
function SUPPRESSION:New(group)
-- Inherits from FSM_CONTROLLABLE
@ -320,7 +322,7 @@ function SUPPRESSION:New(group)
self.lid=string.format("SUPPRESSION %s | ", tostring(group:GetName()))
self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName()))
else
self:E(self.lid.."SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group.)")
self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)")
return nil
end
@ -1186,6 +1188,16 @@ function SUPPRESSION:onafterFightBack(Controllable, From, Event, To)
-- Set ROE and alarm state back to default.
self:_SetROE()
self:_SetAlarmState()
local group=Controllable --Wrapper.Group#GROUP
local Waypoints = group:GetTemplateRoutePoints()
-- env.info("FF waypoints",showMessageBox)
-- self:I(Waypoints)
group:Route(Waypoints, 5)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1251,7 +1263,7 @@ function SUPPRESSION:onafterFallBack(Controllable, From, Event, To, AttackUnit)
self:_SetROE(SUPPRESSION.ROE.Hold)
-- Set alarm state to GREEN and let the unit run away.
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
-- Make the group run away.
self:_Run(Coord, self.Speed, self.Formation, self.FallbackWait)
@ -1537,7 +1549,7 @@ end
-- @param #SUPPRESSION self
-- @param Core.Event#EVENTDATA EventData
function SUPPRESSION:_OnEventHit(EventData)
self:F(EventData)
self:F3(EventData)
local GroupNameSelf=self.Controllable:GetName()
local GroupNameTgt=EventData.TgtGroupName
@ -1676,15 +1688,15 @@ end
function SUPPRESSION:_Run(fin, speed, formation, wait)
speed=speed or 20
formation=formation or "Off road"
formation=formation or ENUMS.Formation.Vehicle.OffRoad
wait=wait or 30
local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE
local group=self.Controllable -- Wrapper.Group#GROUP
if group and group:IsAlive() then
-- Clear all tasks.
group:ClearTasks()
--group:ClearTasks()
-- Current coordinates of group.
local ini=group:GetCoordinate()
@ -1694,57 +1706,18 @@ function SUPPRESSION:_Run(fin, speed, formation, wait)
-- Heading from ini to fin.
local heading=self:_Heading(ini, fin)
-- Number of waypoints.
local nx
if dist <= 50 then
nx=2
elseif dist <= 100 then
nx=3
elseif dist <= 500 then
nx=4
else
nx=5
end
-- Number of intermediate waypoints.
local dx=dist/(nx-1)
-- Waypoint and task arrays.
local wp={}
local tasks={}
-- First waypoint is the current position of the group.
wp[1]=ini:WaypointGround(speed, formation)
tasks[1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, 1, false)
if self.Debug then
local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)", #wp, self.Controllable:GetName()))
end
self:T2(self.lid..string.format("Number of waypoints %d", nx))
for i=1,nx-2 do
local x=dx*i
local coord=ini:Translate(x, heading)
wp[#wp+1]=coord:WaypointGround(speed, formation)
tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, false)
self:T2(self.lid..string.format("%d x = %4.1f", i, x))
if self.Debug then
local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s", #wp, self.Controllable:GetName()))
end
end
self:T2(self.lid..string.format("Total distance: %4.1f", dist))
-- Final waypoint.
wp[#wp+1]=fin:WaypointGround(speed, formation)
if self.Debug then
local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName()))
end
-- Task to hold.
local ConditionWait=group:TaskCondition(nil, nil, nil, nil, wait, nil)
local TaskHold = group:TaskHold()
@ -1753,25 +1726,15 @@ function SUPPRESSION:_Run(fin, speed, formation, wait)
local TaskComboFin = {}
TaskComboFin[#TaskComboFin+1] = group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, true)
TaskComboFin[#TaskComboFin+1] = group:TaskControlled(TaskHold, ConditionWait)
-- Add final task.
tasks[#tasks+1]=group:TaskCombo(TaskComboFin)
-- Original waypoints of the group.
local Waypoints = group:GetTemplateRoutePoints()
-- New points are added to the default route.
for i,p in ipairs(wp) do
table.insert(Waypoints, i, wp[i])
end
-- Set task for all waypoints.
for i,wp in ipairs(Waypoints) do
group:SetTaskWaypoint(Waypoints[i], tasks[i])
end
-- Final waypoint.
wp[#wp+1]=fin:WaypointGround(speed, formation, TaskComboFin)
if self.Debug then
local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName()))
end
-- Submit task and route group along waypoints.
group:Route(Waypoints)
group:Route(wp)
else
self:E(self.lid..string.format("ERROR: Group is not alive!"))
@ -1790,7 +1753,7 @@ function SUPPRESSION._Passing_Waypoint(group, Fsm, i, final)
local text=string.format("Group %s passing waypoint %d (final=%s)", group:GetName(), i, tostring(final))
MESSAGE:New(text,10):ToAllIf(Fsm.Debug)
if Fsm.Debug then
env.info(self.lid..text)
env.info(Fsm.lid..text)
end
if final then
@ -1891,7 +1854,7 @@ function SUPPRESSION:_GetLife()
local groupstrength=#units/self.IniGroupStrength*100
self.T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units))
self:T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units))
for _,unit in pairs(units) do

View File

@ -302,8 +302,8 @@
--
-- Initial Spawn states is as follows:
-- GROUND: ROE, "Return Fire" Alarm, "Green"
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
--
-- A request can be added by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function.
-- The parameters are
@ -2647,6 +2647,13 @@ function WAREHOUSE:SetWarehouseZone(zone)
return self
end
--- Get the warehouse zone.
-- @param #WAREHOUSE self
-- @return Core.Zone#ZONE The warehouse zone.
function WAREHOUSE:GetWarehouseZone()
return self.zone
end
--- Set auto defence on. When the warehouse is under attack, all ground assets are spawned automatically and will defend the warehouse zone.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
@ -5810,6 +5817,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
-- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning.
local Parking={}
if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then
--TODO: Check for airstart. Should be a request property.
Parking=self:_FindParkingForAssets(self.airbase, cargoassets) or {}
end
@ -6069,7 +6077,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
end
if self.Debug then
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
coord:MarkToAll(text)
env.info(text)
end
unit.x=coord.x
@ -7374,6 +7384,7 @@ function WAREHOUSE:_CheckRequestNow(request)
local _transports
local _assetattribute
local _assetcategory
local _assetairstart=false
-- Check if at least one (cargo) asset is available.
if _nassets>0 then
@ -7381,21 +7392,28 @@ function WAREHOUSE:_CheckRequestNow(request)
-- Get the attibute of the requested asset.
_assetattribute=_assets[1].attribute
_assetcategory=_assets[1].category
_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
-- Check available parking for air asset units.
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
if self:IsRunwayOperational() then
if self:IsRunwayOperational() or _assetairstart then
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
--if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
self:_InfoMessage(text, 5)
return false
if _assetairstart then
-- Airstart no need to check parking
else
-- Check parking.
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
-- No parking?
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
self:_InfoMessage(text, 5)
return false
end
end
else
@ -7969,93 +7987,123 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
-- Loop over all assets that need a parking psot.
for _,asset in pairs(assets) do
local _asset=asset --#WAREHOUSE.Assetitem
-- Get terminal type of this asset
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking.
parking[_asset.uid]={}
-- Loop over all units - each one needs a spot.
for i=1,_asset.nunits do
-- Asset name
local assetname=_asset.spawngroupname.."-"..tostring(i)
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
free=false
problem=obstacle
problem.dist=dist
break
else
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this asset unit.
table.insert(parking[_asset.uid], parkingspot)
-- Debug
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
gotit=true
break
if not _asset.spawned then
-- Get terminal type of this asset
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking.
parking[_asset.uid]={}
-- Loop over all units - each one needs a spot.
for i=1,_asset.nunits do
-- Asset name
local assetname=_asset.spawngroupname.."-"..tostring(i)
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Parking valid?
local valid=true
if asset.parkingIDs then
-- If asset has assigned parking spots, we take these no matter what.
valid=self:_CheckParkingAsset(parkingspot, asset)
else
-- Debug output for occupied spots.
if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
self:I(self.lid..text)
coord:MarkToAll(string.format(text))
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
end
-- Valid terminal type depending on attribute.
local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype)
-- Valid parking list.
local validParking=self:_CheckParkingValid(parkingspot)
-- Black and white list.
local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID)
-- Debug info.
--env.info(string.format("FF validTerminal = %s", tostring(validTerminal)))
--env.info(string.format("FF validParking = %s", tostring(validParking)))
--env.info(string.format("FF validBWlist = %s", tostring(validBWlist)))
-- Check if all are true
valid=validTerminal and validParking and validBWlist
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
return nil
end
end -- loop over asset units
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if valid then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
free=false
problem=obstacle
problem.dist=dist
break
else
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this asset unit.
table.insert(parking[_asset.uid], parkingspot)
-- Debug
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
gotit=true
break
else
-- Debug output for occupied spots.
if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
self:I(self.lid..text)
coord:MarkToAll(string.format(text))
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
end
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
return nil
end
end -- loop over asset units
end -- Asset spawned check
end -- loop over asset groups
return parking

View File

@ -93,6 +93,8 @@
-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used.
-- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights
-- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players.
-- @field #string SRSText Text of the complete SRS message (if done at least once, else nil)
-- @field #boolean ATISforFARPs Will be set to true if the base given is a FARP/Helipad
-- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@ -268,6 +270,8 @@
-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before
-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds.
--
-- An SRS Setup-Guide can be found here: [Moose TTS Setup Guide](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf)
--
-- # Examples
--
-- ## Caucasus: Batumi
@ -306,6 +310,19 @@
-- atis:Start()
--
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
--
-- ## FARPS
--
-- ATIS is working with FARPS, but this requires the usage of SRS. The airbase name for the `New()-method` is the UNIT name of the FARP:
--
-- atis = ATIS:New("FARP Gold",119,radio.modulation.AM)
-- atis:SetMetricUnits()
-- atis:SetTransmitOnlyWithPlayers(true)
-- atis:SetReportmBar(true)
-- atis:SetTowerFrequencies(127.50)
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US",nil,5002)
-- atis:SetAdditionalInformation("Welcome to the Jungle!")
-- atis:__Start(3)
--
-- @field #ATIS
ATIS = {
@ -348,6 +365,7 @@ ATIS = {
relHumidity = nil,
ReportmBar = false,
TransmitOnlyWithPlayers = false,
ATISforFARPs = false,
}
--- NATO alphabet.
@ -590,7 +608,7 @@ _ATIS = {}
--- ATIS class version.
-- @field #string version
ATIS.version = "0.9.11"
ATIS.version = "0.9.14"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -616,7 +634,7 @@ ATIS.version = "0.9.11"
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ATIS class object for a specific aircraft carrier unit.
--- Create a new ATIS class object for a specific airbase.
-- @param #ATIS self
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
@ -878,6 +896,13 @@ function ATIS:SetMapMarks( switch )
return self
end
--- Return the complete SRS Text block, if at least generated once. Else nil.
-- @param #ATIS self
-- @return #string SRSText
function ATIS:GetSRSText()
return self.SRSText
end
--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°.
-- @param #ATIS self
-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended.
@ -1039,7 +1064,7 @@ end
--
-- * 186° on the Caucaus map
-- * 192° on the Nevada map
-- * 170° on the Normany map
-- * 170° on the Normandy map
-- * 182° on the Persian Gulf map
--
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
@ -1247,11 +1272,18 @@ end
function ATIS:onafterStart( From, Event, To )
-- Check that this is an airdrome.
if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) )
if self.airbase:GetAirbaseCategory() == Airbase.Category.SHIP then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.", self.airbasename ) )
return
end
-- Check that if is a Helipad.
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
self:E( self.lid .. string.format( "EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON", self.airbasename ) )
self.ATISforFARPs = true
self.useSRS = true
end
-- Info.
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
@ -1463,10 +1495,19 @@ function ATIS:onafterBroadcast( From, Event, To )
--------------
--- Runway ---
--------------
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
local runwayLanding, rwyLandingLeft
local runwayTakeoff, rwyTakeoffLeft
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
runwayLanding, rwyLandingLeft="PAD 01",false
runwayTakeoff, rwyTakeoffLeft="PAD 02",false
else
runwayLanding, rwyLandingLeft=self:GetActiveRunway()
runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
end
------------
--- Time ---
------------
@ -1780,7 +1821,7 @@ function ATIS:onafterBroadcast( From, Event, To )
-- Airbase name
subtitle = string.format( "%s", self.airbasename )
if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
if (not self.ATISforFARPs) and self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
subtitle = subtitle .. " Airport"
end
if not self.useSRS then
@ -1855,8 +1896,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1811")
--self:I(alltext)
-- Visibility
if self.metric then
@ -1874,8 +1913,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1830")
--self:I(alltext)
subtitle = ""
-- Weather phenomena
@ -1977,10 +2014,8 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 1932")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
subtitle = ""
-- Temperature
if self.TDegF then
@ -2009,9 +2044,7 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1962")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
-- Dew point
if self.TDegF then
@ -2040,8 +2073,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1992")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
-- Altimeter QNH/QFE.
@ -2107,69 +2138,68 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 2049")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
-- Active runway.
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
subtitle=subtitle.." Right"
end
local _RUNACT = subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if not self.ATISforFARPs then
-- Active runway.
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
subtitle=subtitle.." Right"
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
local _RUNACT = subtitle
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
end
end
alltext = alltext .. ";\n" .. subtitle
end
end
-- Airfield elevation
if self.elevation then
@ -2236,9 +2266,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- ILS
--self:I({ils=self.ils})
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
--self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft})
if ils then
subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency )
if not self.useSRS then
@ -2253,7 +2281,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.MegaHertz, 0.2 )
end
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
end
-- Outer NDB
@ -2389,6 +2416,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local text = string.gsub( text, "mmHg", "millimeters of Mercury" )
local text = string.gsub( text, "hPa", "hectopascals" )
local text = string.gsub( text, "m/s", "meters per second" )
local text = string.gsub( text, "TACAN", "tackan" )
local text = string.gsub( text, "FARP", "farp" )
-- Replace ";" by "."
local text = string.gsub( text, ";", " . " )
@ -2400,7 +2429,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local duration = STTS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
--self.msrs:PlayText( text )
self.SRSText = text
end
end

View File

@ -26,11 +26,11 @@
--
-- ===
--
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing)
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), The Chosen One (Persistence)
-- @module Ops.CSAR
-- @image OPS_CSAR.jpg
-- Date: November 2022
-- Date: January 2023
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@ -197,6 +197,26 @@
--
-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat
-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE)
--
-- ## 6. Save and load downed pilots - Persistance
--
-- You can save and later load back downed pilots to make your mission persistent.
-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts.
-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you.
--
-- Use the following options to manage your saves:
--
-- mycsar.enableLoadSave = true -- allow auto-saving and loading of files
-- mycsar.saveinterval = 600 -- save every 10 minutes
-- mycsar.filename = "missionsave.csv" -- example filename
-- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
--
-- Then use an initial load at the beginning of your mission:
--
-- mycsar:__Load(10)
--
-- **Caveat:**
-- Dropped troop noMessage and forcedesc parameters aren't saved.
--
-- @field #CSAR
CSAR = {
@ -272,7 +292,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.16"
CSAR.version="1.0.17"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -296,6 +316,8 @@ function CSAR:New(Coalition, Template, Alias)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #CSAR
BASE:T({Coalition, Prefixes, Alias})
--set Coalition
if Coalition and type(Coalition)=="string" then
if Coalition=="blue" then
@ -346,6 +368,8 @@ function CSAR:New(Coalition, Template, Alias)
self:AddTransition("*", "Returning", "*") -- CSAR able to return to base.
self:AddTransition("*", "Rescued", "*") -- Pilot at MASH.
self:AddTransition("*", "KIA", "*") -- Pilot killed in action.
self:AddTransition("*", "Load", "*") -- CSAR load event.
self:AddTransition("*", "Save", "*") -- CSAR save event.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
-- tables, mainly for tracking actions
@ -442,6 +466,14 @@ function CSAR:New(Coalition, Template, Alias)
self.SRSVolume = 1.0 -- volume 0.0 to 1.0
self.SRSGender = "male" -- male or female
local AliaS = string.gsub(self.alias," ","_")
self.filename = string.format("CSAR_%s_Persist.csv",AliaS)
-- load and save downed pilots
self.enableLoadSave = false
self.filepath = nil
self.saveinterval = 600
------------------------
--- Pseudo Functions ---
------------------------
@ -471,6 +503,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @function [parent=#CSAR] __Status
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--
-- --- Triggers the FSM event "Load".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Load" after a delay.
-- @function [parent=#CSAR] __Load
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Save".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Save" after a delay.
-- @function [parent=#CSAR] __Save
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- On After "PilotDown" event. Downed Pilot detected.
-- @function [parent=#CSAR] OnAfterPilotDown
@ -538,6 +588,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @param #string To To state.
-- @param #string Pilotname Name of the pilot KIA.
--- FSM Function OnAfterLoad.
-- @function [parent=#CSAR] OnAfterLoad
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
--- FSM Function OnAfterSave.
-- @function [parent=#CSAR] OnAfterSave
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
return self
end
@ -850,7 +918,7 @@ end
--- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons.
-- @param #CSAR self
-- @param #string _Point a POINT_VEC2.
-- @param Core.Point#COORDINATE _Point
-- @param #number _coalition Coalition.
-- @param #string _description (optional) Description.
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
@ -883,7 +951,7 @@ end
--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
-- @param #CSAR self
-- @param #string Point a POINT_VEC2.
-- @param Core.Point#COORDINATE Point
-- @param #number Coalition Coalition.
-- @param #string Description (optional) Description.
-- @param #boolean addBeacon (optional) yes or no.
@ -893,8 +961,8 @@ end
-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names.
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so:
--
-- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition.
-- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE )
-- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition.
-- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE )
function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
return self
@ -2108,9 +2176,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
local _radioUnit = _group:GetUnit(1)
if _radioUnit then
local Frequency = _freq -- Freq in Hertz
local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
end
end
return self
@ -2218,7 +2287,16 @@ function CSAR:onafterStart(From, Event, To)
self.msrs:SetLabel("CSAR")
self.SRSQueue = MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
@ -2451,6 +2529,240 @@ function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase)
self:T({From, Event, To, HeliName, Airbase})
return self
end
--- On before "Save" event. Checks if io and lfs are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
-- Thanks to @FunkyFranky
-- Check io module is available.
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
--- On after "Save" event. Player data is saved to file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
-- @param #string filename (Optional) File name for saving. Default is Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
-- Thanks to @FunkyFranky
if not self.enableLoadSave then
return self
end
--- Function that saves data to file
local function _savefile(filename, data)
local f = assert(io.open(filename, "wb"))
f:write(data)
f:close()
end
-- Set path or default.
if lfs then
path=self.filepath or lfs.writedir()
end
-- Set file name.
filename=filename or self.filename
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
local pilots = self.downedPilots
--local data = "LoadedData = {\n"
local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
local n = 0
for _,_grp in pairs(pilots) do
local DownedPilot = _grp -- Wrapper.Group#GROUP
if DownedPilot and DownedPilot.alive then
-- get downed pilot data for saving
local playerName = DownedPilot.player
local group = DownedPilot.group
local coalition = group:GetCoalition()
local country = group:GetCountry()
local description = DownedPilot.desc
local typeName = DownedPilot.typename
local freq = DownedPilot.frequency
local location = group:GetVec3()
local unitName = DownedPilot.originalUnit
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: " .. txt)
data = data .. txt
end
end
_savefile(filename, data)
-- AutoSave
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
--- On before "Load" event. Checks if io and lfs and the file are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that check if a file exists.
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Check io module is available.
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename))
return false
--return self
end
end
--- On after "Load" event. Loads dropped units from file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that loads data from a file.
local function _loadfile(filename)
local f=assert(io.open(filename, "rb"))
local data=f:read("*all")
f:close()
return data
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Info message.
local text=string.format("Loading CSAR state from file %s", filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename, "rb"))
local loadeddata = {}
for line in file:lines() do
loadeddata[#loadeddata+1] = line
end
file:close()
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n
local playerName = dataset[1]
local vec3 = {}
vec3.x = tonumber(dataset[2])
vec3.y = tonumber(dataset[3])
vec3.z = tonumber(dataset[4])
local point = COORDINATE:NewFromVec3(vec3)
local coalition = dataset[5]
local country = dataset[6]
local description = dataset[7]
local typeName = dataset[8]
local unitName = dataset[9]
local freq = dataset[10]
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
end
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- End Ops.CSAR
--------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -22,7 +22,7 @@
-- @module Ops.CTLD
-- @image OPS_CTLD.jpg
-- Last Update October 2022
-- Last Update Jan 2023
do
@ -711,6 +711,9 @@ do
-- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes
-- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types.
-- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted.
-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true`
-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds.
-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects
--
-- ## 2.1 User functions
--
@ -966,7 +969,110 @@ do
--
-- **Caveat:**
-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved.
--
-- ## 7. Complex example - Build a complete FARP from a CTLD crate drop
--
-- Prerequisites - you need to add a cargo of type FOB to your CTLD instance, for simplification reasons we call it FOB:
--
-- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2)
--
-- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template
-- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter).
--
-- The following code will build a FARP at the coordinate the FOB was dropped and built:
--
-- -- FARP Radio. First one has 130AM, next 131 and for forth
-- local FARPFreq = 130
-- local FARPName = 1 -- numbers 1..10
--
-- local FARPClearnames = {
-- [1]="London",
-- [2]="Dallas",
-- [3]="Paris",
-- [4]="Moscow",
-- [5]="Berlin",
-- [6]="Rome",
-- [7]="Madrid",
-- [8]="Warsaw",
-- [9]="Dublin",
-- [10]="Perth",
-- }
--
-- function BuildAFARP(Coordinate)
-- local coord = Coordinate -- Core.Point#COORDINATE
--
-- local FarpName = ((FARPName-1)%10)+1
-- local FName = FARPClearnames[FarpName]
--
-- FARPFreq = FARPFreq + 1
-- FARPName = FARPName + 1
--
-- -- Create a SPAWNSTATIC object from a template static FARP object.
-- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA)
--
-- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency.
-- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0)
-- SpawnStaticFarp:InitDead(false)
--
-- -- Spawn FARP
-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false)
-- local Heading = 0
-- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName)
--
-- -- ATC and services - put them 125m from the center of the zone towards North
-- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals")
-- FarpVehicles:InitHeading(180)
-- local FarpVCoord = coord:Translate(125,0)
-- FarpVehicles:SpawnFromCoordinate(FarpVCoord)
--
-- -- We will put the rest of the statics in a nice circle around the center
-- local base = 330
-- local delta = 30
--
-- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA)
-- local sockcoord = coord:Translate(125,base)
-- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName)
-- base=base-delta
--
-- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA)
-- local fuelcoord = coord:Translate(125,base)
-- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName)
-- base=base-delta
--
-- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA)
-- local ammocoord = coord:Translate(125,base)
-- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName)
-- base=base-delta
--
-- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA)
-- local CommandCoord = coord:Translate(125,base)
-- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName)
-- base=base-delta
--
-- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
-- local Tent1Coord = coord:Translate(125,base)
-- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName)
-- base=base-delta
--
-- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
-- local Tent2Coord = coord:Translate(125,base)
-- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName)
--
-- -- add a loadzone to CTLD
-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true)
-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue()
-- end
--
-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle)
-- local name = Vehicle:GetName()
-- if string.match(name,"FOB",1,true) then
-- local Coord = Vehicle:GetCoordinate()
-- Vehicle:Destroy(false)
-- BuildAFARP(Coord)
-- end
-- end
--
--
-- @field #CTLD
CTLD = {
ClassName = "CTLD",
@ -1015,7 +1121,16 @@ CTLD = {
-- @type CTLD.ZoneBeacon
-- @field #string name -- Name of zone for the coordinate
-- @field #number frequency -- in mHz
-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM
-- @field #number modulation -- i.e.CTLD.RadioModulation.FM or CTLD.RadioModulation.AM
--- Radio Modulation
-- @type CTLD.RadioModulation
-- @field #number AM
-- @field #number FM
CTLD.RadioModulation = {
AM = 0,
FM = 1,
}
--- Zone Info.
-- @type CTLD.CargoZone
@ -1067,6 +1182,7 @@ CTLD.UnitTypes = {
["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
["Ka-50_3"] = {type="Ka-50_3", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
@ -1078,7 +1194,7 @@ CTLD.UnitTypes = {
--- CTLD class version.
-- @field #string version
CTLD.version="1.0.19"
CTLD.version="1.0.27"
--- Instantiate a new CTLD.
-- @param #CTLD self
@ -1161,6 +1277,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- radio beacons
self.RadioSound = "beacon.ogg"
self.RadioPath = "l10n/DEFAULT/"
-- zones stuff
self.pickupZones = {}
@ -1243,6 +1360,11 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.usesubcats = false
self.subcats = {}
-- disallow building in loadzones
self.nobuildinloadzones = true
self.movecratesbeforebuild = true
self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER}
local AliaS = string.gsub(self.alias," ","_")
self.filename = string.format("CTLD_%s_Persist.csv",AliaS)
@ -1635,12 +1757,54 @@ function CTLD:_SendMessage(Text, Time, Clearscreen, Group)
return self
end
--- (Internal) Find a troops CTLD_CARGO object in stock
-- @param #CTLD self
-- @param #string Name of the object
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
function CTLD:_FindTroopsCargoObject(Name)
self:T(self.lid .. " _FindTroopsCargoObject")
local cargo = nil
for _,_cargo in pairs(self.Cargo_Troops)do
local cargo = _cargo -- #CTLD_CARGO
if cargo.Name == Name then
return cargo
end
end
return nil
end
--- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot!
-- @param #CTLD self
-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object
-- @param #string Troopname The name of the Troops to be loaded. Must be created prior in the CTLD setup!
-- @return #CTLD self
-- @usage
-- local client = UNIT:FindByName("Helo-1-1")
-- if client and client:IsAlive() then
-- myctld:PreloadTroops(client,"Infantry")
-- end
function CTLD:PreloadTroops(Unit,Troopname)
self:T(self.lid .. " PreloadTroops")
local name = Troopname or "Unknown"
if Unit and Unit:IsAlive() then
local cargo = self:_FindTroopsCargoObject(name)
local group = Unit:GetGroup()
if cargo then
self:_LoadTroops(group,Unit,cargo,true)
else
self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!")
end
end
return self
end
--- (Internal) Function to load troops into a heli.
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Unit#UNIT Unit
-- @param #CTLD_CARGO Cargotype
function CTLD:_LoadTroops(Group, Unit, Cargotype)
-- @param #boolean Inject
function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject)
self:T(self.lid .. " _LoadTroops")
-- check if we have stock
local instock = Cargotype:GetStock()
@ -1648,7 +1812,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype)
local cgotype = Cargotype:GetType()
local cgonetmass = Cargotype:GetNetMass()
local maxloadable = self:_GetMaxLoadableMass(Unit)
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 and not Inject then
-- nothing left over
self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group)
return self
@ -1656,21 +1820,22 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype)
-- landed or hovering over load zone?
local grounded = not self:IsUnitInAir(Unit)
local hoverload = self:CanHoverLoad(Unit)
--local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors
-- check if we are in LOAD zone
local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!", 10, false, Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group)
if not self.debug then return self end
if not Inject then
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!", 10, false, Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group)
if not self.debug then return self end
end
end
-- load troops into heli
local group = Group -- Wrapper.Group#GROUP
@ -2093,10 +2258,11 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
self.CargoCounter = self.CargoCounter + 1
local realcargo = nil
if drop then
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat)
--CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat)
table.insert(droppedcargo,realcargo)
else
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat)
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat)
Cargo:RemoveStock()
end
table.insert(self.Spawned_Cargo, realcargo)
@ -2824,6 +2990,14 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
return self
end
end
if not Engineering and self.nobuildinloadzones then
-- are we in a load zone?
local inloadzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if inloadzone then
self:_SendMessage("You cannot build in a loading area, Pilot!", 10, false, Group)
return self
end
end
-- get nearby crates
local finddist = self.CrateDistance or 35
local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table
@ -2834,7 +3008,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
-- get dropped crates
for _,_crate in pairs(crates) do
local Crate = _crate -- #CTLD_CARGO
if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then
if (Crate:WasDropped() or not self.movecratesbeforebuild) and not Crate:IsRepair() and not Crate:IsStatic() then
-- we can build these - maybe
local name = Crate:GetName()
local required = Crate:GetCratesNeeded()
@ -2879,7 +3053,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok)
report:Add(text)
end -- end list buildables
if not foundbuilds then report:Add(" --- None Found ---") end
if not foundbuilds then
report:Add(" --- None found! ---")
if self.movecratesbeforebuild then
report:Add("*** Crates need to be moved before building!")
end
end
report:Add("------------------------------------------------------------")
local text = report:Text()
if not Engineering then
@ -3440,7 +3619,7 @@ function CTLD:_GetFMBeacon(Name)
table.insert(self.UsedFMFrequencies, FM)
beacon.name = Name
beacon.frequency = FM / 1000000
beacon.modulation = radio.modulation.FM
beacon.modulation = CTLD.RadioModulation.FM
return beacon
end
@ -3460,7 +3639,7 @@ function CTLD:_GetUHFBeacon(Name)
table.insert(self.UsedUHFFrequencies, UHF)
beacon.name = Name
beacon.frequency = UHF / 1000000
beacon.modulation = radio.modulation.AM
beacon.modulation = CTLD.RadioModulation.AM
return beacon
end
@ -3481,7 +3660,7 @@ function CTLD:_GetVHFBeacon(Name)
table.insert(self.UsedVHFFrequencies, VHF)
beacon.name = Name
beacon.frequency = VHF / 1000000
beacon.modulation = radio.modulation.FM
beacon.modulation = CTLD.RadioModulation.FM
return beacon
end
@ -3522,7 +3701,12 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship
ctldzone.name = Name or "NONE"
ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType
ctldzone.hasbeacon = HasBeacon or false
if Type == CTLD.CargoZoneType.BEACON then
self.droppedbeaconref[ctldzone.name] = zone:GetCoordinate()
ctldzone.timestamp = timer.getTime()
end
if HasBeacon then
ctldzone.fmbeacon = self:_GetFMBeacon(Name)
ctldzone.uhfbeacon = self:_GetUHFBeacon(Name)
@ -3611,7 +3795,8 @@ function CTLD:CheckDroppedBeacons()
for _,_beacon in pairs (self.droppedBeacons) do
local beacon = _beacon -- #CTLD.CargoZone
local T0 = beacon.timestamp
if not beacon.timestamp then beacon.timestamp = timer.getTime() + timeout end
local T0 = beacon.timestamp
if timer.getTime() - T0 > timeout then
local name = beacon.name
self.droppedbeaconref[name] = nil
@ -3682,22 +3867,53 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped)
end
end
local Sound = Sound or "beacon.ogg"
if IsDropped and Zone then
if Zone then
if IsDropped then
local ZoneCoord = Zone
local ZoneVec3 = ZoneCoord:GetVec3()
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
local Frequency = Mhz * 1000000 -- Freq in Hertz
local Sound = "l10n/DEFAULT/"..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight
elseif Zone then
local Sound = self.RadioPath..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straight
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
else
local ZoneCoord = Zone:GetCoordinate()
local ZoneVec3 = ZoneCoord:GetVec3()
local Frequency = Mhz * 1000000 -- Freq in Hertz
local Sound = "l10n/DEFAULT/"..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
local Frequency = Mhz * 1000000 -- Freq in Hert
local Sound = self.RadioPath..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straightt
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
end
else
self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name)
end
return self
end
--- Set folder path where the CTLD sound files are located **within you mission (miz) file**.
-- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission.
-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used.
-- @param #CTLD self
-- @param #string FolderPath The path to the sound files, e.g. "CTLD_Soundfiles/".
-- @return #CTLD self
function CTLD:SetSoundfilesFolder( FolderPath )
self:T(self.lid .. " SetSoundfilesFolder")
-- Check that it ends with /
if FolderPath then
local lastchar = string.sub( FolderPath, -1 )
if lastchar ~= "/" then
FolderPath = FolderPath .. "/"
end
end
-- Folderpath.
self.RadioPath = FolderPath
-- Info message.
self:I( self.lid .. string.format( "Setting sound files folder to: %s", self.RadioPath ) )
return self
end
--- (Internal) Function to refresh radio beacons
-- @param #CTLD self
function CTLD:_RefreshRadioBeacons()
@ -3720,10 +3936,10 @@ function CTLD:_RefreshRadioBeacons()
local Name = czone.name
local FM = FMbeacon.frequency -- MHz
local VHF = VHFbeacon.frequency -- KHz
local UHF = UHFbeacon.frequency -- MHz
self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip, IsDropped)
local UHF = UHFbeacon.frequency -- MHz
self:_AddRadioBeacon(Name,Sound,FM, CTLD.RadioModulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped)
end
end
end
@ -4235,6 +4451,8 @@ end
-- @param #CTLD self
-- @param Core.Zone#ZONE Zone The zone where to drop the troops.
-- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn.
-- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
-- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another!
-- @return #CTLD self
-- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone:
-- -- create a matching #CTLD_CARGO type
@ -4242,8 +4460,8 @@ end
-- -- get a #ZONE object
-- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE
-- -- and go:
-- my_ctld:InjectTroops(dropzone,InjectTroopsType)
function CTLD:InjectTroops(Zone,Cargo)
-- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND})
function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation)
self:T(self.lid.." InjectTroops")
local cargo = Cargo -- #CTLD_CARGO
@ -4275,8 +4493,10 @@ end
local temptable = cargo:GetTemplates() or {}
local factor = 1.5
local zone = Zone
local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2()
local randomcoord = zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
if PreciseLocation then
randomcoord = zone:GetCoordinate():GetVec2()
end
for _,_template in pairs(temptable) do
self.TroopCounter = self.TroopCounter + 1
local alias = string.format("%s-%d", _template, math.random(1,100000))
@ -4611,7 +4831,7 @@ end
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build.
-- @return #CTLD self
function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle)
self:I({From, Event, To})
self:T({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
@ -4737,7 +4957,7 @@ end
for _,_cargo in pairs (stcstable) do
local cargo = _cargo -- #CTLD_CARGO
local object = cargo:GetPositionable() -- Wrapper.Static#STATIC
if object and object:IsAlive() and cargo:WasDropped() then
if object and object:IsAlive() and (cargo:WasDropped() or not cargo:HasMoved()) then
statics[#statics+1] = cargo
end
end
@ -4978,7 +5198,7 @@ end
self:InjectVehicles(dropzone,injectvehicle)
elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then
local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
self:InjectTroops(dropzone,injecttroops)
self:InjectTroops(dropzone,injecttroops,self.surfacetypes)
end
elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then
local cargotemplates = dataset[6]

View File

@ -512,7 +512,7 @@ function COMMANDCENTER:AssignTask( TaskGroup )
if Task then
self:I( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority )
self:T( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority )
if not self.AutoAcceptTasks == true then
Task:SetAutoAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) )

View File

@ -413,7 +413,7 @@ end
-- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission.
-- @return #boolean true if Unit is part of a Task in the Mission.
function MISSION:JoinUnit( PlayerUnit, PlayerGroup )
self:I( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } )
self:T( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } )
local PlayerUnitAdded = false
@ -571,7 +571,7 @@ do -- Group Assignment
local MissionGroupName = MissionGroup:GetName()
self.AssignedGroups[MissionGroupName] = MissionGroup
self:I( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) )
self:T( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) )
return self
end
@ -698,7 +698,7 @@ end
function MISSION:AddTask( Task )
local TaskName = Task:GetTaskName()
self:I( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self:T( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self.Tasks[TaskName] = Task
@ -717,7 +717,7 @@ end
function MISSION:RemoveTask( Task )
local TaskName = Task:GetTaskName()
self:I( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self:T( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } )
self:F( TaskName )
self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 }

View File

@ -1061,8 +1061,8 @@ function UTILS.Vec2Norm(a)
end
--- Calculate the distance between two 2D vectors.
-- @param DCS#Vec2 a Vector in 3D with x, y components.
-- @param DCS#Vec2 b Vector in 3D with x, y components.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param DCS#Vec2 b Vector in 2D with x, y components.
-- @return #number Distance between the vectors.
function UTILS.VecDist2D(a, b)
@ -1446,6 +1446,30 @@ function UTILS.GetCoalitionName(Coalition)
end
--- Get the enemy coalition for a given coalition.
-- @param #number Coalition The coalition ID.
-- @param #boolean Neutral Include neutral as enemy.
-- @return #table Enemy coalition table.
function UTILS.GetCoalitionEnemy(Coalition, Neutral)
local Coalitions={}
if Coalition then
if Coalition==coalition.side.RED then
Coalitions={coalition.side.BLUE}
elseif Coalition==coalition.side.BLUE then
Coalitions={coalition.side.RED}
elseif Coalition==coalition.side.NEUTRAL then
Coalitions={coalition.side.RED, coalition.side.BLUE}
end
end
if Neutral then
table.insert(Coalitions, coalition.side.NEUTRAL)
end
return Coalitions
end
--- Get the modulation name from its numerical value.
-- @param #number Modulation The modulation enumerator number. Can be either 0 or 1.
-- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown".
@ -1576,6 +1600,8 @@ function UTILS.GMTToLocalTimeDifference()
return 3 -- Damascus is UTC+3 hours
elseif theatre==DCSMAP.MarianaIslands then
return 10 -- Guam is UTC+10 hours.
elseif theatre==DCSMAP.Falklands then
return -3 -- Fireland is UTC-3 hours.
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0
@ -1907,7 +1933,7 @@ function UTILS.GenerateVHFrequencies()
705,720,722,730,735,740,745,750,770,795,
822,830,862,866,
905,907,920,935,942,950,995,
1000,1025,1030,1050,1065,1116,1175,1182,1210
1000,1025,1030,1050,1065,1116,1175,1182,1210,1215
}
local FreeVHFFrequencies = {}
@ -1975,7 +2001,9 @@ function UTILS.GenerateUHFrequencies()
local _start = 220000000
while _start < 399000000 do
table.insert(FreeUHFFrequencies, _start)
if _start ~= 243000000 then
table.insert(FreeUHFFrequencies, _start)
end
_start = _start + 500000
end
@ -2175,10 +2203,29 @@ function UTILS.CheckFileExists(Path,Filename)
end
end
--- Function to obtain a table of typenames from the group given with the number of units of the same type in the group.
-- @param Wrapper.Group#GROUP Group The group to list
-- @return #table Table of typnames and typename counts, e.g. `{["KAMAZ Truck"]=3,["ATZ-5"]=1}`
function UTILS.GetCountPerTypeName(Group)
local units = Group:GetUnits()
local TypeNameTable = {}
for _,_unt in pairs (units) do
local unit = _unt -- Wrapper.Unit#UNIT
local typen = unit:GetTypeName()
if not TypeNameTable[typen] then
TypeNameTable[typen] = 1
else
TypeNameTable[typen] = TypeNameTable[typen] + 1
end
end
return TypeNameTable
end
--- Function to save the state of a list of groups found by name
-- @param #table List Table of strings with groupnames
-- @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.
-- @usage
-- We will go through the list and find the corresponding group and save the current group size (0 when dead).
@ -2186,7 +2233,7 @@ end
-- Position is still saved for your usage.
-- The idea is to reduce the number of units when reloading the data again to restart the saved mission.
-- The data will be a simple comma separated list of groupname and size, with one header line.
function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
local filename = Filename or "StateListofGroups"
local data = "--Save Stationary List of Groups: "..Filename .."\n"
for _,_group in pairs (List) do
@ -2194,7 +2241,16 @@ function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
if group and group:IsAlive() then
local units = group:CountAliveUnits()
local position = group:GetVec3()
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
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,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
else
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
end
else
data = string.format("%s%s,0,0,0,0\n",data,_group)
end
@ -2208,6 +2264,7 @@ end
-- @param Core.Set#SET_BASE Set of 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.
-- @usage
-- We will go through the set and find the corresponding group and save the current group size and current position.
@ -2217,7 +2274,7 @@ end
-- **Note** Do NOT use dashes or hashes in group template names (-,#)!
-- The data will be a simple comma separated list of groupname and size, with one header line.
-- The current task/waypoint/etc cannot be restored.
function UTILS.SaveSetOfGroups(Set,Path,Filename)
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
local filename = Filename or "SetOfGroups"
local data = "--Save SET of groups: "..Filename .."\n"
local List = Set:GetSetObjects()
@ -2231,7 +2288,16 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename)
end
local units = group:CountAliveUnits()
local position = group:GetVec3()
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
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,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
else
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
end
end
end
-- save the data
@ -2295,8 +2361,41 @@ end
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number.
-- @param #boolean Structured (Optional, needs Reduce = true) If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
local fires = {}
local function Smokers(name,coord,effect,density)
local eff = math.random(8)
if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units = group:GetUnits()
local reduced = 0
for _,_unit in pairs (units) do
local typo = _unit:GetTypeName()
if typename == typo then
if Cinematic then
local coordinate = _unit:GetCoordinate()
local name = _unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced = reduced + 1
if reduced == anzahl then break end
end
end
end
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofGroups"
@ -2313,18 +2412,48 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
local posx = tonumber(dataset[3])
local posy = tonumber(dataset[4])
local posz = tonumber(dataset[5])
local structure = dataset[6]
--BASE:I({structure})
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) }
if reduce then
local actualgroup = GROUP:FindByName(groupname)
if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then
local reduction = actualgroup:CountAliveUnits() - size
BASE:I("Reducing groupsize by ".. reduction .. " units!")
-- reduce existing group
local units = actualgroup:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
if Structured and structure then
--BASE:I("Reducing group structure!")
local loadedstructure = {}
local strcset = UTILS.Split(structure,";")
for _,_data in pairs(strcset) do
local datasplit = UTILS.Split(_data,"==")
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
end
--BASE:I({loadedstructure})
local originalstructure = UTILS.GetCountPerTypeName(actualgroup)
--BASE:I({originalstructure})
for _name,_number in pairs(originalstructure) do
local loadednumber = 0
if loadedstructure[_name] then
loadednumber = loadedstructure[_name]
end
local reduce = false
if loadednumber < _number then reduce = true end
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
if reduce then
Cruncher(actualgroup,_name,_number-loadednumber)
end
end
else
local reduction = actualgroup:CountAliveUnits() - size
--BASE:I("Reducing groupsize by ".. reduction .. " units!")
-- reduce existing group
local units = actualgroup:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
@ -2333,22 +2462,121 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
else
return nil
end
return datatable
return datatable,fires
end
--- Load back a SET of groups from file.
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size.
-- @param #boolean Structured (Optional, needs Spawn=true)If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return Core.Set#SET_GROUP Set of GROUP objects.
-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }`
function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
local fires = {}
local usedtemplates = {}
local spawn = true
if Spawn == false then spawn = false end
BASE:I("Spawn = "..tostring(spawn))
local filename = Filename or "SetOfGroups"
local setdata = SET_GROUP:New()
local datatable = {}
local function Smokers(name,coord,effect,density)
local eff = math.random(8)
if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units = group:GetUnits()
local reduced = 0
for _,_unit in pairs (units) do
local typo = _unit:GetTypeName()
if typename == typo then
if Cinematic then
local coordinate = _unit:GetCoordinate()
local name = _unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced = reduced + 1
if reduced == anzahl then break end
end
end
end
local function PostSpawn(args)
local spwndgrp = args[1]
local size = args[2]
local structure = args[3]
setdata:AddObject(spwndgrp)
local actualsize = spwndgrp:CountAliveUnits()
if actualsize > size then
if Structured and structure then
local loadedstructure = {}
local strcset = UTILS.Split(structure,";")
for _,_data in pairs(strcset) do
local datasplit = UTILS.Split(_data,"==")
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
end
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
for _name,_number in pairs(originalstructure) do
local loadednumber = 0
if loadedstructure[_name] then
loadednumber = loadedstructure[_name]
end
local reduce = false
if loadednumber < _number then reduce = true end
if reduce then
Cruncher(spwndgrp,_name,_number-loadednumber)
end
end
else
local reduction = actualsize-size
-- reduce existing group
local units = spwndgrp:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
local function MultiUse(Data)
local template = Data.template
if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then
-- multispawn
if not usedtemplates[template].done then
local spwnd = 0
local spawngrp = SPAWN:New(template)
spawngrp:InitLimit(0,usedtemplates[template].used)
for _,_entry in pairs(usedtemplates[template].data) do
spwnd = spwnd + 1
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
usedtemplates[template].done = true
end
return true
else
return false
end
end
--BASE:I("Spawn = "..tostring(spawn))
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
@ -2362,36 +2590,37 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
local posx = tonumber(dataset[4])
local posy = tonumber(dataset[5])
local posz = tonumber(dataset[6])
local structure = dataset[7]
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local group=nil
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template }
table.insert(datatable,data)
if spawn then
local group = SPAWN:New(template)
:InitDelayOff()
:OnSpawnGroup(
function(spwndgrp)
setdata:AddObject(spwndgrp)
local actualsize = spwndgrp:CountAliveUnits()
if actualsize > size then
local reduction = actualsize-size
-- reduce existing group
local units = spwndgrp:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
)
:SpawnFromCoordinate(coordinate)
if size > 0 then
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure }
table.insert(datatable,data)
if usedtemplates[template] then
usedtemplates[template].used = usedtemplates[template].used + 1
table.insert(usedtemplates[template].data,data)
else
usedtemplates[template] = {
data = {},
used = 1,
done = false,
}
table.insert(usedtemplates[template].data,data)
end
end
end
for _id,_entry in pairs (datatable) do
if spawn and not MultiUse(_entry) and _entry.size > 0 then
local group = SPAWN:New(_entry.template)
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
end
else
return nil
end
if spawn then
return setdata
return setdata,fires
else
return datatable
end
@ -2410,13 +2639,11 @@ function UTILS.LoadSetOfStatics(Path,Filename)
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- staticname,position.x,position.y,position.z
local staticname = dataset[1]
local posx = tonumber(dataset[2])
local posy = tonumber(dataset[3])
local posz = tonumber(dataset[4])
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
datatable:AddObject(STATIC:FindByName(staticname,false))
local StaticObject = STATIC:FindByName(staticname,false)
if StaticObject then
datatable:AddObject(StaticObject)
end
end
else
return nil
@ -2428,9 +2655,15 @@ end
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Reduce If false, do not destroy the units with size=0.
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object.
-- @param #boolean Dead (Optional, needs Reduce = true) If Dead is true, re-spawn the dead object as dead and do not just delete it.
-- @param #boolean Cinematic (Optional, needs Dead = true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}`
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
-- Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
local fires = {}
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofStatics"
@ -2453,14 +2686,31 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
if size==0 and reduce then
local static = STATIC:FindByName(staticname,false)
if static then
static:Destroy(false)
if Dead then
local deadobject = SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
deadobject:InitDead(true)
local heading = static:GetHeading()
local coord = static:GetCoordinate()
static:Destroy(false)
deadobject:SpawnFromCoordinate(coord,heading,staticname)
if Cinematic then
local effect = math.random(8)
if type(Effect) == "number" then
effect = Effect
end
coord:BigSmokeAndFire(effect,Density,staticname)
table.insert(fires,staticname)
end
else
static:Destroy(false)
end
end
end
end
else
return nil
end
return datatable
return datatable,fires
end
--- Heading Degrees (0-360) to Cardinal
@ -2485,7 +2735,7 @@ end
-- @return #string Formatted BRAA NATO call
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
local BRAANATO = "Merged."
local GroupNumber = FromGrp:GetSize()
local GroupNumber = ToGrp:GetSize()
local GroupWords = "Singleton"
if GroupNumber == 2 then GroupWords = "Two-Ship"
elseif GroupNumber >= 3 then GroupWords = "Heavy"
@ -2509,3 +2759,51 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
end
return BRAANATO
end
--- Check if an object is contained in a table.
-- @param #table Table The table.
-- @param #table Object The object to check.
-- @param #string Key (Optional) Key to check. By default, the object itself is checked.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsInTable(Table, Object, Key)
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
return false
end
--- Check if any object of multiple given objects is contained in a table.
-- @param #table Table The table.
-- @param #table Objects The objects to check.
-- @param #string Key (Optional) Key to check.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsAnyInTable(Table, Objects, Key)
for _,Object in pairs(UTILS.EnsureTable(Objects)) do
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
end
return false
end

View File

@ -246,7 +246,7 @@ AIRBASE.Normandy = {
--
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
-- * AIRBASE.PersianGulf.Al-Bateen_Airport
-- * AIRBASE.PersianGulf.Al_Bateen_Airport
-- * AIRBASE.PersianGulf.Al_Ain_International_Airport
-- * AIRBASE.PersianGulf.Al_Dhafra_AB
-- * AIRBASE.PersianGulf.Al_Maktoum_Intl
@ -265,7 +265,7 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Lavan_Island_Airport
-- * AIRBASE.PersianGulf.Liwa_Airbase
-- * AIRBASE.PersianGulf.Qeshm_Island
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah_International_Airport
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah
-- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport
-- * AIRBASE.PersianGulf.Sharjah_Intl
-- * AIRBASE.PersianGulf.Shiraz_International_Airport
@ -510,6 +510,11 @@ AIRBASE.MarianaIslands = {
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
-- * AIRBASE.SouthAtlantic.Rio_Turbio
-- * AIRBASE.SouthAtlantic.Rio_Chico
-- * AIRBASE.SouthAtlantic.Franco_Bianco
-- * AIRBASE.SouthAtlantic.Goose_Green
-- * AIRBASE.SouthAtlantic.Hipico
-- * AIRBASE.SouthAtlantic.CaletaTortel
--
--@field MarianaIslands
AIRBASE.SouthAtlantic={
@ -532,6 +537,11 @@ AIRBASE.SouthAtlantic={
["Porvenir_Airfield"]="Porvenir Airfield",
["Almirante_Schroeders"]="Almirante Schroeders",
["Rio_Turbio"]="Rio Turbio",
["Rio_Chico"] = "Rio Chico",
["Franco_Bianco"] = "Franco Bianco",
["Goose_Green"] = "Goose Green",
["Hipico"] = "Hipico",
["CaletaTortel"] = "CaletaTortel",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
@ -685,6 +695,9 @@ function AIRBASE:Register(AirbaseName)
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
end
-- Debug info.
self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName)))
return self
end
@ -836,7 +849,7 @@ end
-- Black listed spots overrule white listed spots.
-- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor!
-- @param #AIRBASE self
-- @param #table TerminalIdBlacklist Table of white listed terminal IDs.
-- @param #table TerminalIdWhitelist Table of white listed terminal IDs.
-- @return #AIRBASE self
-- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
@ -1365,7 +1378,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
local _nspots=nspots or group:GetSize()
-- Debug info.
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
-- Table of valid spots.
local validspots={}

View File

@ -67,7 +67,6 @@
-- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point.
-- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point.
-- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone.
-- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase.
--
-- ## 2.2) EnRoute assignment
--
@ -669,12 +668,61 @@ function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Ch
return self
end
--- Activate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! Also needs Link4 to work.
-- @param #CONTROLLABLE self
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the ACLS system is attached to. Defaults to the UNIT itself.
-- @param #string Name (Optional) Name of the ACLS Beacon
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateACLS( UnitID, Name, Delay )
-- Command to activate ACLS system.
local CommandActivateACLS= {
id = 'ActivateACLS',
params = {
unitId = UnitID or self:GetID(),
name = Name or "ACL",
}
}
self:T({CommandActivateACLS})
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandActivateACLS, { self, UnitID, Name }, Delay )
else
self:SetCommand( CommandActivateACLS )
end
return self
end
--- Deactivate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateACLS( Delay )
-- Command to activate ACLS system.
local CommandDeactivateACLS= {
id = 'DeactivateACLS',
params = { }
}
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandDeactivateACLS, { self }, Delay )
else
self:SetCommand( CommandDeactivateACLS )
end
return self
end
--- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Channel ICLS channel.
-- @param #number UnitID The DCS UNIT ID of the unit the ICLS system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
@ -684,13 +732,13 @@ function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
params = {
["type"] = BEACON.Type.ICLS,
["channel"] = Channel,
["unitId"] = UnitID,
["unitId"] = UnitID or self:GetID(),
["callsign"] = Callsign,
},
}
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay )
SCHEDULER:New( nil, self.CommandActivateICLS, { self, Channel, UnitID, Callsign }, Delay )
else
self:SetCommand( CommandActivateICLS )
end
@ -700,53 +748,29 @@ end
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 (defaults to 336 MHz)
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the LINK4 system is attached to. Defaults to the UNIT itself.
-- @param #string Callsign (Optional) Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
local freq = Frequency or 336
-- Command to activate Link4 system.
local CommandActivateLink4= {
id = "ActivateLink4",
params= {
["frequency "] = Frequency*1000,
["unitId"] = UnitID,
["name"] = Callsign,
["frequency "] = freq*1000000,
["unitId"] = UnitID or self:GetID(),
["name"] = Callsign or "LNK",
}
}
self:T({CommandActivateLink4})
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
else
self:SetCommand(CommandActivateLink4)
end
return self
end
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
-- Command to activate Link4 system.
local CommandActivateLink4= {
id = "ActivateLink4",
params= {
["frequency "] = Frequency*1000,
["unitId"] = UnitID,
["name"] = Callsign,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
SCHEDULER:New(nil, self.CommandActivateLink4, {self, Frequency, UnitID, Callsign}, Delay)
else
self:SetCommand(CommandActivateLink4)
end
@ -810,24 +834,6 @@ function CONTROLLABLE:CommandDeactivateICLS( Delay )
return self
end
--- Deactivate the active Link4 of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateLink4(Delay)
-- Command to deactivate
local CommandDeactivateLink4={id='DeactivateLink4', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay)
else
self:SetCommand(CommandDeactivateLink4)
end
return self
end
--- Set callsign of the CONTROLLABLE. See [DCS command setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign)
-- @param #CONTROLLABLE self
-- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called.
@ -1416,7 +1422,7 @@ end
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to land.
-- @param #number Duration The duration in seconds to stay on the ground.
-- @return #CONTROLLABLE self
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint )
-- Get landing point
@ -1665,6 +1671,26 @@ function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority)
return DCSTask
end
--- (AIR) Enroute SEAD task.
-- @param #CONTROLLABLE self
-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Air Defence"}`.
-- @param #number Priority (Optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. Default 0.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes, Priority)
local DCSTask = {
id = 'EngageTargets',
key = "SEAD",
--auto = false,
--enabled = true,
params = {
targetTypes = TargetTypes or {"Air Defence"},
priority = Priority or 0
}
}
return DCSTask
end
--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets.
-- @param #CONTROLLABLE self
@ -2395,7 +2421,7 @@ do -- Route methods
-- @return DCS#Task Task.
-- @return #boolean If true, path on road is possible. If false, task will route the group directly to its destination.
function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, OffRoadFormation, Shortcut, FromCoordinate, WaypointFunction, WaypointFunctionArguments )
self:I( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } )
self:T( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } )
-- Defaults.
Speed = Speed or 20
@ -3954,4 +3980,4 @@ function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType)
end
end
return self
end
end

View File

@ -624,10 +624,9 @@ function GROUP:GetRange()
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
-- @return #table of Wrapper.Unit#UNIT objects, indexed by number.
function GROUP:GetUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
@ -645,7 +644,6 @@ function GROUP:GetUnits()
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}.
@ -676,41 +674,38 @@ function GROUP:IsPlayer()
return self:GetUnit(1):IsPlayer()
end
--- Returns the UNIT wrapper class with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
--- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit.
-- If no underlying DCS Units exist, the method will return nil.
-- @param #GROUP self
-- @param #number UnitNumber The number of the UNIT wrapper class to be returned.
-- @return Wrapper.Unit#UNIT The UNIT wrapper class.
-- @return Wrapper.Unit#UNIT The UNIT object or nil
function GROUP:GetUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
if DCSGroup then
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
for _,_unit in pairs(units) do
local UnitFound = UNIT:Find(_unit)
-- Maybe fixed with 2.8?
local units = DCSGroup:getUnits() or {}
if units[UnitNumber] then
local UnitFound = UNIT:Find(units[UnitNumber])
if UnitFound then
return UnitFound
end
else
for _,_unit in pairs(units) do
local UnitFound = UNIT:Find(_unit)
if UnitFound then
return UnitFound
end
end
end
end
return nil
return nil
end
--- Returns the DCS Unit with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
-- If the underlying DCS Unit does not exist, the method will return try to find the next unit. Returns nil if no units are found.
-- @param #GROUP self
-- @param #number UnitNumber The number of the DCS Unit to be returned.
-- @return DCS#Unit The DCS Unit.
@ -723,8 +718,7 @@ function GROUP:GetDCSUnit( UnitNumber )
if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then
return DCSGroup:getUnit( UnitNumber )
else
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
@ -803,7 +797,20 @@ function GROUP:GetFirstUnitAlive()
return nil
end
--- Get the first unit of the group. Might be nil!
-- @param #GROUP self
-- @return Wrapper.Unit#UNIT First unit or nil if it does not exist.
function GROUP:GetFirstUnit()
self:F3({self.GroupName})
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
return units[1]
end
return nil
end
--- Returns the average velocity Vec3 vector.
-- @param Wrapper.Group#GROUP self

View File

@ -58,18 +58,16 @@
-- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function.
--
-- mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE )
--
-- ### To Blue Coalition
--
-- ### To Red Coalition
--
--
-- This would show the marker only to the Blue coalition.
--
-- ## For a Group
--
-- mymarker = MARKER:New( Coordinate , "Target Location" ):ToGroup( tankGroup )
--
-- # Removing a Marker
--
-- mymarker:Remove(60)
-- This removes the marker after 60 seconds
--
-- # Updating a Marker
--
@ -175,8 +173,6 @@ function MARKER:New( Coordinate, Text )
-- Inherit everything from FSM class.
local self = BASE:Inherit( self, FSM:New() ) -- #MARKER
local self=BASE:Inherit(self, FSM:New()) -- #MARKER
self.coordinate=UTILS.DeepCopy(Coordinate)
self.text = Text
@ -307,7 +303,7 @@ end
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
--- Marker is readonly. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadOnly()
@ -317,7 +313,7 @@ function MARKER:ReadOnly()
return self
end
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
--- Marker is read and write. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadWrite()

View File

@ -225,3 +225,10 @@ function SCENERY:FindAllByZoneName( ZoneName )
end
end
end
--- SCENERY objects cannot be destroyed via the API (at the punishment of game crash).
--@param #SCENERY self
--@return #SCENERY self
function SCENERY:Destroy()
return self
end

View File

@ -229,7 +229,7 @@ function UNIT:ReSpawnAt( Coordinate, Heading )
SpawnGroupTemplate.y = Coordinate.z
self:F( #SpawnGroupTemplate.units )
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do
local GroupUnit = UnitData -- #UNIT
self:F( GroupUnit:GetName() )
if GroupUnit:IsAlive() then
@ -630,21 +630,23 @@ end
-- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
function UNIT:GetGroup()
self:F2( self.UnitName )
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() )
self:F2( self.UnitName )
local UnitGroup = GROUP:FindByName(self.GroupName)
if UnitGroup then
return UnitGroup
else
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local grp = DCSUnit:getGroup()
if grp then
local UnitGroup = GROUP:FindByName( grp:getName() )
return UnitGroup
end
end
end
return nil
end
-- Need to add here functions to check if radar is on and which object etc.
--- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign.
-- DCS Units spawned with the @{Core.Spawn#SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name.
-- The spawn sequence number and unit number are contained within the name after the '#' sign.

View File

@ -68,7 +68,7 @@ Pene has kindly created a [tutorial series for MOOSE](https://youtube.com/playli
## [MOOSE on Discord](https://discord.gg/yBPfxC6)
## [MOOSE on Discord](https://discord.gg/aQtjcR94Qf)
MOOSE has a living (chat and video) community of users, beta testers and contributors. The gathering point is a service provided by discord.com. If you want to join this community, just click Discord and you'll be on board in no time.