mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48721859fa | ||
|
|
ee59d999f5 | ||
|
|
2f4f98cfe0 | ||
|
|
ac1f202c19 | ||
|
|
3f97ba3bd7 | ||
|
|
c0442fca68 | ||
|
|
c20927b6d7 | ||
|
|
0d5b6d4c3e | ||
|
|
bdc08a530d | ||
|
|
6ab1632b4b | ||
|
|
cec865b9fb | ||
|
|
d763e924a9 | ||
|
|
57a30621e1 | ||
|
|
f344084791 | ||
|
|
be68314c3a | ||
|
|
53cff8229b | ||
|
|
1d52e27668 | ||
|
|
6ec867196c | ||
|
|
80798f278c | ||
|
|
4e61bbb92e | ||
|
|
fabab9bfbb | ||
|
|
4ae0089e4f | ||
|
|
d82bce79c9 | ||
|
|
55fcaf1c05 | ||
|
|
e83df502ed | ||
|
|
6501e89fa2 | ||
|
|
91801d441f | ||
|
|
dd2a4ee7ff | ||
|
|
8cedd88ce2 | ||
|
|
c3fde2b698 | ||
|
|
59e8aed445 | ||
|
|
e47b8f377c | ||
|
|
793c0d988e | ||
|
|
b0eef34146 | ||
|
|
3fbfb8b528 | ||
|
|
cdd240abb7 | ||
|
|
5d802f0e16 | ||
|
|
8661d07e1e | ||
|
|
41eec658e0 | ||
|
|
9facf07955 | ||
|
|
cd4844d26c | ||
|
|
9619b11c58 | ||
|
|
535060153a |
@@ -372,9 +372,58 @@ do -- Zones
|
||||
end
|
||||
end
|
||||
|
||||
-- Drawings as zones
|
||||
if env.mission.drawings and env.mission.drawings.layers then
|
||||
|
||||
-- Loop over layers.
|
||||
for layerID, layerData in pairs(env.mission.drawings.layers or {}) do
|
||||
|
||||
-- Loop over objects in layers.
|
||||
for objectID, objectData in pairs(layerData.objects or {}) do
|
||||
|
||||
-- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
|
||||
if objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then
|
||||
|
||||
-- Name of the zone.
|
||||
local ZoneName=objectData.name or "Unknown Drawing Zone"
|
||||
|
||||
-- Reference point. All other points need to be translated by this.
|
||||
local vec2={x=objectData.mapX, y=objectData.mapY}
|
||||
|
||||
-- Copy points array.
|
||||
local points=UTILS.DeepCopy(objectData.points)
|
||||
|
||||
-- Translate points.
|
||||
for i,_point in pairs(points) do
|
||||
local point=_point --DCS#Vec2
|
||||
points[i]=UTILS.Vec2Add(point, vec2)
|
||||
end
|
||||
|
||||
-- Remove last point.
|
||||
table.remove(points, #points)
|
||||
|
||||
-- Debug output
|
||||
self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)", ZoneName, #points))
|
||||
|
||||
-- Create new polygon zone.
|
||||
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points)
|
||||
|
||||
-- Set color.
|
||||
Zone:SetColor({1, 0, 0}, 0.15)
|
||||
|
||||
-- Store in DB.
|
||||
self.ZONENAMES[ZoneName] = ZoneName
|
||||
|
||||
-- Add zone.
|
||||
self:AddZone(ZoneName, Zone)
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end -- zone
|
||||
|
||||
do -- Zone_Goal
|
||||
|
||||
@@ -1224,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 )
|
||||
@@ -1280,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
|
||||
|
||||
|
||||
@@ -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.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@@ -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
|
||||
@@ -504,7 +540,7 @@ do -- COORDINATE
|
||||
local gotscenery=false
|
||||
|
||||
local function EvaluateZone(ZoneObject)
|
||||
|
||||
BASE:T({ZoneObject})
|
||||
if ZoneObject then
|
||||
|
||||
-- Get category of scanned object.
|
||||
@@ -1285,7 +1321,15 @@ do -- COORDINATE
|
||||
self.y=alt
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set altitude to be at land height (i.e. on the ground!)
|
||||
-- @param #COORDINATE self
|
||||
function COORDINATE:SetAtLandheight()
|
||||
local alt=self:GetLandHeight()
|
||||
self.y=alt
|
||||
return self
|
||||
end
|
||||
|
||||
--- Build an air type route point.
|
||||
-- @param #COORDINATE self
|
||||
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
|
||||
@@ -1911,7 +1955,6 @@ do -- COORDINATE
|
||||
-- @param #COORDINATE self
|
||||
-- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object.
|
||||
function COORDINATE:StopBigSmokeAndFire( name )
|
||||
self:F2( { name = name } )
|
||||
name = name or self.firename
|
||||
trigger.action.effectSmokeStop( name )
|
||||
end
|
||||
|
||||
@@ -53,6 +53,7 @@ do -- SET_BASE
|
||||
-- @field #table Index Table of indices.
|
||||
-- @field #table List Unused table.
|
||||
-- @field Core.Scheduler#SCHEDULER CallScheduler
|
||||
-- @field #SET_BASE.Filters Filter Filters
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
--- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects.
|
||||
@@ -83,6 +84,11 @@ do -- SET_BASE
|
||||
YieldInterval = nil,
|
||||
}
|
||||
|
||||
--- Filters
|
||||
-- @type SET_BASE.Filters
|
||||
-- @field #table Coalition Coalitions
|
||||
-- @field #table Prefix Prefixes.
|
||||
|
||||
--- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE
|
||||
@@ -135,11 +141,12 @@ do -- SET_BASE
|
||||
|
||||
--- Clear the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #boolean TriggerEvent If `true`, an event remove is triggered for each group that is removed from the set.
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:Clear()
|
||||
function SET_BASE:Clear(TriggerEvent)
|
||||
|
||||
for Name, Object in pairs( self.Set ) do
|
||||
self:Remove( Name )
|
||||
self:Remove( Name, not TriggerEvent )
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -166,7 +173,7 @@ do -- SET_BASE
|
||||
|
||||
--- Gets a list of the Names of the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
-- @return #table Table of names.
|
||||
function SET_BASE:GetSetNames() -- R2.3
|
||||
self:F2()
|
||||
|
||||
@@ -181,7 +188,7 @@ do -- SET_BASE
|
||||
|
||||
--- Returns a table of the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
-- @return #table Table of objects.
|
||||
function SET_BASE:GetSetObjects() -- R2.3
|
||||
self:F2()
|
||||
|
||||
@@ -197,16 +204,21 @@ do -- SET_BASE
|
||||
--- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name.
|
||||
-- @param #SET_BASE self
|
||||
-- @param #string ObjectName
|
||||
-- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event.
|
||||
-- @param #boolean NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event.
|
||||
function SET_BASE:Remove( ObjectName, NoTriggerEvent )
|
||||
self:F2( { ObjectName = ObjectName } )
|
||||
|
||||
local TriggerEvent = true
|
||||
if NoTriggerEvent then TriggerEvent = false end
|
||||
if NoTriggerEvent then
|
||||
TriggerEvent = false
|
||||
else
|
||||
TriggerEvent = true
|
||||
end
|
||||
|
||||
local Object = self.Set[ObjectName]
|
||||
|
||||
if Object then
|
||||
|
||||
for Index, Key in ipairs( self.Index ) do
|
||||
if Key == ObjectName then
|
||||
table.remove( self.Index, Index )
|
||||
@@ -214,6 +226,7 @@ do -- SET_BASE
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- When NoTriggerEvent is true, then no Removed event will be triggered.
|
||||
if TriggerEvent then
|
||||
self:Removed( ObjectName, Object )
|
||||
@@ -311,7 +324,6 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @param Core.Set#SET_BASE SetB Set other set, called *B*.
|
||||
-- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*.
|
||||
|
||||
function SET_BASE:GetSetIntersection(SetB)
|
||||
|
||||
local intersection=SET_BASE:New()
|
||||
@@ -461,16 +473,32 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:FilterOnce()
|
||||
|
||||
--self:Clear()
|
||||
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
else
|
||||
self:Remove(ObjectName, true)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clear all filters. You still need to apply :FilterOnce()
|
||||
-- @param #SET_BASE self
|
||||
-- @return #SET_BASE self
|
||||
function SET_BASE:FilterClear()
|
||||
|
||||
for key,value in pairs(self.Filter) do
|
||||
self.Filter[key]={}
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Starts the filtering for the defined collection.
|
||||
-- @param #SET_BASE self
|
||||
@@ -817,7 +845,7 @@ do -- SET_BASE
|
||||
--- Decides whether an object is in the SET
|
||||
-- @param #SET_BASE self
|
||||
-- @param #table Object
|
||||
-- @return #SET_BASE self
|
||||
-- @return #boolean `true` if object is in set and `false` otherwise.
|
||||
function SET_BASE:IsInSet( Object )
|
||||
self:F3( Object )
|
||||
local outcome = false
|
||||
@@ -1021,9 +1049,9 @@ do -- SET_GROUP
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
--- Get a *new* set that only contains alive groups.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #table Table of objects
|
||||
-- @return #SET_GROUP Set of alive groups.
|
||||
function SET_GROUP:GetAliveSet()
|
||||
self:F2()
|
||||
|
||||
@@ -1169,11 +1197,14 @@ do -- SET_GROUP
|
||||
--- Builds a set of groups in zones.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
function SET_GROUP:FilterZones( Zones, Clear )
|
||||
|
||||
if Clear or not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
end
|
||||
|
||||
local zones = {}
|
||||
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
|
||||
zones = Zones.Set
|
||||
@@ -1183,34 +1214,12 @@ do -- SET_GROUP
|
||||
else
|
||||
zones = Zones
|
||||
end
|
||||
|
||||
for _, Zone in pairs( zones ) do
|
||||
local zonename = Zone:GetName()
|
||||
self.Filter.Zones[zonename] = Zone
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups in zones.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
end
|
||||
local zones = {}
|
||||
if Zones.ClassName and Zones.ClassName == "SET_ZONE" then
|
||||
zones = Zones.Set
|
||||
elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then
|
||||
self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!")
|
||||
return self
|
||||
else
|
||||
zones = Zones
|
||||
end
|
||||
for _,Zone in pairs( zones ) do
|
||||
local zonename = Zone:GetName()
|
||||
self.Filter.Zones[zonename] = Zone
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1218,17 +1227,21 @@ do -- SET_GROUP
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterCoalitions( Coalitions )
|
||||
if not self.Filter.Coalitions then
|
||||
function SET_GROUP:FilterCoalitions( Coalitions, Clear )
|
||||
|
||||
if Clear or (not self.Filter.Coalitions) then
|
||||
self.Filter.Coalitions = {}
|
||||
end
|
||||
if type( Coalitions ) ~= "table" then
|
||||
Coalitions = { Coalitions }
|
||||
end
|
||||
|
||||
-- Ensure table.
|
||||
Coalitions = UTILS.EnsureTable(Coalitions, false)
|
||||
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1236,17 +1249,22 @@ do -- SET_GROUP
|
||||
-- Possible current categories are plane, helicopter, ground, ship.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_GROUP self
|
||||
function SET_GROUP:FilterCategories( Categories )
|
||||
if not self.Filter.Categories then
|
||||
function SET_GROUP:FilterCategories( Categories, Clear )
|
||||
|
||||
if Clear or not self.Filter.Categories then
|
||||
self.Filter.Categories = {}
|
||||
end
|
||||
|
||||
if type( Categories ) ~= "table" then
|
||||
Categories = { Categories }
|
||||
end
|
||||
|
||||
for CategoryID, Category in pairs( Categories ) do
|
||||
self.Filter.Categories[Category] = Category
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1899,6 +1917,41 @@ do -- SET_GROUP
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Get the closest group of the set with respect to a given reference coordinate. Optionally, only groups of given coalitions are considered in the search.
|
||||
-- @param #SET_GROUP self
|
||||
-- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined.
|
||||
-- @return Wrapper.Group#GROUP The closest group (if any).
|
||||
-- @return #number Distance in meters to the closest group.
|
||||
function SET_GROUP:GetClosestGroup(Coordinate, Coalitions)
|
||||
|
||||
local Set = self:GetSet()
|
||||
|
||||
local dmin=math.huge
|
||||
local gmin=nil
|
||||
|
||||
for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP
|
||||
local group=GroupData --Wrapper.Group#GROUP
|
||||
|
||||
if group and group:IsAlive() and (Coalitions==nil or UTILS.IsAnyInTable(Coalitions, group:GetCoalition())) then
|
||||
|
||||
local coord=group:GetCoord()
|
||||
|
||||
-- Distance between ref. coordinate and group coordinate.
|
||||
local d=UTILS.VecDist3D(Coordinate, coord)
|
||||
|
||||
if d<dmin then
|
||||
dmin=d
|
||||
gmin=group
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return gmin, dmin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
do -- SET_UNIT
|
||||
@@ -2120,9 +2173,11 @@ do -- SET_UNIT
|
||||
if type( Coalitions ) ~= "table" then
|
||||
Coalitions = { Coalitions }
|
||||
end
|
||||
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -4030,7 +4085,7 @@ do -- SET_CLIENT
|
||||
--- Builds a set of clients in zones.
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
|
||||
-- @return #SET_TABLE self
|
||||
-- @return #SET_CLIENT self
|
||||
function SET_CLIENT:FilterZones( Zones )
|
||||
if not self.Filter.Zones then
|
||||
self.Filter.Zones = {}
|
||||
@@ -5840,8 +5895,7 @@ do -- SET_ZONE
|
||||
-- If zones overlap, the first zone that validates the test is returned.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
|
||||
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
|
||||
-- @return #nil No zone has been found.
|
||||
-- @return Core.Zone#ZONE_BASE The zone (if any) that validates the coordinate location.
|
||||
function SET_ZONE:IsCoordinateInZone( Coordinate )
|
||||
|
||||
for _, Zone in pairs( self:GetSet() ) do
|
||||
@@ -5853,6 +5907,27 @@ do -- SET_ZONE
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Get the closest zone to a given coordinate.
|
||||
-- @param #SET_ZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
|
||||
-- @return Core.Zone#ZONE_BASE The closest zone (if any).
|
||||
-- @return #number Distance to ref coordinate in meters.
|
||||
function SET_ZONE:GetClosestZone( Coordinate )
|
||||
|
||||
local dmin=math.huge
|
||||
local zmin=nil
|
||||
for _, Zone in pairs( self:GetSet() ) do
|
||||
local Zone = Zone -- Core.Zone#ZONE_BASE
|
||||
local d=Zone:Get2DDistance(Coordinate)
|
||||
if d<dmin then
|
||||
dmin=d
|
||||
zmin=Zone
|
||||
end
|
||||
end
|
||||
|
||||
return zmin, dmin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -6167,6 +6242,461 @@ do -- SET_ZONE_GOAL
|
||||
|
||||
end
|
||||
|
||||
do -- SET_OPSZONE
|
||||
|
||||
--- @type SET_OPSZONE
|
||||
-- @extends Core.Set#SET_BASE
|
||||
|
||||
--- Mission designers can use the @{Core.Set#SET_OPSZONE} class to build sets of zones of various types.
|
||||
--
|
||||
-- ## SET_OPSZONE constructor
|
||||
--
|
||||
-- Create a new SET_OPSZONE object with the @{#SET_OPSZONE.New} method:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.New}: Creates a new SET_OPSZONE object.
|
||||
--
|
||||
-- ## Add or Remove ZONEs from SET_OPSZONE
|
||||
--
|
||||
-- ZONEs can be added and removed using the @{Core.Set#SET_OPSZONE.AddZonesByName} and @{Core.Set#SET_OPSZONE.RemoveZonesByName} respectively.
|
||||
-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_OPSZONE.
|
||||
--
|
||||
-- ## SET_OPSZONE filter criteria
|
||||
--
|
||||
-- You can set filter criteria to build the collection of zones in SET_OPSZONE.
|
||||
-- Filter criteria are defined by:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.FilterPrefixes}: Builds the SET_OPSZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*.
|
||||
--
|
||||
-- Once the filter criteria have been set for the SET_OPSZONE, you can start filtering using:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.FilterStart}: Starts the filtering of the zones within the SET_OPSZONE.
|
||||
--
|
||||
-- ## SET_OPSZONE iterators
|
||||
--
|
||||
-- Once the filters have been defined and the SET_OPSZONE has been built, you can iterate the SET_OPSZONE with the available iterator methods.
|
||||
-- The iterator methods will walk the SET_OPSZONE set, and call for each airbase within the set a function that you provide.
|
||||
-- The following iterator methods are currently available within the SET_OPSZONE:
|
||||
--
|
||||
-- * @{#SET_OPSZONE.ForEachZone}: Calls a function for each zone it finds within the SET_OPSZONE.
|
||||
--
|
||||
-- ===
|
||||
-- @field #SET_OPSZONE SET_OPSZONE
|
||||
SET_OPSZONE = {
|
||||
ClassName = "SET_OPSZONE",
|
||||
Zones = {},
|
||||
Filter = {
|
||||
Prefixes = nil,
|
||||
Coalitions = nil,
|
||||
},
|
||||
FilterMeta = {
|
||||
Coalitions = {
|
||||
red = coalition.side.RED,
|
||||
blue = coalition.side.BLUE,
|
||||
neutral = coalition.side.NEUTRAL,
|
||||
},
|
||||
}, --FilterMeta
|
||||
}
|
||||
|
||||
--- Creates a new SET_OPSZONE object, building a set of zones.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:New()
|
||||
|
||||
-- Inherits from BASE
|
||||
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.OPSZONES ) )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Add an OPSZONE to set.
|
||||
-- @param Core.Set#SET_OPSZONE self
|
||||
-- @param Ops.OpsZone#OPSZONE Zone The OPSZONE object.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:AddZone( Zone )
|
||||
|
||||
self:Add( Zone:GetName(), Zone )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Remove ZONEs from SET_OPSZONE.
|
||||
-- @param Core.Set#SET_OPSZONE self
|
||||
-- @param #table RemoveZoneNames A single name or an array of OPSZONE names.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:RemoveZonesByName( RemoveZoneNames )
|
||||
|
||||
local RemoveZoneNamesArray = (type( RemoveZoneNames ) == "table") and RemoveZoneNames or { RemoveZoneNames }
|
||||
|
||||
--UTILS.EnsureTable(Object,ReturnNil)
|
||||
|
||||
for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do
|
||||
self:Remove( RemoveZoneName )
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Finds a Zone based on its name.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string ZoneName
|
||||
-- @return Ops.OpsZone#OPSZONE The found Zone.
|
||||
function SET_OPSZONE:FindZone( ZoneName )
|
||||
|
||||
local ZoneFound = self.Set[ZoneName]
|
||||
|
||||
return ZoneFound
|
||||
end
|
||||
|
||||
--- Get a random zone from the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return Ops.OpsZone#OPSZONE The random Zone.
|
||||
function SET_OPSZONE:GetRandomZone()
|
||||
|
||||
if self:Count() ~= 0 then
|
||||
|
||||
local Index = self.Index
|
||||
local ZoneFound = nil -- Core.Zone#ZONE_BASE
|
||||
|
||||
-- Loop until a zone has been found.
|
||||
-- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected.
|
||||
-- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues!
|
||||
while not ZoneFound do
|
||||
local ZoneRandom = math.random( 1, #Index )
|
||||
ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe()
|
||||
end
|
||||
|
||||
return ZoneFound
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--- Set a zone probability.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string ZoneName The name of the zone.
|
||||
-- @param #number Probability The probability in percent.
|
||||
function SET_OPSZONE:SetZoneProbability( ZoneName, Probability )
|
||||
local Zone = self:FindZone( ZoneName )
|
||||
Zone:SetZoneProbability( Probability )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of OPSZONEs that contain the given string in their name.
|
||||
-- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterPrefixes( Prefixes )
|
||||
|
||||
if not self.Filter.Prefixes then
|
||||
self.Filter.Prefixes = {}
|
||||
end
|
||||
|
||||
Prefixes=UTILS.EnsureTable(Prefixes, false)
|
||||
|
||||
for PrefixID, Prefix in pairs( Prefixes ) do
|
||||
self.Filter.Prefixes[Prefix] = Prefix
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of groups of coalitions. Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterCoalitions(Coalitions)
|
||||
|
||||
-- Create an empty set.
|
||||
if not self.Filter.Coalitions then
|
||||
self.Filter.Coalitions={}
|
||||
end
|
||||
|
||||
-- Ensure we got a table.
|
||||
Coalitions=UTILS.EnsureTable(Coalitions, false)
|
||||
|
||||
-- Set filter.
|
||||
for CoalitionID, Coalition in pairs( Coalitions ) do
|
||||
self.Filter.Coalitions[Coalition] = Coalition
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Filters for the defined collection.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterOnce()
|
||||
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
|
||||
-- First remove the object (without creating an event).
|
||||
self:Remove(ObjectName, true)
|
||||
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Clear all filters. You still need to apply `FilterOnce()` to have an effect on the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterClear()
|
||||
|
||||
local parent=self:GetParent(self, SET_OPSZONE) --#SET_BASE
|
||||
|
||||
parent:FilterClear()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Starts the filtering.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterStart()
|
||||
|
||||
if _DATABASE then
|
||||
|
||||
-- We initialize the first set.
|
||||
for ObjectName, Object in pairs( self.Database ) do
|
||||
if self:IsIncludeObject( Object ) then
|
||||
self:Add( ObjectName, Object )
|
||||
else
|
||||
self:RemoveZonesByName( ObjectName )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:HandleEvent( EVENTS.NewZoneGoal )
|
||||
self:HandleEvent( EVENTS.DeleteZoneGoal )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Stops the filtering for the defined collection.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:FilterStop()
|
||||
|
||||
self:UnHandleEvent( EVENTS.NewZoneGoal )
|
||||
self:UnHandleEvent( EVENTS.DeleteZoneGoal )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
|
||||
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
function SET_OPSZONE:AddInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
|
||||
end
|
||||
|
||||
--- Handles the Database to check on any event that Object exists in the Database.
|
||||
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA Event
|
||||
-- @return #string The name of the AIRBASE
|
||||
-- @return #table The AIRBASE
|
||||
function SET_OPSZONE:FindInDatabase( Event )
|
||||
self:F3( { Event } )
|
||||
|
||||
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
|
||||
end
|
||||
|
||||
--- Iterate the SET_OPSZONE and call an iterator function for each ZONE, providing the ZONE and optional parameters.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_OPSZONE. The function needs to accept a AIRBASE parameter.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:ForEachZone( IteratorFunction, ... )
|
||||
self:F2( arg )
|
||||
|
||||
self:ForEach( IteratorFunction, arg, self:GetSet() )
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Private function that checks if an object is contained in the set or filtered.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Ops.OpsZone#OPSZONE MZone The OPSZONE object.
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:IsIncludeObject( MZone )
|
||||
self:F2( MZone )
|
||||
|
||||
local MZoneInclude = true
|
||||
|
||||
if MZone then
|
||||
|
||||
local MZoneName = MZone:GetName()
|
||||
|
||||
if self.Filter.Prefixes then
|
||||
|
||||
local MZonePrefix = false
|
||||
|
||||
-- Loop over prefixes.
|
||||
for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do
|
||||
|
||||
-- Prifix
|
||||
self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } )
|
||||
|
||||
if string.find(MZoneName, ZonePrefix, 1) then
|
||||
MZonePrefix = true
|
||||
break --Break the loop as we found the prefix.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:T( { "Evaluated Prefix", MZonePrefix } )
|
||||
|
||||
MZoneInclude = MZoneInclude and MZonePrefix
|
||||
end
|
||||
|
||||
-- Filter coalitions.
|
||||
if self.Filter.Coalitions then
|
||||
|
||||
local MGroupCoalition = false
|
||||
|
||||
local coalition=MZone:GetOwner()
|
||||
|
||||
for _, CoalitionName in pairs( self.Filter.Coalitions ) do
|
||||
|
||||
if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName]==coalition then
|
||||
MGroupCoalition = true
|
||||
break -- Break the loop the coalition is contains.
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
MZoneInclude = MZoneInclude and MGroupCoalition
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self:T2( MZoneInclude )
|
||||
return MZoneInclude
|
||||
end
|
||||
|
||||
--- Handles the OnEventNewZone event for the Set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SET_OPSZONE:OnEventNewZoneGoal( EventData )
|
||||
|
||||
-- Debug info.
|
||||
self:T( { "New Zone Capture Coalition", EventData } )
|
||||
self:T( { "Zone Capture Coalition", EventData.ZoneGoal } )
|
||||
|
||||
if EventData.ZoneGoal then
|
||||
if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then
|
||||
self:T( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
|
||||
self:Add( EventData.ZoneGoal.ZoneName, EventData.ZoneGoal )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Handles the OnDead or OnCrash event for alive units set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function SET_OPSZONE:OnEventDeleteZoneGoal( EventData ) -- R2.1
|
||||
self:F3( { EventData } )
|
||||
|
||||
if EventData.ZoneGoal then
|
||||
local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName )
|
||||
if Zone and Zone.ZoneName then
|
||||
|
||||
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
|
||||
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
|
||||
-- And this is a problem because it will remove all entries from the SET_OPSZONEs.
|
||||
-- To prevent this from happening, the Zone object has a flag NoDestroy.
|
||||
-- When true, the SET_OPSZONE won't Remove the Zone object from the set.
|
||||
-- This flag is switched off after the event handlers have been called in the EVENT class.
|
||||
self:F( { ZoneNoDestroy = Zone.NoDestroy } )
|
||||
if Zone.NoDestroy then
|
||||
else
|
||||
self:Remove( Zone.ZoneName )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Start all opszones of the set.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @return #SET_OPSZONE self
|
||||
function SET_OPSZONE:Start()
|
||||
|
||||
for _,_Zone in pairs( self:GetSet() ) do
|
||||
local Zone = _Zone --Ops.OpsZone#OPSZONE
|
||||
if Zone:IsStopped() then
|
||||
Zone:Start()
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Validate if a coordinate is in one of the zones in the set.
|
||||
-- Returns the ZONE object where the coordiante is located.
|
||||
-- If zones overlap, the first zone that validates the test is returned.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
|
||||
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
|
||||
-- @return #nil No zone has been found.
|
||||
function SET_OPSZONE:IsCoordinateInZone( Coordinate )
|
||||
|
||||
for _,_Zone in pairs( self:GetSet() ) do
|
||||
local Zone = _Zone --Ops.OpsZone#OPSZONE
|
||||
if Zone:GetZone():IsCoordinateInZone( Coordinate ) then
|
||||
return Zone
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Get the closest OPSZONE from a given reference coordinate. Only started zones are considered.
|
||||
-- @param #SET_OPSZONE self
|
||||
-- @param Core.Point#COORDINATE Coordinate The reference coordinate from which the closest zone is determined.
|
||||
-- @param #table Coalitions Only consider the given coalition(s), *e.g.* `{coaliton.side.RED}` to find the closest red zone.
|
||||
-- @return Ops.OpsZone#OPSZONE The closest OPSZONE (if any).
|
||||
-- @return #number Distance to ref coordinate in meters.
|
||||
function SET_OPSZONE:GetClosestZone( Coordinate, Coalitions )
|
||||
|
||||
Coalitions=UTILS.EnsureTable(Coalitions, true)
|
||||
|
||||
local dmin=math.huge --#number
|
||||
local zmin=nil --Ops.OpsZone#OPSZONE
|
||||
|
||||
for _,_opszone in pairs(self:GetSet()) do
|
||||
local opszone=_opszone --Ops.OpsZone#OPSZONE
|
||||
|
||||
local coal=opszone:GetOwner()
|
||||
|
||||
if opszone:IsStarted() and (Coalitions==nil or (Coalitions and UTILS.IsInTable(Coalitions, coal))) then
|
||||
|
||||
-- Get 2D distance.
|
||||
local d=opszone:GetZone():Get2DDistance(Coordinate)
|
||||
|
||||
if d<dmin then
|
||||
dmin=d
|
||||
zmin=opszone
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return zmin, dmin
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
do -- SET_OPSGROUP
|
||||
@@ -6289,7 +6819,7 @@ do -- SET_OPSGROUP
|
||||
return self
|
||||
end
|
||||
|
||||
--- Gets the Set.
|
||||
--- Gets a **new** set that only contains alive groups.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:GetAliveSet()
|
||||
@@ -6454,11 +6984,12 @@ do -- SET_OPSGROUP
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral" or combinations as a table, for example `{"red", "neutral"}`.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCoalitions(Coalitions)
|
||||
function SET_OPSGROUP:FilterCoalitions(Coalitions, Clear)
|
||||
|
||||
-- Create an empty set.
|
||||
if not self.Filter.Coalitions then
|
||||
if Clear or not self.Filter.Coalitions then
|
||||
self.Filter.Coalitions={}
|
||||
end
|
||||
|
||||
@@ -6487,10 +7018,11 @@ do -- SET_OPSGROUP
|
||||
--
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship" or combinations as a table, for example `{"plane", "helicopter"}`.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCategories( Categories )
|
||||
function SET_OPSGROUP:FilterCategories( Categories, Clear )
|
||||
|
||||
if not self.Filter.Categories then
|
||||
if Clear or not self.Filter.Categories then
|
||||
self.Filter.Categories={}
|
||||
end
|
||||
|
||||
@@ -6548,11 +7080,12 @@ do -- SET_OPSGROUP
|
||||
--- Builds a set of groups of defined countries.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Countries Can take those country strings known within DCS world.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterCountries(Countries)
|
||||
function SET_OPSGROUP:FilterCountries(Countries, Clear)
|
||||
|
||||
-- Create empty table if necessary.
|
||||
if not self.Filter.Countries then
|
||||
if Clear or not self.Filter.Countries then
|
||||
self.Filter.Countries = {}
|
||||
end
|
||||
|
||||
@@ -6574,11 +7107,12 @@ do -- SET_OPSGROUP
|
||||
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string.
|
||||
-- @param #SET_OPSGROUP self
|
||||
-- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings.
|
||||
-- @param #boolean Clear If `true`, clear any previously defined filters.
|
||||
-- @return #SET_OPSGROUP self
|
||||
function SET_OPSGROUP:FilterPrefixes(Prefixes)
|
||||
function SET_OPSGROUP:FilterPrefixes(Prefixes, Clear)
|
||||
|
||||
-- Create emtpy table if necessary.
|
||||
if not self.Filter.GroupPrefixes then
|
||||
if Clear or not self.Filter.GroupPrefixes then
|
||||
self.Filter.GroupPrefixes={}
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
-- @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
|
||||
@@ -309,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 = {
|
||||
@@ -351,6 +365,7 @@ ATIS = {
|
||||
relHumidity = nil,
|
||||
ReportmBar = false,
|
||||
TransmitOnlyWithPlayers = false,
|
||||
ATISforFARPs = false,
|
||||
}
|
||||
|
||||
--- NATO alphabet.
|
||||
@@ -593,7 +608,7 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "0.9.12"
|
||||
ATIS.version = "0.9.14"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@@ -619,7 +634,7 @@ ATIS.version = "0.9.12"
|
||||
-- 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.
|
||||
@@ -1049,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.
|
||||
@@ -1257,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 ) )
|
||||
|
||||
@@ -1473,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 ---
|
||||
------------
|
||||
@@ -1790,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
|
||||
@@ -1865,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
|
||||
@@ -1884,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
|
||||
@@ -1987,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
|
||||
@@ -2019,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
|
||||
@@ -2050,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.
|
||||
@@ -2117,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
|
||||
|
||||
@@ -2246,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
|
||||
@@ -2263,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
|
||||
@@ -2399,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, ";", " . " )
|
||||
|
||||
@@ -32,20 +32,21 @@
|
||||
-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73\)) (CVN-73) [Super Carrier Module]
|
||||
-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module]
|
||||
-- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59\)) (CV-59) [Heatblur Carrier Module]
|
||||
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12) [**WIP**]
|
||||
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05) [**WIP**]
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1) [**WIP**]
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6) [**WIP**]
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**]
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02) [**WIP**]
|
||||
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12)
|
||||
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05)
|
||||
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1)
|
||||
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6)
|
||||
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61)
|
||||
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02)
|
||||
--
|
||||
-- **Supported Aircraft:**
|
||||
--
|
||||
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
|
||||
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
|
||||
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
|
||||
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
|
||||
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
|
||||
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO mod) (Player & AI)
|
||||
-- * [FE/A-18E/F/G Superhornet](https://forum.dcs.world/topic/316971-cjs-super-hornet-community-mod-v20-official-thread/) (CJS mod) (Player & AI)
|
||||
-- * F/A-18C Hornet (AI)
|
||||
-- * F-14A Tomcat (AI)
|
||||
-- * E-2D Hawkeye (AI)
|
||||
@@ -1278,7 +1279,10 @@ AIRBOSS = {
|
||||
-- @field #string S3BTANKER Lockheed S-3B Viking tanker.
|
||||
-- @field #string E2D Grumman E-2D Hawkeye AWACS.
|
||||
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
|
||||
-- @field #string T45C T-45C by VNAO
|
||||
-- @field #string T45C T-45C by VNAO.
|
||||
-- @field #string RHINOE F/A-18E Superhornet (mod).
|
||||
-- @field #string RHINOF F/A-18F Superhornet (mod).
|
||||
-- @field #string GROWLER FEA-18G Superhornet (mod).
|
||||
AIRBOSS.AircraftCarrier={
|
||||
AV8B="AV8BNA",
|
||||
HORNET="FA-18C_hornet",
|
||||
@@ -1292,6 +1296,9 @@ AIRBOSS.AircraftCarrier={
|
||||
S3BTANKER="S-3B Tanker",
|
||||
E2D="E-2C",
|
||||
C2A="C2A_Greyhound",
|
||||
RHINOE="FA-18E",
|
||||
RHINOF="FA-18F",
|
||||
GROWLER="EA-18G",
|
||||
}
|
||||
|
||||
--- Carrier types.
|
||||
@@ -1302,7 +1309,7 @@ AIRBOSS.AircraftCarrier={
|
||||
-- @field #string STENNIS USS John C. Stennis (CVN-74)
|
||||
-- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module]
|
||||
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete]
|
||||
-- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!]
|
||||
-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier]
|
||||
-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier]
|
||||
-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier]
|
||||
@@ -5177,7 +5184,10 @@ end
|
||||
function AIRBOSS:_GetAircraftAoA( playerData )
|
||||
|
||||
-- Get AC type.
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
@@ -5340,7 +5350,10 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
|
||||
step = step or playerData.step
|
||||
|
||||
-- Get AC type.
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
|
||||
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
|
||||
@@ -6251,6 +6264,9 @@ function AIRBOSS:_RefuelAI( flight )
|
||||
actype==AIRBOSS.AircraftCarrier.F14B or
|
||||
actype==AIRBOSS.AircraftCarrier.F14A_AI or
|
||||
actype==AIRBOSS.AircraftCarrier.HORNET or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOE or
|
||||
actype==AIRBOSS.AircraftCarrier.RHINOF or
|
||||
actype==AIRBOSS.AircraftCarrier.GROWLER or
|
||||
actype==AIRBOSS.AircraftCarrier.FA18C or
|
||||
actype==AIRBOSS.AircraftCarrier.S3B or
|
||||
actype==AIRBOSS.AircraftCarrier.S3BTANKER then
|
||||
@@ -6348,7 +6364,11 @@ function AIRBOSS:_LandAI( flight )
|
||||
-- Aircraft speed when flying the pattern.
|
||||
local Speed = UTILS.KnotsToKmph( 200 )
|
||||
|
||||
if flight.actype == AIRBOSS.AircraftCarrier.HORNET or flight.actype == AIRBOSS.AircraftCarrier.FA18C then
|
||||
if flight.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.FA18C
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
Speed = UTILS.KnotsToKmph( 200 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
|
||||
Speed = UTILS.KnotsToKmph( 150 )
|
||||
@@ -9172,7 +9192,13 @@ function AIRBOSS:_DirtyUp( playerData )
|
||||
self:_PlayerHint( playerData )
|
||||
|
||||
-- Radio call "Say/Fly needles". Delayed by 10/15 seconds.
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET or playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14A
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.F14B
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
|
||||
then
|
||||
local callsay = self:_NewRadioCall( self.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard )
|
||||
local callfly = self:_NewRadioCall( self.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard )
|
||||
self:RadioTransmission( self.MarshalRadio, callsay, false, 55, nil, true )
|
||||
@@ -10263,7 +10289,10 @@ function AIRBOSS:_Trapped( playerData )
|
||||
|
||||
-- Get current wire (estimate). This now based on the position where the player comes to a standstill which should reflect the trapped wire better.
|
||||
local dcorr = 100
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
dcorr = 100
|
||||
elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
-- TODO: Check Tomcat.
|
||||
@@ -12463,7 +12492,10 @@ function AIRBOSS:_PlayerHint( playerData, delay, soundoff )
|
||||
if playerData.step == AIRBOSS.PatternStep.BULLSEYE then
|
||||
-- Hint follow the needles.
|
||||
if playerData.difficulty == AIRBOSS.Difficulty.EASY then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
hint = hint .. string.format( "\nIntercept glideslope and follow the needles." )
|
||||
else
|
||||
hint = hint .. string.format( "\nIntercept glideslope." )
|
||||
@@ -13957,6 +13989,10 @@ function AIRBOSS:_GetACNickname( actype )
|
||||
nickname = "Tomcat"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
nickname = "Hornet"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.RHINOE or actype == AIRBOSS.AircraftCarrier.RHINOF then
|
||||
nickname = "Rhino"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
nickname = "Growler"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.S3B or actype == AIRBOSS.AircraftCarrier.S3BTANKER then
|
||||
nickname = "Viking"
|
||||
end
|
||||
|
||||
@@ -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
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Ops.CTLD
|
||||
-- @image OPS_CTLD.jpg
|
||||
|
||||
-- Last Update December 2022
|
||||
-- Last Update Jan 2023
|
||||
|
||||
do
|
||||
|
||||
@@ -712,7 +712,10 @@ do
|
||||
-- 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.
|
||||
-- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you.
|
||||
--
|
||||
-- ## 2.1 User functions
|
||||
--
|
||||
-- ### 2.1.1 Adjust or add chopper unit-type capabilities
|
||||
@@ -730,6 +733,7 @@ do
|
||||
-- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400},
|
||||
-- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700},
|
||||
-- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
|
||||
-- ["Mi-8MTV2"] = {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},
|
||||
-- ["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},
|
||||
@@ -967,7 +971,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",
|
||||
@@ -1075,8 +1182,9 @@ CTLD.UnitTypes = {
|
||||
["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400},
|
||||
["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700},
|
||||
["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},
|
||||
["Mi-8MT"] = {type="Mi-8MT", 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.
|
||||
@@ -1088,7 +1196,7 @@ CTLD.UnitTypes = {
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.23"
|
||||
CTLD.version="1.0.29"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@@ -1171,6 +1279,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
|
||||
-- radio beacons
|
||||
self.RadioSound = "beacon.ogg"
|
||||
self.RadioPath = "l10n/DEFAULT/"
|
||||
|
||||
-- zones stuff
|
||||
self.pickupZones = {}
|
||||
@@ -1199,6 +1308,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self.Engineers = 0 -- #number use as counter
|
||||
self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects
|
||||
self.EngineerSearch = 2000 -- #number search distance for crates to build or repair
|
||||
self.nobuildmenu = false -- enfore engineer build only?
|
||||
|
||||
-- setup
|
||||
self.CrateDistance = 35 -- list/load crates in this radius
|
||||
@@ -1255,6 +1365,8 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
|
||||
-- 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)
|
||||
@@ -2899,7 +3011,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()
|
||||
@@ -2944,7 +3056,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
|
||||
@@ -3209,6 +3326,12 @@ function CTLD:_RefreshF10Menus()
|
||||
self.subcats[entry.Subcategory] = entry.Subcategory
|
||||
end
|
||||
end
|
||||
for _id,_cargo in pairs(self.Cargo_Statics) do
|
||||
local entry = _cargo -- #CTLD_CARGO
|
||||
if not self.subcats[entry.Subcategory] then
|
||||
self.subcats[entry.Subcategory] = entry.Subcategory
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- build unit menus
|
||||
@@ -3272,6 +3395,13 @@ function CTLD:_RefreshF10Menus()
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
local subcat = entry.Subcategory
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
else
|
||||
for _,_entry in pairs(self.Cargo_Crates) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
@@ -3279,17 +3409,21 @@ function CTLD:_RefreshF10Menus()
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
for _,_entry in pairs(self.Cargo_Statics) do
|
||||
local entry = _entry -- #CTLD_CARGO
|
||||
menucount = menucount + 1
|
||||
local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0)
|
||||
menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry)
|
||||
end
|
||||
end
|
||||
listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit)
|
||||
local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit)
|
||||
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
|
||||
local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh()
|
||||
if not self.nobuildmenu then
|
||||
local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit)
|
||||
local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh()
|
||||
else
|
||||
unloadmenu:Refresh()
|
||||
end
|
||||
end
|
||||
if self:IsHercules(_unit) then
|
||||
local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh()
|
||||
@@ -3374,13 +3508,14 @@ end
|
||||
-- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1".
|
||||
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
||||
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
||||
function CTLD:AddStaticsCargo(Name,Mass,Stock)
|
||||
-- @param #string SubCategory Name of sub-category (optional).
|
||||
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory)
|
||||
self:T(self.lid .. " AddStaticsCargo")
|
||||
self.CargoCounter = self.CargoCounter + 1
|
||||
local type = CTLD_CARGO.Enum.STATIC
|
||||
local template = STATIC:FindByName(Name,true):GetTypeName()
|
||||
-- Crates are not directly loadable
|
||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock)
|
||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory)
|
||||
table.insert(self.Cargo_Statics,cargo)
|
||||
return self
|
||||
end
|
||||
@@ -3753,32 +3888,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(1)
|
||||
local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz
|
||||
--local Frequency = Mhz*1000000
|
||||
local Sound = "l10n/DEFAULT/"..Sound
|
||||
--local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation))
|
||||
--trigger.action.stopRadioTransmission(name)
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
--local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
elseif Zone then
|
||||
local ZoneCoord = Zone:GetCoordinate(1)
|
||||
local ZoneVec3 = ZoneCoord:GetVec3()
|
||||
local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz
|
||||
--local Frequency = Mhz*1000000
|
||||
local Sound = "l10n/DEFAULT/"..Sound
|
||||
--local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation))
|
||||
--trigger.action.stopRadioTransmission(name)
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
--local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
|
||||
local Frequency = Mhz * 1000000 -- Freq in Hertz
|
||||
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() 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()
|
||||
@@ -3801,11 +3957,7 @@ function CTLD:_RefreshRadioBeacons()
|
||||
local Name = czone.name
|
||||
local FM = FMbeacon.frequency -- MHz
|
||||
local VHF = VHFbeacon.frequency -- KHz
|
||||
local UHF = UHFbeacon.frequency -- MHz
|
||||
-- local co = coroutine.create(self._AddRadioBeacon)
|
||||
--coroutine.resume(co, self, Name,Sound,FM,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,UHF,CTLD.RadioModulation.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)
|
||||
@@ -4320,6 +4472,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
|
||||
@@ -4327,8 +4481,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
|
||||
|
||||
@@ -4360,8 +4514,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))
|
||||
@@ -5063,7 +5219,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]
|
||||
|
||||
@@ -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".
|
||||
@@ -1909,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 = {}
|
||||
@@ -1977,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
|
||||
|
||||
@@ -2177,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).
|
||||
@@ -2188,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
|
||||
@@ -2196,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
|
||||
@@ -2210,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.
|
||||
@@ -2219,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()
|
||||
@@ -2233,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
|
||||
@@ -2297,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"
|
||||
@@ -2315,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
|
||||
@@ -2335,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
|
||||
@@ -2364,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
|
||||
@@ -2412,12 +2639,7 @@ 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})
|
||||
local StaticObject = STATIC:FindByName(staticname,false)
|
||||
if StaticObject then
|
||||
datatable:AddObject(StaticObject)
|
||||
@@ -2433,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"
|
||||
@@ -2458,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
|
||||
@@ -2490,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"
|
||||
@@ -2514,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
|
||||
|
||||
@@ -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,7 +510,11 @@ AIRBASE.MarianaIslands = {
|
||||
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
|
||||
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Turbio
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Chico_Airfield
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Chico
|
||||
-- * AIRBASE.SouthAtlantic.Franco_Bianco
|
||||
-- * AIRBASE.SouthAtlantic.Goose_Green
|
||||
-- * AIRBASE.SouthAtlantic.Hipico
|
||||
-- * AIRBASE.SouthAtlantic.CaletaTortel
|
||||
--
|
||||
--@field MarianaIslands
|
||||
AIRBASE.SouthAtlantic={
|
||||
@@ -534,6 +538,10 @@ AIRBASE.SouthAtlantic={
|
||||
["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".
|
||||
|
||||
@@ -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
|
||||
@@ -3954,4 +3980,4 @@ function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user