Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2022-10-18 22:33:07 +02:00
commit 6f82fd7eb6
16 changed files with 1653 additions and 294 deletions

View File

@ -594,7 +594,7 @@ do -- COORDINATE
--- Scan/find SCENERY objects within a certain radius around the coordinate using the world.searchObjects() DCS API function.
-- @param #COORDINATE self
-- @param #number radius (Optional) Scan radius in meters. Default 100 m.
-- @return table Set of scenery objects.
-- @return table Table of SCENERY objects.
function COORDINATE:ScanScenery(radius)
local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true)
@ -1519,7 +1519,7 @@ do -- COORDINATE
-- @param #number Coalition (Optional) Coalition of the airbase.
-- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate.
-- @return #number Distance to the closest airbase in meters.
function COORDINATE:GetClosestAirbase2(Category, Coalition)
function COORDINATE:GetClosestAirbase(Category, Coalition)
-- Get all airbases of the map.
local airbases=AIRBASE.GetAllAirbases(Coalition)
@ -1553,34 +1553,15 @@ do -- COORDINATE
return closest,distmin
end
--- Gets the nearest airbase with respect to the current coordinates.
--- [kept for downwards compatibility only] Gets the nearest airbase with respect to the current coordinates.
-- @param #COORDINATE self
-- @param #number Category (Optional) Category of the airbase. Enumerator of @{Wrapper.Airbase#AIRBASE.Category}.
-- @param #number Coalition (Optional) Coalition of the airbase.
-- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate.
-- @return #number Distance to the closest airbase in meters.
function COORDINATE:GetClosestAirbase(Category, Coalition)
local a=self:GetVec3()
local distmin=math.huge
local airbase=nil
for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do
local b=DCSairbase:getPoint()
local c=UTILS.VecSubstract(a,b)
local dist=UTILS.VecNorm(c)
--env.info(string.format("Airbase %s dist=%d category=%d", DCSairbase:getName(), dist, DCSairbase:getCategory()))
if dist<distmin and (Category==nil or Category==DCSairbase:getDesc().category) then
distmin=dist
airbase=DCSairbase
end
end
return AIRBASE:Find(airbase)
function COORDINATE:GetClosestAirbase2(Category, Coalition)
local closest, distmin = self:GetClosestAirbase(Category, Coalition)
return closest, distmin
end
--- Gets the nearest parking spot.

View File

@ -26,6 +26,7 @@
-- * @{#SET_AIRBASE}: Defines a collection of @{Wrapper.Airbase}s filtered by filter criteria.
-- * @{#SET_CARGO}: Defines a collection of @{Cargo.Cargo}s filtered by filter criteria.
-- * @{#SET_ZONE}: Defines a collection of @{Core.Zone}s filtered by filter criteria.
-- * @{#SET_SCENERY}: Defines a collection of @{Warpper.Scenery}s added via a filtered @{#SET_ZONE}.
--
-- These classes are derived from @{#SET_BASE}, which contains the main methods to manage the collections.
--
@ -37,7 +38,7 @@
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions: **funkyfranky**
-- ### Contributions: **funkyfranky**, **applevangelist**
--
-- ===
--
@ -277,6 +278,8 @@ do -- SET_BASE
-- @return #SET_BASE self
function SET_BASE:AddSet(SetToAdd)
if not SetToAdd then return self end
for _,ObjectB in pairs(SetToAdd.Set) do
self:AddObject(ObjectB)
end
@ -2637,7 +2640,9 @@ do -- SET_UNIT
-- @return Core.Point#COORDINATE The center coordinate of all the units in the set, including heading in degrees and speed in mps in case of moving units.
function SET_UNIT:GetCoordinate()
local Coordinate = self:GetFirst():GetCoordinate()
local Coordinate = self:GetRandom():GetCoordinate()
--self:F({Coordinate:GetVec3()})
local x1 = Coordinate.x
local x2 = Coordinate.x
@ -6728,3 +6733,245 @@ do -- SET_OPSGROUP
end
end
do -- SET_SCENERY
---
-- @type SET_SCENERY
-- @extends Core.Set#SET_BASE
--- Mission designers can use the SET_SCENERY class to build sets of scenery belonging to certain:
--
-- * Zone Sets
--
-- ## SET_SCENERY constructor
--
-- Create a new SET_SCENERY object with the @{#SET_SCENERY.New} method:
--
-- * @{#SET_SCENERY.New}: Creates a new SET_SCENERY object.
--
-- ## Add or Remove SCENERY(s) from SET_SCENERY
--
-- SCENERYs can be added and removed using the @{Core.Set#SET_SCENERY.AddSceneryByName} and @{Core.Set#SET_SCENERY.RemoveSceneryByName} respectively.
-- These methods take a single SCENERY name or an array of SCENERY names to be added or removed from SET_SCENERY.
--
-- ## SET_SCENERY filter criteria
--
-- N/A at the moment
--
-- ## SET_SCENERY iterators
--
-- Once the filters have been defined and the SET_SCENERY has been built, you can iterate the SET_SCENERY with the available iterator methods.
-- The iterator methods will walk the SET_SCENERY set, and call for each element within the set a function that you provide.
-- The following iterator methods are currently available within the SET_SCENERY:
--
-- * @{#SET_SCENERY.ForEachScenery}: Calls a function for each alive object it finds within the SET_SCENERY.
--
-- ## SET_SCENERY atomic methods
--
-- N/A at the moment
--
-- ===
-- @field #SET_SCENERY SET_SCENERY
SET_SCENERY = {
ClassName = "SET_SCENERY",
Scenerys = {},
Filter = {
SceneryPrefixes = nil,
Zones = nil,
},
}
--- Creates a new SET_SCENERY object. Scenery is **not** auto-registered in the Moose database, there are too many objects on each map. Hence we need to find them first. For this we are using a SET_ZONE.
-- @param #SET_SCENERY self
-- @param #SET_ZONE ZoneSet SET_ZONE of ZONE objects as created by right-clicks on the map in the mission editor, choosing "assign as...". Rename the zones for grouping purposes, e.g. all sections of a bridge as "Bridge-1" to "Bridge-3".
-- @return #SET_SCENERY
-- @usage
-- -- Define a new SET_SCENERY Object. This Object will contain a reference to all added Scenery Objects.
-- ZoneSet = SET_ZONE:New():FilterPrefixes("Bridge"):FilterOnce()
-- mysceneryset = SET_SCENERY:New(ZoneSet)
function SET_SCENERY:New(ZoneSet)
local zoneset = {}
-- Inherits from BASE
local self = BASE:Inherit( self, SET_BASE:New( zoneset ) ) -- Core.Set#SET_SCENERY
local zonenames = {}
for _,_zone in pairs(ZoneSet.Set) do
table.insert(zonenames,_zone:GetName())
end
self:AddSceneryByName(zonenames)
return self
end
--- Add SCENERY(s) to SET_SCENERY.
-- @param #SET_SCENERY self
-- @param #string AddScenery A single SCENERY.
-- @return #SET_SCENERY self
function SET_SCENERY:AddScenery( AddScenery )
self:F2( AddScenery:GetName() )
self:Add( AddScenery:GetName(), AddScenery )
return self
end
--- Add SCENERY(s) to SET_SCENERY.
-- @param #SET_SCENERY self
-- @param #string AddSceneryNames A single name or an array of SCENERY zone names.
-- @return #SET_SCENERY self
function SET_SCENERY:AddSceneryByName( AddSceneryNames )
local AddSceneryNamesArray = ( type( AddSceneryNames ) == "table" ) and AddSceneryNames or { AddSceneryNames }
self:T( AddSceneryNamesArray )
for AddSceneryID, AddSceneryName in pairs( AddSceneryNamesArray ) do
self:Add( AddSceneryName, SCENERY:FindByZoneName( AddSceneryName ) )
end
return self
end
--- Remove SCENERY(s) from SET_SCENERY.
-- @param Core.Set#SET_SCENERY self
-- @param Wrapper.Scenery#SCENERY RemoveSceneryNames A single name or an array of SCENERY zone names.
-- @return self
function SET_SCENERY:RemoveSceneryByName( RemoveSceneryNames )
local RemoveSceneryNamesArray = ( type( RemoveSceneryNames ) == "table" ) and RemoveSceneryNames or { RemoveSceneryNames }
for RemoveSceneryID, RemoveSceneryName in pairs( RemoveSceneryNamesArray ) do
self:Remove( RemoveSceneryName )
end
return self
end
--- Finds a Scenery in the SET, based on the Scenery Name.
-- @param #SET_SCENERY self
-- @param #string SceneryName
-- @return Wrapper.Scenery#SCENERY The found Scenery.
function SET_SCENERY:FindScenery( SceneryName )
local SceneryFound = self.Set[SceneryName]
return SceneryFound
end
--- Builds a set of scenery objects in zones.
-- @param #SET_SCENERY self
-- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE
-- @return #SET_SCENERY self
function SET_SCENERY: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
--- Builds a set of SCENERYs that contain the given string in their name.
-- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all scenery that **contain** the string.
-- @param #SET_SCENERY self
-- @param #string Prefixes The string pattern(s) that need to be contained in the scenery name. Can also be passed as a `#table` of strings.
-- @return #SET_SCENERY self
function SET_SCENERY:FilterPrefixes( Prefixes )
if not self.Filter.SceneryPrefixes then
self.Filter.SceneryPrefixes = {}
end
if type( Prefixes ) ~= "table" then
Prefixes = { Prefixes }
end
for PrefixID, Prefix in pairs( Prefixes ) do
self.Filter.SceneryPrefixes[Prefix] = Prefix
end
return self
end
--- Iterate the SET_SCENERY and count how many SCENERYSs are alive.
-- @param #SET_SCENERY self
-- @return #number The number of SCENERYSs alive.
function SET_SCENERY:CountAlive()
local Set = self:GetSet()
local CountU = 0
for UnitID, UnitData in pairs(Set) do
if UnitData and UnitData:IsAlive() then
CountU = CountU + 1
end
end
return CountU
end
--- Iterate the SET_SCENERY and call an iterator function for each **alive** SCENERY, providing the SCENERY and optional parameters.
-- @param #SET_SCENERY self
-- @param #function IteratorFunction The function that will be called when there is an alive SCENERY in the SET_SCENERY. The function needs to accept a SCENERY parameter.
-- @return #SET_SCENERY self
function SET_SCENERY:ForEachScenery( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self:GetSet() )
return self
end
--- Get the center coordinate of the SET_SCENERY.
-- @param #SET_SCENERY self
-- @return Core.Point#COORDINATE The center coordinate of all the objects in the set.
function SET_SCENERY:GetCoordinate()
local Coordinate = self:GetRandom():GetCoordinate()
local x1 = Coordinate.x
local x2 = Coordinate.x
local y1 = Coordinate.y
local y2 = Coordinate.y
local z1 = Coordinate.z
local z2 = Coordinate.z
for SceneryName, SceneryData in pairs( self:GetSet() ) do
local Scenery = SceneryData -- Wrapper.Scenery#SCENERY
local Coordinate = Scenery:GetCoordinate()
x1 = ( Coordinate.x < x1 ) and Coordinate.x or x1
x2 = ( Coordinate.x > x2 ) and Coordinate.x or x2
y1 = ( Coordinate.y < y1 ) and Coordinate.y or y1
y2 = ( Coordinate.y > y2 ) and Coordinate.y or y2
z1 = ( Coordinate.y < z1 ) and Coordinate.z or z1
z2 = ( Coordinate.y > z2 ) and Coordinate.z or z2
end
Coordinate.x = ( x2 - x1 ) / 2 + x1
Coordinate.y = ( y2 - y1 ) / 2 + y1
Coordinate.z = ( z2 - z1 ) / 2 + z1
self:F( { Coordinate = Coordinate } )
return Coordinate
end
---
-- @param #SET_SCENERY self
-- @param Wrapper.Scenery#SCENERY MScenery
-- @return #SET_SCENERY self
function SET_SCENERY:IsIncludeObject( MScenery )
self:F2( MScenery )
return true
end
end

View File

@ -631,8 +631,9 @@ ZONE_RADIUS = {
-- @param #string ZoneName Name of the zone.
-- @param DCS#Vec2 Vec2 The location of the zone.
-- @param DCS#Distance Radius The radius of the zone.
-- @param DCS#Boolean DoNotRegisterZone Determins if the Zone should not be registered in the _Database Table. Default=false
-- @return #ZONE_RADIUS self
function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone )
-- Inherit ZONE_BASE.
local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS
@ -641,6 +642,10 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius )
self.Radius = Radius
self.Vec2 = Vec2
if not DoNotRegisterZone then
_EVENTDISPATCHER:CreateEventNewZone(self)
end
--self.Coordinate=COORDINATE:NewFromVec2(Vec2)
return self
@ -1538,7 +1543,7 @@ function ZONE:New( ZoneName )
end
-- Create a new ZONE_RADIUS.
local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius))
local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius, true))
self:F(ZoneName)
-- Color of zone.
@ -1605,7 +1610,7 @@ function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset)
self.relative_to_unit = Offset.relative_to_unit or false
end
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) )
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius, true ) )
self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } )
@ -1721,7 +1726,7 @@ ZONE_GROUP = {
-- @param DCS#Distance Radius The radius of the zone.
-- @return #ZONE_GROUP self
function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius )
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius ) )
local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneGROUP:GetVec2(), Radius, true ) )
self:F( { ZoneName, ZoneGROUP:GetVec2(), Radius } )
self._.ZoneGROUP = ZoneGROUP
@ -2321,7 +2326,7 @@ function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, C
for Segment = 0, Segments do
local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments )
local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments )
ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true)
ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius, true ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, false)
end
end
j = i
@ -2948,7 +2953,7 @@ do -- ZONE_AIRBASE
local Airbase = AIRBASE:FindByName( AirbaseName )
local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) )
local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius, true ) )
self._.ZoneAirbase = Airbase
self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2()

View File

@ -69,8 +69,8 @@ do -- ZONE_CAPTURE_COALITION
-- In order to use ZONE_CAPTURE_COALITION, you need to:
--
-- * Create a @{Zone} object from one of the ZONE_ classes.
-- Note that ZONE_POLYGON_ classes are not yet functional.
-- The only functional ZONE_ classses are those derived from a ZONE_RADIUS.
-- The functional ZONE_ classses are those derived from a ZONE_RADIUS.
-- In order to use a ZONE_POLYGON, hand over the **GROUP name** of a late activated group forming a polygon with it's waypoints.
-- * Set the state of the zone. Most of the time, Guarded would be the initial state.
-- * Start the zone capturing **monitoring process**.
-- This will check the presence of friendly and/or enemy units within the zone and will transition the state of the zone when the tactical situation changed.

View File

@ -422,6 +422,44 @@ function AIRWING:SetPayloadAmount(Payload, Navailable)
return self
end
--- Increase or decrease the amount of available payloads. Unlimited playloads first need to be set to a limited number with the `SetPayloadAmount` function.
-- @param #AIRWING self
-- @param #AIRWING.Payload Payload The payload table created by the `:NewPayload` function.
-- @param #number N Number of payloads to be added. Use negative number to decrease amount. Default 1.
-- @return #AIRWING self
function AIRWING:IncreasePayloadAmount(Payload, N)
N=N or 1
if Payload and Payload.navail>=0 then
-- Increase/decrease amount.
Payload.navail=Payload.navail+N
-- Ensure playload does not drop below 0.
Payload.navail=math.max(Payload.navail, 0)
end
return self
end
--- Get amount of payloads available for a given playload.
-- @param #AIRWING self
-- @param #AIRWING.Payload Payload The payload table created by the `:NewPayload` function.
-- @return #number Number of payloads available. Unlimited payloads will return -1.
function AIRWING:GetPayloadAmount(Payload)
return Payload.navail
end
--- Get capabilities of a given playload.
-- @param #AIRWING self
-- @param #AIRWING.Payload Payload The payload data table.
-- @return #table Capabilities.
function AIRWING:GetPayloadCapabilities(Payload)
return Payload.capabilities
end
--- Add a mission capability to an existing payload.
-- @param #AIRWING self
-- @param #AIRWING.Payload Payload The payload table to which the capability should be added.

View File

@ -497,7 +497,7 @@ do
-- @field #AWACS
AWACS = {
ClassName = "AWACS", -- #string
version = "0.2.44", -- #string
version = "0.2.45", -- #string
lid = "", -- #string
coalition = coalition.side.BLUE, -- #number
coalitiontxt = "blue", -- #string
@ -3515,7 +3515,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr)
text = string.format("%s. %s. %s. %s",managedgroup.CallSign,self.callsigntxt,alpha,alphacheckbulls)
self:__CheckedIn(1,managedgroup.GID)
local AW = FlightGroup:GetAirWing()
local AW = FlightGroup.legion
if AW.HasOwnStation then
self:__AssignAnchor(5,managedgroup.GID,AW.HasOwnStation,AW.StationName)
else

View File

@ -30,7 +30,7 @@
-- @module Ops.CSAR
-- @image OPS_CSAR.jpg
-- Date: June 2022
-- Date: October 2022
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@ -270,7 +270,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.11"
CSAR.version="1.0.15"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -613,6 +613,19 @@ function CSAR:_DoubleEjection(_unitname)
return false
end
--- (User) Add a PLAYERTASK - FSM events will check success
-- @param #CSAR self
-- @param Ops.PlayerTask#PLAYERTASK PlayerTask
-- @return #CSAR self
function CSAR:AddPlayerTask(PlayerTask)
self:T(self.lid .. " AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue = FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
--- (Internal) Spawn a downed pilot
-- @param #CSAR self
-- @param #number country Country for template.
@ -1197,6 +1210,38 @@ function CSAR:_RemoveNameFromDownedPilots(name,force)
return found
end
--- [User] Set callsign options for TTS output. See @{Wrapper.Group#GROUP.GetCustomCallSign}() on how to set customized callsigns.
-- @param #CSAR self
-- @param #boolean ShortCallsign If true, only call out the major flight number
-- @param #boolean Keepnumber If true, keep the **customized callsign** in the #GROUP name for players as-is, no amendments or numbers.
-- @param #table CallsignTranslations (optional) Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
-- callsigns from playername or group name.
-- @return #CSAR self
function CSAR:SetCallSignOptions(ShortCallsign,Keepnumber,CallsignTranslations)
if not ShortCallsign or ShortCallsign == false then
self.ShortCallsign = false
else
self.ShortCallsign = true
end
self.Keepnumber = Keepnumber or false
self.CallsignTranslations = CallsignTranslations
return self
end
--- (Internal) Check if a name is in downed pilot table and remove it.
-- @param #CSAR self
-- @param #string UnitName
-- @return #string CallSign
function CSAR:_GetCustomCallSign(UnitName)
local callsign = Unitname
local unit = UNIT:FindByName(UnitName)
if unit and unit:IsAlive() then
local group = unit:GetGroup()
callsign = group:GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
end
return callsign
end
--- (Internal) Check state of wounded group.
-- @param #CSAR self
-- @param #string heliname heliname
@ -1253,9 +1298,9 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
local dist = UTILS.MetersToNM(self.autosmokedistance)
disttext = string.format("%.0fnm",dist)
end
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", self:_GetCustomCallSign(_heliName), _pilotName, disttext), self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true)
end
--mark as shown for THIS heli and THIS group
self.heliVisibleMessage[_lookupKeyHeli] = true
@ -1319,7 +1364,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam
_maxUnits = self.max_units
end
if _unitsInHelicopter + 1 > _maxUnits then
self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We\'re already crammed with %d guys! Sorry!", _pilotName, self:_GetCustomCallSign(_heliName), _unitsInHelicopter, _unitsInHelicopter), self.messageTime,false,false,true)
return self
end
@ -1337,13 +1382,29 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam
_woundedGroup:Destroy(false)
self:_RemoveNameFromDownedPilots(_woundedGroupName,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I\'m in! Get to the MASH ASAP! ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,true,true)
self:_UpdateUnitCargoMass(_heliName)
self:__Boarded(5,_heliName,_woundedGroupName,grouptable.desc)
return self
end
--- (Internal) Function to calculate and set Unit internal cargo mass
-- @param #CSAR self
-- @param #string _heliName Unit name
-- @return #CSAR self
function CSAR:_UpdateUnitCargoMass(_heliName)
self:T(self.lid .. " _UpdateUnitCargoMass")
local calculatedMass = self:_PilotsOnboard(_heliName)*80
local Unit = UNIT:FindByName(_heliName)
if Unit then
Unit:SetUnitInternalCargo(calculatedMass)
end
return self
end
--- (Internal) Move group to destination.
-- @param #CSAR self
-- @param Wrapper.Group#GROUP _leader
@ -1358,7 +1419,6 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination)
return self
end
--- (internal) Function to check if the heli door(s) are open. Thanks to Shadowze.
-- @param #CSAR self
-- @param #string unit_name Name of unit.
@ -1392,9 +1452,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
if self.heliCloseMessage[_lookupKeyHeli] == nil then
if self.autosmoke == true then
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land or hover at the smoke.", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You\'re close now! Land in a safe place, I will go there ", self:_GetCustomCallSign(_heliName), _pilotName), self.messageTime,false,true)
end
self.heliCloseMessage[_lookupKeyHeli] = true
end
@ -1447,7 +1507,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
end
if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then
-- TODO - make variable
-- DONE - make variable
if _distance < self.rescuehoverdistance then
--check height!
@ -1455,7 +1515,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
if leaderheight < 0 then leaderheight = 0 end
local _height = _heliUnit:GetHeight() - leaderheight
-- TODO - make variable
-- DONE - make variable
if _height <= self.rescuehoverheight then
local _time = self.hoverStatus[_lookupKeyHeli]
@ -1561,9 +1621,12 @@ function CSAR:_RescuePilots(_heliUnit)
self.inTransitGroups[_heliName] = nil
local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved)
local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", self:_GetCustomCallSign(_heliName), PilotsSaved)
self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime)
self:_UpdateUnitCargoMass(_heliName)
-- trigger event
self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved)
return self
@ -1597,7 +1660,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
local _clear = _clear or nil
local _time = _time or self.messageTime
if _override or not self.suppressmessages then
local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group)
local m = MESSAGE:New(_text,_time,"CSAR",_clear):ToGroup(group)
end
-- integrate SRS
if _speak and self.useSRS then
@ -1746,7 +1809,7 @@ function CSAR:_SignalFlare(_unitName)
else
_distance = string.format("%.1fkm",_closest.distance)
end
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
local _coord = _closest.pilot:GetCoordinate()
@ -1800,7 +1863,7 @@ function CSAR:_Reqsmoke( _unitName )
else
_distance = string.format("%.1fkm",_closest.distance/1000)
end
local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
local _coord = _closest.pilot:GetCoordinate()
local color = self.smokecolor
@ -1851,7 +1914,7 @@ function CSAR:_GetClosestMASH(_heli)
if self.allowFARPRescue then
local position = _heli:GetCoordinate()
local afb,distance = position:GetClosestAirbase2(nil,self.coalition)
local afb,distance = position:GetClosestAirbase(nil,self.coalition)
_shortestDistance = distance
end
@ -2004,12 +2067,16 @@ function CSAR:_GetClockDirection(_heli, _group)
local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions )
local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 )
self:T(self.lid .. " _GetClockDirection"..tostring(Angle).." "..tostring(_heading))
local hours = 0
local clock = 12
if _heading then
local Aspect = Angle - _heading
if Aspect == 0 then Aspect = 360 end
clock = math.abs(UTILS.Round((Aspect / 30),0))
if clock == 0 then clock = 12 end
if _heading and Angle then
clock = 12
--if angle == 0 then angle = 360 end
clock = _heading-Angle
hours = (clock/30)*-1
clock = 12+hours
clock = UTILS.Round(clock,0)
if clock > 12 then clock = clock-12 end
end
return clock
end
@ -2282,6 +2349,29 @@ end
function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname)
self:T({From, Event, To, Heliname, Woundedgroupname})
self:_ScheduledSARFlight(Heliname,Woundedgroupname)
local Unit = UNIT:FindByName(Heliname)
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
local dropcoord = Unit:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function (Task)
local task = Task -- Ops.PlayerTask#PLAYERTASK
local subtype = task:GetSubType()
-- right subtype?
if Event == subtype and not task:IsDone() then
local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case ....
if (targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2))
or (string.find(task.CSARPilotName,Woundedgroupname)) then
if task.Clients:HasUniqueID(playername) then
-- success
task:__Success(-1)
end
end
end
end
)
end
return self
end
@ -2311,6 +2401,23 @@ function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved)
self:T({From, Event, To, HeliName, HeliUnit})
self.rescues = self.rescues + 1
self.rescuedpilots = self.rescuedpilots + PilotsSaved
local Unit = HeliUnit or UNIT:FindByName(HeliName)
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
self.PlayerTaskQueue:ForEach(
function (Task)
local task = Task -- Ops.PlayerTask#PLAYERTASK
local subtype = task:GetSubType()
-- right subtype?
if Event == subtype and not task:IsDone() then
if task.Clients:HasUniqueID(playername) then
-- success
task:__Success(-1)
end
end
end
)
end
return self
end

View File

@ -22,8 +22,7 @@
-- @module Ops.CTLD
-- @image OPS_CTLD.jpg
-- Date: Feb 2022
-- Last Update Sep 2022
-- Last Update October 2022
do
@ -288,8 +287,8 @@ CTLD_ENGINEERING = {
end
do
do
------------------------------------------------------
--- **CTLD_CARGO** class, extends Core.Base#BASE
-- @type CTLD_CARGO
@ -308,9 +307,8 @@ do
-- @field #string Subcategory Sub-category name.
-- @extends Core.Base#BASE
---
-- @field CTLD_CARGO
-- @field #CTLD_CARGO CTLD_CARGO
CTLD_CARGO = {
ClassName = "CTLD_CARGO",
ID = 0,
@ -343,7 +341,7 @@ CTLD_CARGO = {
CRATE = "Crate", -- #string crate
REPAIR = "Repair", -- #string repair
ENGINEERS = "Engineers", -- #string engineers
STATIC = "Static", -- #string engineers
STATIC = "Static", -- #string statics
}
--- Function to create new CTLD_CARGO object.
@ -574,6 +572,10 @@ CTLD_CARGO = {
end
do
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO CTLD
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------
--- **CTLD** class, extends Core.Base#BASE, Core.Fsm#FSM
-- @type CTLD
@ -824,6 +826,8 @@ do
--
-- To award player with points, using the SCORING Class (SCORING: my_Scoring, CTLD: CTLD_Cargotransport)
--
-- my_scoring = SCORING:New("Combat Transport")
--
-- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable)
-- local points = 10
-- if Unit then
@ -901,7 +905,7 @@ do
--
-- my_ctld.useprefix = true -- this is true by default and MUST BE ON.
--
-- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method)
-- ### 5.2 Integrate Hercules ground crew (F8 Menu) loadable objects (alternative method, use either the above OR this method, NOT both!)
--
-- Integrate to your CTLD instance like so, where `my_ctld` is a previously created CTLD instance:
--
@ -928,6 +932,8 @@ do
-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does
-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD (see 5.1).
--
-- DO NOT use the "splash damage" script together with this method! Your cargo will explode on the ground!
--
-- There are two ways of airdropping:
--
-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu)
@ -1068,7 +1074,7 @@ CTLD.UnitTypes = {
--- CTLD class version.
-- @field #string version
CTLD.version="1.0.11"
CTLD.version="1.0.16"
--- Instantiate a new CTLD.
-- @param #CTLD self
@ -1476,6 +1482,19 @@ function CTLD:SetTroopDropZoneRadius(Radius)
return self
end
--- (User) Add a PLAYERTASK - FSM events will check success
-- @param #CTLD self
-- @param Ops.PlayerTask#PLAYERTASK PlayerTask
-- @return #CTLD self
function CTLD:AddPlayerTask(PlayerTask)
self:T(self.lid .. " AddPlayerTask")
if not self.PlayerTaskQueue then
self.PlayerTaskQueue = FIFO:New()
end
self.PlayerTaskQueue:Push(PlayerTask,PlayerTask.PlayerTaskNr)
return self
end
--- (Internal) Event handler function
-- @param #CTLD self
-- @param Core.Event#EVENTDATA EventData
@ -3387,6 +3406,12 @@ end
function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Shipwidth)
self:T(self.lid .. " AddCTLDZone")
local zone = ZONE:FindByName(Name)
if not zone then
self:E(self.lid.."**** Zone does not exist: "..Name)
return self
end
local ctldzone = {} -- #CTLD.CargoZone
ctldzone.active = Active or false
ctldzone.color = Color or SMOKECOLOR.Red
@ -3632,9 +3657,10 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
local zoneret = nil
local zonewret = nil
local zonenameret = nil
local unitcoord = Unit:GetCoordinate()
local unitVec2 = unitcoord:GetVec2()
for _,_cargozone in pairs(zonetable) do
local czone = _cargozone -- #CTLD.CargoZone
local unitcoord = Unit:GetCoordinate()
local zonename = czone.name
local active = czone.active
local color = czone.color
@ -3651,17 +3677,17 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
zone = ZONE:FindByName(zonename)
self:T("Checking Zone: "..zonename)
zonecoord = zone:GetCoordinate()
zoneradius = zone:GetRadius()
zoneradius = 1500
zonewidth = zoneradius
elseif AIRBASE:FindByName(zonename) then
zone = AIRBASE:FindByName(zonename):GetZone()
self:T("Checking Zone: "..zonename)
zonecoord = zone:GetCoordinate()
zoneradius = zone:GetRadius()
zoneradius = 2500
zonewidth = zoneradius
end
local distance = self:_GetDistance(zonecoord,unitcoord)
if distance <= zoneradius and active then
if zone:IsVec2InZone(unitVec2) and active then
outcome = true
end
if maxdist > distance then
@ -4244,7 +4270,7 @@ end
end
-------------------------------------------------------------------
-- FSM functions
-- TODO FSM functions
-------------------------------------------------------------------
--- (Internal) FSM Function onafterStart.
@ -4417,6 +4443,27 @@ end
-- @return #CTLD self
function CTLD:onbeforeTroopsDeployed(From, Event, To, Group, Unit, Troops)
self:T({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
local dropcoord = Troops:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function (Task)
local task = Task -- Ops.PlayerTask#PLAYERTASK
local subtype = task:GetSubType()
-- right subtype?
if Event == subtype and not task:IsDone() then
local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case ....
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then
if task.Clients:HasUniqueID(playername) then
-- success
task:__Success(-1)
end
end
end
end
)
end
return self
end
@ -4444,7 +4491,28 @@ end
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build.
-- @return #CTLD self
function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle)
self:T({From, Event, To})
self:I({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
local dropvec2 = dropcoord:GetVec2()
self.PlayerTaskQueue:ForEach(
function (Task)
local task = Task -- Ops.PlayerTask#PLAYERTASK
local subtype = task:GetSubType()
-- right subtype?
if Event == subtype and not task:IsDone() then
local targetzone = task.Target:GetObject() -- Core.Zone#ZONE should be a zone in this case ....
if targetzone and targetzone.ClassName and string.match(targetzone.ClassName,"ZONE") and targetzone:IsVec2InZone(dropvec2) then
if task.Clients:HasUniqueID(playername) then
-- success
task:__Success(-1)
end
end
end
end
)
end
return self
end
@ -4802,7 +4870,9 @@ end -- end do
do
--- **Hercules Cargo AIR Drop Events** by Anubis Yinepu
-- Moose CTLD OO refactoring by Applevangelist
--
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO CTLD_HERCULES
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- This script will only work for the Herculus mod by Anubis, and only for **Air Dropping** cargo from the Hercules.
-- Use the standard Moose CTLD if you want to unload on the ground.
-- Payloads carried by pylons 11, 12 and 13 need to be declared in the Herculus_Loadout.lua file
@ -4932,13 +5002,21 @@ CTLD_HERCULES.Types = {
--
-- Expected template names are the ones in the rounded brackets.
--
-- HINTS
-- ### HINTS
--
-- The script works on the EVENTS.Shot trigger, which is used by the mod when you **drop cargo from the Hercules while flying**. Unloading on the ground does
-- not achieve anything here. If you just want to unload on the ground, use the normal Moose CTLD.
-- **Do not use** the **splash damage** script together with this, your cargo will just explode when reaching the ground!
--
-- ### Airdrops
--
-- There are two ways of airdropping:
-- 1) Very low and very slow (>5m and <10m AGL) - here you can drop stuff which has "Skid" at the end of the cargo name (loaded via F8 Ground Crew menu)
-- 2) Higher up and slow (>100m AGL) - here you can drop paratroopers and cargo which has "Air" at the end of the cargo name (loaded via F8 Ground Crew menu)
--
-- ### General
--
-- Use either this method to integrate the Hercules **or** the one from the "normal" CTLD. Never both!
function CTLD_HERCULES:New(Coalition, Alias, CtldObject)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #CTLD_HERCULES

View File

@ -210,7 +210,7 @@ FLIGHTGROUP.Players={}
--- FLIGHTGROUP class version.
-- @field #string version
FLIGHTGROUP.version="0.8.0"
FLIGHTGROUP.version="0.8.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -372,11 +372,34 @@ end
--- Get airwing the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return Ops.AirWing#AIRWING The AIRWING object.
function FLIGHTGROUP:GetAirWing()
-- @return Ops.AirWing#AIRWING The AIRWING object (if any).
function FLIGHTGROUP:GetAirwing()
return self.legion
end
--- Get name of airwing the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return #string Name of the airwing or "None" if the flightgroup does not belong to any airwing.
function FLIGHTGROUP:GetAirwingName()
local name=self.legion and self.legion.alias or "None"
return name
end
--- Get squadron the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return Ops.Squadron#SQUADRON The SQUADRON of this flightgroup or #nil if the flightgroup does not belong to any squadron.
function FLIGHTGROUP:GetSquadron()
return self.cohort
end
--- Get squadron name the flight group belongs to.
-- @param #FLIGHTGROUP self
-- @return #string The squadron name or "None" if the flightgroup does not belon to any squadron.
function FLIGHTGROUP:GetSquadronName()
local name=self.cohort and self.cohort:GetName() or "None"
return name
end
--- Set if aircraft is VTOL capable. Unfortunately, there is no DCS way to determine this via scripting.
-- @param #FLIGHTGROUP self
-- @return #FLIGHTGROUP self
@ -836,6 +859,7 @@ end
function FLIGHTGROUP:GetKills()
return self.Nkills
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1935,7 +1959,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To)
end
--TODO: Check that current base is airwing base.
local airwing=self:GetAirWing() --airwing:GetAirbaseName()==self.currbase:GetName()
local airwing=self:GetAirwing() --airwing:GetAirbaseName()==self.currbase:GetName()
-- Check what to do.
if airwing and not (self:IsPickingup() or self:IsTransporting()) then

View File

@ -1854,7 +1854,7 @@ end
--- Count total number of assets of the legion.
-- @param #LEGION self
-- @param #boolean InStock If true, only assets that are in the warehouse stock/inventory are counted.
-- @param #boolean InStock If `true`, only assets that are in the warehouse stock/inventory are counted.
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`.
-- @return #number Amount of asset groups in stock.

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
--- **Ops** - PlayerTask (mission) for Players.
---- **Ops** - PlayerTask (mission) for Players.
--
-- ## Main Features:
--
-- * Simplifies defining and executing Player tasks
-- * FSM events when a mission is added, done, successful or failed, replanned
-- * Ready to use SRS and localization
-- * Mission locations can be smoked, flared and marked on the map
-- * Mission locations can be smoked, flared, illuminated and marked on the map
--
-- ===
--
@ -21,7 +21,7 @@
-- ===
-- @module Ops.PlayerTask
-- @image OPS_PlayerTask.jpg
-- @date Last Update September 2022
-- @date Last Update October 2022
do
@ -50,6 +50,13 @@ do
-- @field Ops.PlayerTask#PLAYERTASKCONTROLLER TaskController
-- @field #number timestamp
-- @field #number lastsmoketime
-- @field #number coalition
-- @field #string Freetext
-- @field #string FreetextTTS
-- @field #string TaskSubType
-- @field #table NextTaskSuccess
-- @field #table NextTaskFailure
-- @field #string FinalState
-- @extends Core.Fsm#FSM
@ -78,11 +85,17 @@ PLAYERTASK = {
TaskController = nil,
timestamp = 0,
lastsmoketime = 0,
Freetext = nil,
FreetextTTS = nil,
TaskSubType = nil,
NextTaskSuccess = {},
NextTaskFailure = {},
FinalState = "none",
}
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASK.version="0.1.3"
PLAYERTASK.version="0.1.9"
--- Generic task condition.
-- @type PLAYERTASK.Condition
@ -95,6 +108,7 @@ PLAYERTASK.version="0.1.3"
-- @param Ops.Target#TARGET Target Target for this task
-- @param #boolean Repeat Repeat this task if true (default = false)
-- @param #number Times Repeat on failure this many times if Repeat is true (default = 1)
-- @param #string TTSType TTS friendly task type name
-- @return #PLAYERTASK self
function PLAYERTASK:New(Type, Target, Repeat, Times, TTSType)
@ -252,6 +266,124 @@ function PLAYERTASK:_SetController(Controller)
return self
end
--- [User] Set a coalition side for this task
-- @param #PLAYERTASK self
-- @param #number Coalition Coaltion side to add, e.g. coalition.side.BLUE
-- @return #PLAYERTASK self
function PLAYERTASK:SetCoalition(Coalition)
self:T(self.lid.."SetCoalition")
self.coalition = Coalition or coalition.side.BLUE
return self
end
--- [User] Get the coalition side for this task
-- @param #PLAYERTASK self
-- @return #number Coalition Coaltion side, e.g. coalition.side.BLUE, or nil if not set
function PLAYERTASK:GetCoalition()
self:T(self.lid.."GetCoalition")
return self.coalition
end
--- [USER] Add a free text description to this task.
-- @param #PLAYERTASK self
-- @param #string Text
-- @return #PLAYERTASK self
function PLAYERTASK:AddFreetext(Text)
self:T(self.lid.."AddFreetext")
self.Freetext = Text
return self
end
--- [USER] Query if a task has free text description.
-- @param #PLAYERTASK self
-- @return #PLAYERTASK self
function PLAYERTASK:HasFreetext()
self:T(self.lid.."HasFreetext")
return self.Freetext ~= nil and true or false
end
--- [USER] Query if a task has free text TTS description.
-- @param #PLAYERTASK self
-- @return #PLAYERTASK self
function PLAYERTASK:HasFreetextTTS()
self:T(self.lid.."HasFreetextTTS")
return self.FreetextTTS ~= nil and true or false
end
--- [USER] Set a task sub type description to this task.
-- @param #PLAYERTASK self
-- @param #string Type
-- @return #PLAYERTASK self
function PLAYERTASK:SetSubType(Type)
self:T(self.lid.."AddSubType")
self.TaskSubType = Type
return self
end
--- [USER] Get task sub type description from this task.
-- @param #PLAYERTASK self
-- @return #string Type or nil
function PLAYERTASK:GetSubType()
self:T(self.lid.."GetSubType")
return self.TaskSubType
end
--- [USER] Get the free text description from this task.
-- @param #PLAYERTASK self
-- @return #string Text
function PLAYERTASK:GetFreetext()
self:T(self.lid.."GetFreetext")
return self.Freetext or self.FreetextTTS or "No Details"
end
--- [USER] Add a free text description for TTS to this task.
-- @param #PLAYERTASK self
-- @param #string TextTTS
-- @return #PLAYERTASK self
function PLAYERTASK:AddFreetextTTS(TextTTS)
self:T(self.lid.."AddFreetextTTS")
self.FreetextTTS = TextTTS
return self
end
--- [USER] Get the free text TTS description from this task.
-- @param #PLAYERTASK self
-- @return #string Text
function PLAYERTASK:GetFreetextTTS()
self:T(self.lid.."GetFreetextTTS")
return self.FreetextTTS or self.Freetext or "No Details"
end
--- [USER] Add a short free text description for the menu entry of this task.
-- @param #PLAYERTASK self
-- @param #string Text
-- @return #PLAYERTASK self
function PLAYERTASK:SetMenuName(Text)
self:T(self.lid.."SetMenuName")
self.Target.name = Text
return self
end
--- [USER] Add a task to be assigned to same clients when task was a success.
-- @param #PLAYERTASK self
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @return #PLAYERTASK self
function PLAYERTASK:AddNextTaskAfterSuccess(Task)
self:T(self.lid.."AddNextTaskAfterSuccess")
table.insert(self.NextTaskSuccess,Task)
return self
end
--- [USER] Add a task to be assigned to same clients when task was a failure.
-- @param #PLAYERTASK self
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @return #PLAYERTASK self
function PLAYERTASK:AddNextTaskAfterFailure(Task)
self:T(self.lid.."AddNextTaskAfterFailure")
table.insert(self.NextTaskFailure,Task)
return self
end
--- [User] Check if task is done
-- @param #PLAYERTASK self
-- @return #boolean done
@ -398,7 +530,7 @@ function PLAYERTASK:SmokeTarget(Color)
if not self.lastsmoketime then self.lastsmoketime = 0 end
local TDiff = timer.getAbsTime() - self.lastsmoketime
if self.Target and TDiff > 299 then
local coordinate = self.Target:GetCoordinate()
local coordinate = self.Target:GetAverageCoordinate()
if coordinate then
coordinate:Smoke(color)
self.lastsmoketime = timer.getAbsTime()
@ -415,7 +547,7 @@ function PLAYERTASK:FlareTarget(Color)
self:T(self.lid.."SmokeTarget")
local color = Color or FLARECOLOR.Red
if self.Target then
local coordinate = self.Target:GetCoordinate()
local coordinate = self.Target:GetAverageCoordinate()
if coordinate then
coordinate:Flare(color,0)
end
@ -423,6 +555,25 @@ function PLAYERTASK:FlareTarget(Color)
return self
end
--- [User] Illuminate Target Area
-- @param #PLAYERTASK self
-- @param #number Power Power of illumination bomb in Candela. Default 1000 cd.
-- @param #number Height Height above target used to release the bomb, default 150m.
-- @return #PLAYERTASK self
function PLAYERTASK:IlluminateTarget(Power,Height)
self:T(self.lid.."IlluminateTarget")
local Power = Power or 1000
local Height = Height or 150
if self.Target then
local coordinate = self.Target:GetAverageCoordinate()
if coordinate then
local bcoord = COORDINATE:NewFromVec2( coordinate:GetVec2(), Height )
bcoord:IlluminationBomb(Power)
end
end
return self
end
-- success / failure function addion courtesy @FunkyFranky.
--- [User] Add success condition.
@ -650,6 +801,7 @@ function PLAYERTASK:onafterCancel(From, Event, To)
self.TaskController:__TaskCancelled(-1,self)
end
self.timestamp = timer.getAbsTime()
self.FinalState = "Cancel"
self:__Done(-1)
return self
end
@ -669,6 +821,7 @@ function PLAYERTASK:onafterSuccess(From, Event, To)
self.TargetMarker:Remove()
end
self.timestamp = timer.getAbsTime()
self.FinalState = "Success"
self:__Done(-1)
return self
end
@ -693,9 +846,7 @@ function PLAYERTASK:onafterFailed(From, Event, To)
if self.TargetMarker then
self.TargetMarker:Remove()
end
if self.TaskController then
self.TaskController:__TaskFailed(-1,self)
end
self.FinalState = "Failed"
self:__Done(-1)
end
self.timestamp = timer.getAbsTime()
@ -715,6 +866,8 @@ do
-- DONE Flash directions
-- DONE less rebuilds menu, Task info menu available after join
-- DONE Limit menu entries
-- DONE Integrated basic CTLD tasks
-- DONE Integrate basic CSAR tasks
-------------------------------------------------------------------------------------------------------------------
--- PLAYERTASKCONTROLLER class.
@ -762,6 +915,9 @@ do
-- @field #table PlayerInfoMenu
-- @field #boolean noflaresmokemenu
-- @field #boolean TransmitOnlyWithPlayers
-- @field #boolean buddylasing
-- @field Ops.PlayerRecce#PLAYERRECCE PlayerRecce
-- @field #number Coalition
-- @extends Core.Fsm#FSM
---
@ -788,7 +944,7 @@ do
--
-- ## 2 Task Types
--
-- Targets can be of types GROUP, SET\_GROUP, UNIT, SET\_UNIT, STATIC, SET\_STATIC, AIRBASE, ZONE or COORDINATE. The system will auto-create tasks for players from these targets.
-- Targets can be of types GROUP, SET\_GROUP, UNIT, SET\_UNIT, STATIC, SET\_STATIC, SET\_SCENERY, AIRBASE, ZONE or COORDINATE. The system will auto-create tasks for players from these targets.
-- Tasks are created as @{Ops.PlayerTask#PLAYERTASK} objects, which leverage @{Ops.Target#TARGET} for the management of the actual target. The system creates these task types
-- from the target objects:
--
@ -808,6 +964,8 @@ do
-- * ZONE and COORDINATE - Targets will be scanned for GROUND or STATIC enemy units and tasks created from these
-- * Intercept - Any airborne targets, if the controller is of type "A2A"
-- * Anti-Ship - Any ship targets, if the controller is of type "A2S"
-- * CTLD - Combat transport and logistics deployment
-- * CSAR - Combat search and rescue
--
-- ## 3 Task repetition
--
@ -937,11 +1095,14 @@ do
-- NONE = "None",
-- POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!",
-- POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s",
-- RECCETARGETREPORT = "\nRecce %s in reach: %s\nLasing: %s",
-- POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.",
-- TARGET = "Target",
-- FLASHON = "%s - Flashing directions is now ON!",
-- FLASHOFF = "%s - Flashing directions is now OFF!",
-- FLASHMENU = "Flash Directions Switch",
-- BRIEFING = "Briefing",
-- TARGETLOCATION ="Target location",
-- },
--
-- e.g.
@ -1065,6 +1226,9 @@ PLAYERTASKCONTROLLER = {
PlayerInfoMenu = {},
noflaresmokemenu = false,
TransmitOnlyWithPlayers = true,
buddylasing = false,
PlayerRecce = nil,
Coalition = nil,
}
---
@ -1080,8 +1244,10 @@ PLAYERTASKCONTROLLER.Type = {
A2GS = "Air-To-Ground-Sea",
}
--- Define a new AUFTRAG Type
--- Define new AUFTRAG Types
AUFTRAG.Type.PRECISIONBOMBING = "Precision Bombing"
AUFTRAG.Type.CTLD = "Combat Transport"
AUFTRAG.Type.CSAR = "Combat Rescue"
---
-- @type SeadAttributes
@ -1152,11 +1318,14 @@ PLAYERTASKCONTROLLER.Messages = {
NONE = "None",
POINTEROVERTARGET = "%s, %s, pointer in reach for task %03d, lasing!",
POINTERTARGETREPORT = "\nPointer in reach: %s\nLasing: %s",
RECCETARGETREPORT = "\nRecce %s in reach: %s\nLasing: %s",
POINTERTARGETLASINGTTS = ". Pointer in reach and lasing.",
TARGET = "Target",
FLASHON = "%s - Flashing directions is now ON!",
FLASHOFF = "%s - Flashing directions is now OFF!",
FLASHMENU = "Flash Directions Switch",
BRIEFING = "Briefing",
TARGETLOCATION ="Target location",
},
DE = {
TASKABORT = "Auftrag abgebrochen!",
@ -1213,19 +1382,22 @@ PLAYERTASKCONTROLLER.Messages = {
NONE = "Keine",
POINTEROVERTARGET = "%s, %s, Marker im Zielbereich für %03d, Laser an!",
POINTERTARGETREPORT = "\nMarker im Zielbereich: %s\nLaser an: %s",
RECCETARGETREPORT = "\nSpäher % im Zielbereich: %s\nLasing: %s",
POINTERTARGETLASINGTTS = ". Marker im Zielbereich, Laser is an.",
TARGET = "Ziel",
FLASHON = "%s - Richtungsangaben einblenden ist EIN!",
FLASHOFF = "%s - Richtungsangaben einblenden ist AUS!",
FLASHMENU = "Richtungsangaben Schalter",
BRIEFING = "Briefing",
TARGETLOCATION ="Zielkoordinate",
},
}
--- PLAYERTASK class version.
-- @field #string version
PLAYERTASKCONTROLLER.version="0.1.38"
PLAYERTASKCONTROLLER.version="0.1.43"
--- Constructor
--- Create and run a new TASKCONTROLLER instance.
-- @param #PLAYERTASKCONTROLLER self
-- @param #string Name Name of this controller
-- @param #number Coalition of this controller, e.g. coalition.side.BLUE
@ -1303,9 +1475,9 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
self:AddTransition("*", "TaskRepeatOnFailed", "*")
self:AddTransition("*", "Stop", "Stopped")
self:__Start(-1)
self:__Start(2)
local starttime = math.random(5,10)
self:__Status(-starttime)
self:__Status(starttime)
-- Player leaves
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler)
@ -1454,12 +1626,13 @@ end
-- @param #string text Original text.
-- @return #string Spoken text.
function PLAYERTASKCONTROLLER:_GetTextForSpeech(text)
self:T(self.lid.."_GetTextForSpeech")
-- Space out numbers.
text=string.gsub(text,"%d","%1 ")
-- get rid of leading or trailing spaces
text=string.gsub(text,"^%s*","")
text=string.gsub(text,"%s*$","")
text=string.gsub(text," "," ")
return text
end
@ -1546,6 +1719,27 @@ function PLAYERTASKCONTROLLER:EnablePrecisionBombing(FlightGroup,LaserCode)
return self
end
--- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing.
-- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerRecce#PLAYERRECCE Recce (Optional) The PLAYERRECCE object governing the lasing players.
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:EnableBuddyLasing(Recce)
self:T(self.lid.."EnableBuddyLasing")
self.buddylasing = true
self.PlayerRecce = Recce
return self
end
--- [User] Allow precision laser-guided bombing on statics and "high-value" ground units (MBT etc) with player units lasing.
-- @param #PLAYERTASKCONTROLLER self
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:DisableBuddyLasing()
self:T(self.lid.."DisableBuddyLasing")
self.buddylasing = false
return self
end
--- [User] Allow addition of targets with user F10 map markers.
-- @param #PLAYERTASKCONTROLLER self
-- @param #string Tag (Optional) The tagname to use to identify commands, defaults to "TASK"
@ -1583,7 +1777,7 @@ end
-- @return #string playername
-- @return #string ttsplayername
function PLAYERTASKCONTROLLER:_GetPlayerName(Client)
self:T(self.lid.."DisablePrecisionBombing")
self:T(self.lid.."_GetPlayerName")
local playername = Client:GetPlayerName()
local ttsplayername = nil
if not self.customcallsigns[playername] then
@ -1717,7 +1911,7 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData)
end
playername = self:_GetTextForSpeech(playername)
--local text = string.format("%s, %s, switch to %s for task assignment!",EventData.IniPlayerName,self.MenuName or self.Name,freqtext)
local text = string.format(switchtext,self.MenuName or self.Name,playername,freqtext)
local text = string.format(switchtext,playername,self.MenuName or self.Name,freqtext)
self.SRSQueue:NewTransmission(text,nil,self.SRS,timer.getAbsTime()+60,2,{EventData.IniGroup},text,30,self.BCFrequency,self.BCModulation)
end
end
@ -1872,6 +2066,23 @@ function PLAYERTASKCONTROLLER:_CheckTaskQueue()
self:T("*****Removing player " .. _id)
self.TasksPerPlayer:PullByID(_id)
end
-- Follow-up tasks?
local nexttasks = {}
if task.FinalState == "Success" then
nexttasks = task.NextTaskSuccess
elseif task.FinalState == "Failed" then
nexttasks = task.NextTaskFailure
end
local clientlist, count = task:GetClientObjects()
if count > 0 then
for _,_client in pairs(clientlist) do
local client = _client --Wrapper.Client#CLIENT
local group = client:GetGroup()
for _,task in pairs(nexttasks) do
self:_JoinTask(group,client,task,true)
end
end
end
local TNow = timer.getAbsTime()
if TNow - task.timestamp > 10 then
local task = self.TaskQueue:PullByID(_id) -- Ops.PlayerTask#PLAYERTASK
@ -2225,7 +2436,7 @@ function PLAYERTASKCONTROLLER:_AddTask(Target)
ttstype = self.gettext:GetEntry("BAITTS",self.locale)
end
-- see if we can do precision bombing
if (type == AUFTRAG.Type.BAI or type == AUFTRAG.Type.CAS) and self.precisionbombing then
if (type == AUFTRAG.Type.BAI or type == AUFTRAG.Type.CAS) and (self.precisionbombing or self.buddylasing) then
-- threatlevel between 3 and 6 means, it's artillery, tank, modern tank or AAA
if threat > 2 and threat < 7 then
type = AUFTRAG.Type.PRECISIONBOMBING
@ -2317,16 +2528,53 @@ function PLAYERTASKCONTROLLER:_AddTask(Target)
return self
end
--- [User] Add a PLAYERTASK object to the list of (open) tasks
-- @param #PLAYERTASKCONTROLLER self
-- @param Ops.PlayerTask#PLAYERTASK PlayerTask
-- @return #PLAYERTASKCONTROLLER self
-- @usage
-- Example to create a PLAYERTASK of type CTLD and give Players 10 minutes to complete:
--
-- local newtask = PLAYERTASK:New(AUFTRAG.Type.CTLD,ZONE:Find("Unloading"),false,0,"Combat Transport")
-- newtask.Time0 = timer.getAbsTime() -- inject a timestamp for T0
-- newtask:AddFreetext("Transport crates to the drop zone and build a vehicle in the next 10 minutes!")
--
-- -- add a condition for failure - fail after 10 minutes
-- newtask:AddConditionFailure(
-- function()
-- local Time = timer.getAbsTime()
-- if Time - newtask.Time0 > 600 then
-- return true
-- end
-- return false
-- end
-- )
--
-- taskmanager:AddPlayerTaskToQueue(PlayerTask)
function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask)
self:T(self.lid.."AddPlayerTaskToQueue")
if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName == "PLAYERTASK" then
PlayerTask:_SetController(self)
PlayerTask:SetCoalition(self.Coalition)
self.TaskQueue:Push(PlayerTask)
self:__TaskAdded(-1,PlayerTask)
else
self:E(self.lid.."***** NO valid PAYERTASK object sent!")
end
return self
end
--- [Internal] Join a player to a task
-- @param #PLAYERTASKCONTROLLER self
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Client#CLIENT Client
-- @param Ops.PlayerTask#PLAYERTASK Task
-- @param #boolean Force Assign task even if client already has one
-- @return #PLAYERTASKCONTROLLER self
function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task)
function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task, Force)
self:T(self.lid.."_JoinTask")
local playername, ttsplayername = self:_GetPlayerName(Client)
if self.TasksPerPlayer:HasUniqueID(playername) then
if self.TasksPerPlayer:HasUniqueID(playername) and not Force then
-- Player already has a task
if not self.NoScreenOutput then
local text = self.gettext:GetEntry("HAVEACTIVETASK",self.locale)
@ -2350,7 +2598,7 @@ function PLAYERTASKCONTROLLER:_JoinTask(Group, Client, Task)
--local m=MESSAGE:New(text,"10","Tasking"):ToAll()
end
if self.UseSRS then
self:I(self.lid..text)
self:T(self.lid..text)
self.SRSQueue:NewTransmission(text,nil,self.SRS,nil,2)
end
self.TasksPerPlayer:Push(Task,playername)
@ -2424,8 +2672,10 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
self:T(self.lid.."_ActiveTaskInfo")
local playername, ttsplayername = self:_GetPlayerName(Client)
local text = ""
local textTTS = ""
if self.TasksPerPlayer:HasUniqueID(playername) or Task then
-- TODO: Show multiple?
-- NODO: Show multiple?
-- Details
local task = Task or self.TasksPerPlayer:ReadByID(playername) -- Ops.PlayerTask#PLAYERTASK
local tname = self.gettext:GetEntry("TASKNAME",self.locale)
local ttsname = self.gettext:GetEntry("TASKNAMETTS",self.locale)
@ -2438,6 +2688,7 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
else
CoordText = Coordinate:ToStringA2A(Client)
end
-- Threat Level
local ThreatLevel = task.Target:GetThreatLevelMax()
--local ThreatLevelText = "high"
local ThreatLevelText = self.gettext:GetEntry("THREATHIGH",self.locale)
@ -2448,11 +2699,13 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
--ThreatLevelText = "low"
ThreatLevelText = self.gettext:GetEntry("THREATLOW",self.locale)
end
-- Targetno and Threat
local targets = task.Target:CountTargets() or 0
local clientlist, clientcount = task:GetClients()
local ThreatGraph = "[" .. string.rep( "", ThreatLevel ) .. string.rep( "", 10 - ThreatLevel ) .. "]: "..ThreatLevel
local ThreatLocaleText = self.gettext:GetEntry("THREATTEXT",self.locale)
text = string.format(ThreatLocaleText, taskname, ThreatGraph, targets, CoordText)
-- Prec bombing
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.precisionbombing then
if self.LasingDrone and self.LasingDrone.playertask then
local yes = self.gettext:GetEntry("YES",self.locale)
@ -2464,12 +2717,57 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
text = text .. prectext
end
end
-- Buddylasing
if task.Type == AUFTRAG.Type.PRECISIONBOMBING and self.buddylasing then
if self.PlayerRecce then
local yes = self.gettext:GetEntry("YES",self.locale)
local no = self.gettext:GetEntry("NO",self.locale)
-- TODO make dist dependent on PlayerRecce Object
local reachdist = 8000
local inreach = false
-- someone close enough?
local pset = self.PlayerRecce.PlayerSet:GetAliveSet()
for _,_player in pairs(pset) do
local player = _player -- Wrapper.Client#CLIENT
local pcoord = player:GetCoordinate()
if pcoord:Get2DDistance(Coordinate) <= reachdist then
inreach = true
local callsign = player:GetGroup():GetCustomCallSign(self.ShortCallsign,self.Keepnumber,self.CallsignTranslations)
local playername = player:GetPlayerName()
local islasing = no
if self.PlayerRecce.CanLase[player:GetTypeName()] and self.PlayerRecce.AutoLase[playername] then
-- TODO - maybe compare Spot target
islasing = yes
end
local inrtext = inreach == true and yes or no
local prectext = self.gettext:GetEntry("RECCETARGETREPORT",self.locale)
-- RECCETARGETREPORT = "\nSpäher % im Zielbereich: %s\nLasing: %s",
prectext = string.format(prectext,callsign,inrtext,islasing)
text = text .. prectext
end
end
end
-- Transport
elseif task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR then
-- THREATTEXT = "%s\nThreat: %s\nTargets left: %d\nCoord: %s",
-- THREATTEXTTTS = "%s, %s. Target information for %s. Threat level %s. Targets left %d. Target location %s.",
text = taskname
textTTS = taskname
local detail = task:GetFreetext()
local detailTTS = task:GetFreetextTTS()
local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale)
local locatxt = self.gettext:GetEntry("TARGETLOCATION",self.locale)
text = text .. string.format("\n%s: %s\n%s %s",brieftxt,detail,locatxt,CoordText)
--text = text .. "\nBriefing: "..detail.."\nTarget location "..CoordText
--textTTS = textTTS .. "; Briefing: "..detailTTS.."\nTarget location "..CoordText
textTTS = textTTS .. string.format("; %s: %s; %s %s",brieftxt,detailTTS,locatxt,CoordText)
end
-- Pilots
local clienttxt = self.gettext:GetEntry("PILOTS",self.locale)
if clientcount > 0 then
for _,_name in pairs(clientlist) do
if self.customcallsigns[_name] then
-- personalized flight name in player naming
--_name = string.match(_name,"| ([%a]+)")
_name = self.customcallsigns[_name]
end
clienttxt = clienttxt .. _name .. ", "
@ -2479,7 +2777,14 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
local keine = self.gettext:GetEntry("NONE",self.locale)
clienttxt = clienttxt .. keine
end
-- Task Report
text = text .. clienttxt
if task:HasFreetext() and not ( task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR) then
local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale)
text = text .. string.format("\n%s: ",brieftxt)..task:GetFreetext()
end
textTTS = textTTS .. clienttxt
if self.UseSRS then
if string.find(CoordText," BR, ") then
CoordText = string.gsub(CoordText," BR, "," Bee, Arr, ")
@ -2492,6 +2797,15 @@ function PLAYERTASKCONTROLLER:_ActiveTaskInfo(Group, Client, Task)
local lasingtext = self.gettext:GetEntry("POINTERTARGETLASINGTTS",self.locale)
ttstext = ttstext .. lasingtext
end
elseif task.Type == AUFTRAG.Type.CTLD or task.Type == AUFTRAG.Type.CSAR then
ttstext = textTTS
if string.find(ttstext," BR, ") then
CoordText = string.gsub(ttstext," BR, "," Bee, Arr, ")
end
elseif task:HasFreetext() then
-- add tts freetext
local brieftxt = self.gettext:GetEntry("BRIEFING",self.locale)
ttstext = ttstext .. string.format("; %s: ",brieftxt)..task:GetFreetextTTS()
end
self.SRSQueue:NewTransmission(ttstext,nil,self.SRS,nil,2)
end
@ -2758,11 +3072,13 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
local active = MENU_GROUP_DELAYED:New(group,menuactive,topmenu)
local info = MENU_GROUP_COMMAND_DELAYED:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client)
local mark = MENU_GROUP_COMMAND_DELAYED:New(group,menumark,active,self._MarkTask,self,group,client)
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A or self.noflaresmokemenu then
-- no smoking/flaring here if A2A or designer has set to false
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
if self.noflaresmokemenu ~= true then
-- no smoking/flaring here if A2A or designer has set noflaresmokemenu to true
local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client)
local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client)
end
end
local abort = MENU_GROUP_COMMAND_DELAYED:New(group,menuabort,active,self._AbortTask,self,group,client)
if self.activehasinfomenu and self.taskinfomenu then
--self:T("Building Active-Info Menus for "..playername)

View File

@ -4,7 +4,7 @@
--
-- * Manages target, number alive, life points, damage etc.
-- * Events when targets are damaged or destroyed
-- * Various target objects: UNIT, GROUP, STATIC, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT
-- * Various target objects: UNIT, GROUP, STATIC, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT, SET_SCENERY
--
-- ===
--
@ -69,6 +69,7 @@ TARGET = {
casualties = {},
threatlevel0 = 0,
conditionStart = {},
TStatus = 30,
}
@ -146,7 +147,7 @@ _TARGETID=0
--- TARGET class version.
-- @field #string version
TARGET.version="0.5.2"
TARGET.version="0.5.4"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -185,6 +186,7 @@ function TARGET:New(TargetObject)
-- Defaults.
self:SetPriority()
self:SetImportance()
self.TStatus = 30
-- Log ID.
self.lid=string.format("TARGET #%03d | ", _TARGETID)
@ -259,12 +261,13 @@ end
-- * SET_UNIT
-- * SET_STATIC
-- * SET_OPSGROUP
-- * SET_SCENERY
--
-- @param #TARGET self
-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE.
function TARGET:AddObject(Object)
if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_OPSGROUP") then
if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_SCENERY") or Object:IsInstanceOf("SET_OPSGROUP") then
---
-- Sets
@ -570,7 +573,7 @@ function TARGET:onafterStatus(From, Event, To)
-- Update status again in 30 sec.
if self:IsAlive() then
self:__Status(-30)
self:__Status(-self.TStatus)
end
end
@ -828,8 +831,8 @@ function TARGET:_AddObject(Object)
if static and static:IsAlive() then
target.Life0=1
target.Life=1
target.Life0=static:GetLife0()
target.Life=static:GetLife()
target.N0=target.N0+1
table.insert(self.elements, target.Name)
@ -1131,8 +1134,9 @@ end
--- Get target 3D position vector.
-- @param #TARGET self
-- @param #TARGET.Object Target Target object.
-- @param #boolean Average
-- @return DCS#Vec3 Vector with x,y,z components.
function TARGET:GetTargetVec3(Target)
function TARGET:GetTargetVec3(Target, Average)
if Target.Type==TARGET.ObjectType.GROUP then
@ -1140,6 +1144,9 @@ function TARGET:GetTargetVec3(Target)
if object and object:IsAlive() then
local vec3=object:GetVec3()
if Average then
vec3=object:GetAverageVec3()
end
if vec3 then
return vec3
@ -1218,8 +1225,9 @@ end
--- Get target coordinate.
-- @param #TARGET self
-- @param #TARGET.Object Target Target object.
-- @param #boolean Average
-- @return Core.Point#COORDINATE Coordinate of the target.
function TARGET:GetTargetCoordinate(Target)
function TARGET:GetTargetCoordinate(Target, Average)
if Target.Type==TARGET.ObjectType.COORDINATE then
@ -1229,7 +1237,7 @@ function TARGET:GetTargetCoordinate(Target)
else
-- Get updated position vector.
local vec3=self:GetTargetVec3(Target)
local vec3=self:GetTargetVec3(Target, Average)
-- Update position. This saves us to create a new COORDINATE object each time.
if vec3 then
@ -1362,6 +1370,26 @@ function TARGET:GetCoordinate()
return nil
end
--- Get average coordinate.
-- @param #TARGET self
-- @return Core.Point#COORDINATE Coordinate of the target.
function TARGET:GetAverageCoordinate()
for _,_target in pairs(self.targets) do
local Target=_target --#TARGET.Object
local coordinate=self:GetTargetCoordinate(Target,true)
if coordinate then
return coordinate
end
end
self:E(self.lid..string.format("ERROR: Cannot get average coordinate of target %s", tostring(self.name)))
return nil
end
--- Get category.
-- @param #TARGET self
-- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`.

View File

@ -1019,9 +1019,9 @@ function GROUP:GetVec2()
end
--- Returns the current Vec3 vector of the first DCS Unit in the GROUP.
--- Returns the current Vec3 vector of the first Unit in the GROUP.
-- @param #GROUP self
-- @return DCS#Vec3 Current Vec3 of the first DCS Unit of the GROUP.
-- @return DCS#Vec3 Current Vec3 of the first Unit of the GROUP or nil if cannot be found.
function GROUP:GetVec3()
-- Get first unit.
@ -1036,6 +1036,37 @@ function GROUP:GetVec3()
return nil
end
--- Returns the average Vec3 vector of the Units in the GROUP.
-- @param #GROUP self
-- @return DCS#Vec3 Current Vec3 of the GROUP or nil if cannot be found.
function GROUP:GetAverageVec3()
local units = self:GetUnits() or {}
-- Init.
local x=0 ; local y=0 ; local z=0 ; local n=0
-- Loop over all units.
for _,unit in pairs(units) do
local vec3=nil --DCS#Vec3
if unit and unit:IsAlive() then
vec3 = unit:GetVec3()
end
if vec3 then
-- Sum up posits.
x=x+vec3.x
y=y+vec3.y
z=z+vec3.z
-- Increase counter.
n=n+1
end
end
if n>0 then
-- Average.
local Vec3={x=x/n, y=y/n, z=z/n} --DCS#Vec3
return Vec3
end
return nil
end
--- Returns a POINT_VEC2 object indicating the point in 2D of the first UNIT of the GROUP within the mission.
-- @param #GROUP self
-- @return Core.Point#POINT_VEC2 The 2D point vector of the first DCS Unit of the GROUP.
@ -1056,6 +1087,21 @@ function GROUP:GetPointVec2()
return nil
end
--- Returns a COORDINATE object indicating the average position of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetAverageCoordinate()
local vec3 = self:GetAverageVec3()
if vec3 then
local coord = COORDINATE:NewFromVec3(vec3)
local Heading = self:GetHeading()
coord.Heading = Heading
else
BASE:E( { "Cannot GetAverageCoordinate", Group = self, Alive = self:IsAlive() } )
return nil
end
end
--- Returns a COORDINATE object indicating the point of the first UNIT of the GROUP within the mission.
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.

View File

@ -4,7 +4,7 @@
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
-- ### Contributions: **Applevangelist**
--
-- ===
--
@ -14,6 +14,10 @@
--- @type SCENERY
-- @field #string ClassName
-- @field #string SceneryName
-- @field #DCS.Object SceneryObject
-- @field #number Life0
-- @extends Wrapper.Positionable#POSITIONABLE
@ -30,7 +34,6 @@ SCENERY = {
ClassName = "SCENERY",
}
--- Register scenery object as POSITIONABLE.
--@param #SCENERY self
--@param #string SceneryName Scenery name.
@ -40,17 +43,54 @@ function SCENERY:Register( SceneryName, SceneryObject )
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
self.SceneryName = SceneryName
self.SceneryObject = SceneryObject
if self.SceneryObject then
self.Life0 = self.SceneryObject:getLife()
else
self.Life0 = 0
end
return self
end
--- Register scenery object as POSITIONABLE.
--- Obtain DCS Object from the SCENERY Object.
--@param #SCENERY self
--@return #DCS.Object DCS scenery object.
function SCENERY:GetDCSObject()
return self.SceneryObject
end
--- Register scenery object as POSITIONABLE.
--- Get current life points from the SCENERY Object.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife()
local life = 0
if self.SceneryObject then
life = self.SceneryObject:getLife()
end
return life
end
--- Get current initial life points from the SCENERY Object.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife0()
return self.Life0 or 0
end
--- Check if SCENERY Object is alive.
--@param #SCENERY self
--@return #number life
function SCENERY:IsAlive()
return self:GetLife() >= 1 and true or false
end
--- Check if SCENERY Object is dead.
--@param #SCENERY self
--@return #number life
function SCENERY:IsDead()
return self:GetLife() < 1 and true or false
end
--- Get the threat level of a SCENERY object. Always 0.
--@param #SCENERY self
--@return #number Threat level 0.
--@return #string "Scenery".
@ -58,39 +98,73 @@ function SCENERY:GetThreatLevel()
return 0, "Scenery"
end
--- Find a SCENERY object by it's name/id.
--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
-- to find the correct object.
--@param #SCENERY self
--@param #string name The name/id of the scenery object as taken from the ME. Ex. '595785449'
--@return #SCENERY Scenery Object or nil if not found.
function SCENERY:FindByName(name)
local findAirbase = function ()
local airbases = AIRBASE.GetAllAirbases()
for index,airbase in pairs(airbases) do
local surftype = airbase:GetCoordinate():GetSurfaceType()
if surftype ~= land.SurfaceType.SHALLOW_WATER and surftype ~= land.SurfaceType.WATER then
return airbase:GetCoordinate()
--@param #string Name The name/id of the scenery object as taken from the ME. Ex. '595785449'
--@param Core.Point#COORDINATE Coordinate Where to find the scenery object
--@param #number Radius (optional) Search radius around coordinate, defaults to 100
--@return #SCENERY Scenery Object or `nil` if it cannot be found
function SCENERY:FindByName(Name, Coordinate, Radius)
local radius = Radius or 100
local name = Name or "unknown"
local scenery = nil
---
-- @param Core.Point#COORDINATE coordinate
-- @param #number radius
-- @param #string name
local function SceneryScan(coordinate, radius, name)
if coordinate ~= nil then
local scenerylist = coordinate:ScanScenery(radius)
local rscenery = nil
for _,_scenery in pairs(scenerylist) do
local scenery = _scenery -- Wrapper.Scenery#SCENERY
if tostring(scenery.SceneryName) == tostring(name) then
rscenery = scenery
break
end
end
return rscenery
end
return nil
end
local sceneryScan = function (scancoord)
if scancoord ~= nil then
local _,_,sceneryfound,_,_,scenerylist = scancoord:ScanObjects(200, false, false, true)
if sceneryfound == true then
scenerylist[1].id_ = name
SCENERY.SceneryObject = SCENERY:Register(scenerylist[1].id_, scenerylist[1])
return SCENERY.SceneryObject
end
end
return nil
if Coordinate then
scenery = SceneryScan(Coordinate, radius, name)
end
if SCENERY.SceneryObject then
SCENERY.SceneryObject.SceneryObject.id_ = name
SCENERY.SceneryObject.SceneryName = name
return SCENERY:Register(SCENERY.SceneryObject.SceneryObject.id_, SCENERY.SceneryObject.SceneryObject)
else
return sceneryScan(findAirbase())
return scenery
end
--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
-- to find the correct object.
--@param #SCENERY self
--@param #string Name The name or id of the scenery object as taken from the ME. Ex. '595785449'
--@param Core.Zone#ZONE Zone Where to find the scenery object. Can be handed as zone name.
--@param #number Radius (optional) Search radius around coordinate, defaults to 100
--@return #SCENERY Scenery Object or `nil` if it cannot be found
function SCENERY:FindByNameInZone(Name, Zone, Radius)
local radius = Radius or 100
local name = Name or "unknown"
if type(Zone) == "string" then
Zone = ZONE:FindByName(Zone)
end
local coordinate = Zone:GetCoordinate()
return self:FindByName(Name,coordinate,Radius)
end
--- Find a SCENERY object from its zone name. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
-- to find the correct object.
--@param #SCENERY self
--@param #string ZoneName The name of the scenery zone as created with a right-click on the map in the mission editor and select "assigned to...". Can be handed over as ZONE object.
--@return #SCENERY Scenery Object or `nil` if it cannot be found
function SCENERY:FindByZoneName( ZoneName )
local zone = ZoneName
if type(ZoneName) == "string" then
zone = ZONE:FindByName(ZoneName)
end
local _id = zone:GetProperty('OBJECT ID')
return self:FindByName(_id, zone:GetCoordinate())
end

View File

@ -55,9 +55,33 @@ STATIC = {
function STATIC:Register( StaticName )
local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) )
self.StaticName = StaticName
local DCSStatic = StaticObject.getByName( self.StaticName )
if DCSStatic then
local Life0 = DCSStatic:getLife() or 1
self.Life0 = Life0
end
return self
end
--- Get initial life points
-- @param #STATIC self
-- @return #number lifepoints
function STATIC:GetLife0()
return self.Life0 or 1
end
--- Get current life points
-- @param #STATIC self
-- @return #number lifepoints or nil
function STATIC:GetLife()
local DCSStatic = StaticObject.getByName( self.StaticName )
if DCSStatic then
return DCSStatic:getLife() or 1
end
return nil
end
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
-- @param #STATIC self