Compare commits

...

49 Commits

Author SHA1 Message Date
Applevangelist
0971f8ffa7 #SPAWN
* Fix for InitCleanup()
2022-09-22 10:57:43 +02:00
Applevangelist
9739f94428 #ATIS
* Add MSRSQUEUE to avoid overlaps
2022-09-20 17:15:57 +02:00
Applevangelist
450f4eaec0 #AIRBASE, ATIS
* less noise in log

#SRS
* Added enumerator for voices MSRS.Voices.Microsoft... and MSRS.Voices.Google...

#RANGE
* Added SRS support, some bug fixing
2022-09-20 15:51:16 +02:00
Applevangelist
b5186fa328 #SETTINGS
* Fix IsImperial()
2022-09-20 15:26:34 +02:00
Applevangelist
526dc8363d #DESIGNATE
* Remove useless summary autolase report, restored single lines when lasing
2022-09-20 10:26:19 +02:00
Applevangelist
242d735d8c #DETECTION
* Make designate refer A2G coordinate system as set by user
2022-09-20 09:27:19 +02:00
Applevangelist
9fedd32e78 #UTILS
* Fix for group set reloading - thanks @BlueEcko!
2022-09-20 08:23:49 +02:00
Applevangelist
a146003a6a #DESIGNATE
* Some documentation clarifications
2022-09-19 18:25:54 +02:00
Applevangelist
d17df74191 ATIS
* Remove dupe inheritance of BASE
2022-09-19 13:20:45 +02:00
Applevangelist
d23403383f #CSAR
* Added option to use the OV-10A Bronco
* Slight changes to SRS approach
* Added door check in UTILS for Bronco
2022-09-19 11:43:50 +02:00
Applevangelist
877b36f8a8 #CSAR
* overcome error cannot get Vec3
2022-09-15 19:51:15 +02:00
Applevangelist
299820162b #ATIS
* No old-style radioqueue when using SRS
2022-09-15 14:01:23 +02:00
Applevangelist
5d20a59d92 #GROUP
* Customized Callsigns - remove extra spaces
2022-09-15 13:40:37 +02:00
Applevangelist
c1ab3b5476 #GROUP
* Fix ignore leading spaces in customized callsigns
2022-09-15 08:17:37 +02:00
Applevangelist
0076130483 #GROUP
* Docu clarifications
2022-09-13 10:51:25 +02:00
Applevangelist
a5194b1999 #GROUP
-- remove log output
2022-09-12 17:33:40 +02:00
Applevangelist
b53b1f083f #GROUP
* Fix IsPlayer
* Added `GROUP:GetCustomCallSign()`
2022-09-12 17:28:49 +02:00
Frank
994a36001f Merge pull request #1782 from FlightControl-Master/FF/MasterDevel
Updates from develop
2022-09-11 13:38:00 +02:00
Frank
26b91e3f66 Updates from develop
DCS.lua
Ops/ATIS.lua
Ops/RecoveryTanker.lua
Utilities/Enums.lua
2022-09-11 13:32:57 +02:00
Applevangelist
f42aedd485 #CONTROLLABLE
*Foreign class definiton removed
2022-09-10 15:14:28 +02:00
Applevangelist
a73818a615 #BUILD files 2022-09-10 12:08:30 +02:00
Applevangelist
7c22e9fe69 #Changes from Develop 2022-09-10 11:56:03 +02:00
Applevangelist
c58e91b956 #TASKING
* Merge changes from Develop
2022-09-10 11:41:41 +02:00
Applevangelist
a61059242c #AI
* Merge changes from DEV
2022-09-10 11:38:50 +02:00
Applevangelist
cea9437e66 #GROUP
Fix GetSpeedMax() always returns zero
2022-09-09 17:33:23 +02:00
Applevangelist
3f052ef1da #GROUP
* changes from Dev
#UNIT
* changes from Dev
2022-09-08 15:49:58 +02:00
Applevangelist
c137a4b06d Revert "#ZONE"
This reverts commit 1207417894.
2022-09-06 13:18:22 +02:00
Applevangelist
6c46dafc59 Revert "Auxiliary commit to revert individual files from 1207417894a369fa4a353ca0a1f6120c2fc32666"
This reverts commit 551d4a9c85c89e4aaad2c59ed058c84ff8c7ad6c.
2022-09-06 13:16:40 +02:00
Applevangelist
1207417894 #ZONE
* ZONE_ELASTIC
* Typo
2022-09-06 11:17:50 +02:00
Frank
7c717c219c Merge pull request #1771 from grandpaSam/master
Updated CARGO tag documentation
2022-09-01 22:54:22 +02:00
grandpaSam
a9fad53255 Updated CARGO tag documentation
The documentation for the CARGO tag on static objects omitted a required C= parameter. Without this parameter the database will not register the static as a CARGO object
2022-09-01 11:49:46 -07:00
Applevangelist
372cc021bd UNIT - added optical tracker to HasSEAD() 2022-09-01 08:36:40 +02:00
Applevangelist
6820774266 GROUP - added optical tracker to ID SAMs 2022-09-01 08:03:53 +02:00
Applevangelist
a0c189a349 UTILS - added NDB 305kHz in frequency generator 2022-08-31 18:11:55 +02:00
Applevangelist
1060ff16d8 GROUP - Attributes aligned to dev 2022-08-31 17:40:44 +02:00
Applevangelist
1255f48645 Update Moose.files 2022-08-30 14:38:36 +02:00
Applevangelist
7ef69208d4 #ZONE_RADIUS
* Added `ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(...)`

#MARKEROPS_BASE
* Added Class
2022-08-30 14:36:42 +02:00
Applevangelist
5277cca4e1 Range fix issue #1753 (#1767)
Issue #1753 - when using `AddBombingTargets` the randommove flag was not passed to `AddBombingTargetUnit` for a group object
2022-08-25 16:00:47 +02:00
Applevangelist
aaf77815ca #SRS
* Added MSRSQUEUE message queue
2022-08-25 10:50:45 +02:00
Applevangelist
3f488cc091 #AIRBASE
* Added 3 new airports to the enumerator SouthAtlantic
2022-08-25 10:38:44 +02:00
Applevangelist
bdbbdfe60e #RANGE
* Added Instructor and RangeControl radio info on F10
2022-08-23 09:58:50 +02:00
Applevangelist
d15c2be2d0 Range fixes (#1764)
* Fixed some typos in self.PlayerSettings
* Fixed result messages
* Thanks for [JFG] Caponi
2022-08-22 17:03:33 +02:00
Applevangelist
73994914eb Fix for #1763 UTILS.GetOSTime() (#1765)
* Fix GetOSTime fixes #1763
2022-08-22 17:03:21 +02:00
Applevangelist
63b0dae794 Update ATC_Ground.lua (#1766)
* Make ATC_Ground work on any map
2022-08-22 17:03:02 +02:00
Engines
003e865ff7 Late break bug (#1762)
* Quick update to Airboss.lua to inluce Invincible.  Not yet tested.

* Initial quick test and calibration of the landing spot; looks good.  More testing required.

* Recompiled the Moose.lua based off the last commit.

* Removing surpurflous Moose.lua for pull request.

* There was an error with the late break paratemers on the Brit carriers causing pilots to be thrown off the pattern on the break.  Fixed, tested on Invicible, working as anticipated now.

* Removing the test Moose.lua (again).

* Fixing merge confilict

* Trying again.
2022-08-15 07:15:15 +02:00
Gavin Edwards
e3c03287b7 Engines invincible (#1758)
* Quick update to Airboss.lua to inluce Invincible.  Not yet tested.

* Initial quick test and calibration of the landing spot; looks good.  More testing required.

* Recompiled the Moose.lua based off the last commit.

* Removing surpurflous Moose.lua for pull request.
2022-08-13 21:40:53 +02:00
Applevangelist
73493c3a23 Update Unit.lua (#1751) 2022-07-31 09:17:36 +02:00
Applevangelist
562a3f6208 SET - fix for dead units 2022-07-29 08:51:23 +02:00
Applevangelist
3c5f3d6c37 UNIT - added get altitude function with AGL option 2022-07-25 08:12:28 +02:00
72 changed files with 10150 additions and 6634 deletions

View File

@@ -0,0 +1,17 @@
{
"Lua.workspace.preloadFileSize": 1000,
"Lua.diagnostics.disable": [
"undefined-doc-name"
],
"Lua.diagnostics.globals": [
"BASE",
"lfs",
"__Moose",
"trigger",
"coord",
"missionCommands"
],
"Lua.completion.displayContext": 5,
"Lua.runtime.version": "Lua 5.1",
"Lua.completion.callSnippet": "Both"
}

View File

@@ -881,8 +881,9 @@ do -- AI_A2A_DISPATCHER
--- Enumerator for spawns at airbases
-- @type AI_A2A_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
---
-- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defines Landing type/location.
@@ -928,6 +929,8 @@ do -- AI_A2A_DISPATCHER
self.DefenderTasks = {} -- The Defenders Tasks.
self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons.
self.SetSendPlayerMessages = false --#boolean Flash messages to player
-- TODO: Check detection through radar.
self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
-- self.Detection:InitDetectRadar( true )
@@ -2319,6 +2322,13 @@ do -- AI_A2A_DISPATCHER
return self
end
--- Set flashing player messages on or off
-- @param #AI_A2A_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function AI_A2A_DISPATCHER:SetSendMessages( onoff )
self.SetSendPlayerMessages = onoff
end
--- Sets flights to take-off in the air, as part of the defense system.
-- @param #AI_A2A_DISPATCHER self
-- @param #string SquadronName The name of the squadron.
@@ -3188,7 +3198,9 @@ do -- AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
end
AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling
end
end
@@ -3200,10 +3212,10 @@ do -- AI_A2A_DISPATCHER
self:GetParent( self ).onafterPatrolRoute( self, DefenderGroup, From, Event, To )
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3219,8 +3231,8 @@ do -- AI_A2A_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end
@@ -3404,10 +3416,10 @@ do -- AI_A2A_DISPATCHER
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup )
if DefenderTarget then
if Squadron.Language == "EN" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup )
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup )
end
-- Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
@@ -3425,12 +3437,12 @@ do -- AI_A2A_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
if Squadron.Language == "EN" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "DE" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехватывая боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
end
end
self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
@@ -3447,10 +3459,10 @@ do -- AI_A2A_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
if Squadron.Language == "EN" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", задействуя боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
end
end
self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit )
@@ -3465,10 +3477,10 @@ do -- AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
if Squadron.Language == "EN" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup )
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращение на базу.", DefenderGroup )
end
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3496,11 +3508,11 @@ do -- AI_A2A_DISPATCHER
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron.Language == "EN" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup )
end
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", посадка на базу.", DefenderGroup )
end
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
@@ -3890,7 +3902,7 @@ do
self:CAP( SquadronName )
end
--- Add resources to a Squadron
--- Add resources to a Squadron
-- @param #AI_A2A_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to add.
@@ -3913,7 +3925,7 @@ do
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
end
do
@@ -4484,5 +4496,5 @@ do
return self
end
end

View File

@@ -999,7 +999,9 @@ do -- AI_A2G_DISPATCHER
-- self.Detection:InitDetectRadar( false )
-- self.Detection:InitDetectVisual( true )
-- self.Detection:SetRefreshTimeInterval( 30 )
self.SetSendPlayerMessages = false --flash messages to players
self:SetDefenseRadius()
self:SetDefenseLimit( nil )
self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random )
@@ -3718,7 +3720,9 @@ do -- AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
end
AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit
end
end
@@ -3730,7 +3734,7 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
end
@@ -3749,8 +3753,9 @@ do -- AI_A2G_DISPATCHER
if Squadron and AttackSetUnit:Count() > 0 then
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3764,8 +3769,9 @@ do -- AI_A2G_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3776,8 +3782,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end
@@ -3789,7 +3796,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
end
if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3804,8 +3813,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
end
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3861,7 +3871,9 @@ do -- AI_A2G_DISPATCHER
self:F( { DefenderTarget = DefenderTarget } )
if DefenderTarget then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
end
AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
end
end
@@ -3876,8 +3888,9 @@ do -- AI_A2G_DISPATCHER
if Squadron then
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
end
@@ -3892,8 +3905,9 @@ do -- AI_A2G_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3903,8 +3917,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
end
self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3918,8 +3933,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
--Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
end
if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3934,8 +3950,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
end
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3975,7 +3992,7 @@ do -- AI_A2G_DISPATCHER
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
-- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates.
-- (y1 y2)x + (x2 x1)y + (x1y2 x2y1) = 0
-- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0
local c1 = DefenseCoordinate
local c2 = AttackCoordinate
@@ -4036,7 +4053,7 @@ do -- AI_A2G_DISPATCHER
for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do
-- Here we check if the defenders have a defense line to the attackers.
-- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage.
-- If the attackers are behind enemy lines or too close to an other defense line; then don't engage.
local DefenseCoordinate = DefenderGroup:GetCoordinate()
local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem )
@@ -4341,7 +4358,7 @@ do -- AI_A2G_DISPATCHER
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
@@ -4559,7 +4576,7 @@ do -- AI_A2G_DISPATCHER
if self.TacticalDisplay then
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
@@ -4729,7 +4746,15 @@ do
self:Patrol( SquadronName, PatrolTaskType )
end
--- Add resources to a Squadron
--- Set flashing player messages on or off
-- @param #AI_A2G_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function AI_A2G_DISPATCHER:SetSendMessages( onoff )
self.SetSendPlayerMessages = onoff
end
end
--- Add resources to a Squadron
-- @param #AI_A2G_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to add.
@@ -4751,7 +4776,4 @@ do
Squadron.ResourceCount = Squadron.ResourceCount - Amount
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
end
end

View File

@@ -428,9 +428,7 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then -- flight dead at this point
return self
end
if not CurrentVec2 then return self end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()

View File

@@ -459,7 +459,7 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed

View File

@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
self:SetPickupSpeed( 350, 150 )
self:SetDeploySpeed( 350, 150 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:SetPickupRadius( 40, 12 )
self:SetDeployRadius( 40, 12 )
self:SetPickupHeight( 500, 200 )
self:SetDeployHeight( 500, 200 )
@@ -186,6 +186,9 @@ end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
dispatcher:SetLandingSpeedAndHeight(27, 6)
return dispatcher
end

View File

@@ -21,7 +21,7 @@
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Ship
-- @image AI_Cargo_Dispatching_For_Ship.JPG
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_DISPATCHER_SHIP
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER

View File

@@ -7,7 +7,7 @@
-- ===
--
-- @module AI.AI_Cargo_Ship
-- @image AI_Cargo_Dispatching_For_Ship.JPG
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_SHIP
-- @extends AI.AI_Cargo#AI_CARGO

View File

@@ -1140,8 +1140,8 @@ end
-- @param DCS#Vec3 CV2 Vec3
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
FollowGroup:OptionROTEvadeFire()

View File

@@ -725,7 +725,6 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
return
end
local life = self.Controllable:GetLife() or 0
if self.Controllable:IsAlive() and life > 1 then
-- Determine if the AIControllable is within the PatrolZone.
@@ -745,7 +744,7 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
self:T( "Not in the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
if not CurrentVec2 then return end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed

View File

@@ -206,27 +206,29 @@
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.
-- Note that this option is optional, so can be omitted. The default value of the RR is 10 meters.
--
-- ## 5.2) The \#CARGO tag to create CARGO_CRATE objects:
-- ## 5.2) The \#CARGO tag to create CARGO_CRATE or CARGO_SLINGLOAD objects:
--
-- You can also use the \#CARGO tag on **static** objects, including **static cargo** objects of the mission editor.
--
-- For example, the following #CARGO naming in the **static name** of the object, will create a CARGO_CRATE object when the mission starts.
--
-- `Static #CARGO(T=Workmaterials,RR=500,NR=25)`
-- `Static #CARGO(T=Workmaterials,C=CRATE,RR=500,NR=25)`
--
-- This will create a CARGO_CRATE object:
--
-- * with the group name `Static #CARGO`
-- * is of type `Workmaterials`
-- * is of category `CRATE` (as opposed to `SLING`)
-- * will report when a carrier is within 500 meters
-- * will board to carriers when the carrier is within 500 meters from the cargo object
-- * will disappear when the cargo is within 25 meters from the carrier during boarding
--
-- So the overall syntax of the #CARGO naming tag and arguments are:
--
-- `StaticName #CARGO(T=CargoTypeName,RR=Range,NR=Range)`
-- `StaticName #CARGO(T=CargoTypeName,C=Category,RR=Range,NR=Range)`
--
-- * **T=** Provide a text that contains the type name of the cargo object. This type name can be used to filter cargo within a SET_CARGO object.
-- * **C=** Provide either `CRATE` or `SLING` to have this static created as a CARGO_CRATE or CARGO_SLINGLOAD respectivly.
-- * **RR=** Provide the minimal range in meters when the report to the carrier, and board to the carrier.
-- Note that this option is optional, so can be omitted. The default value of the RR is 250 meters.
-- * **NR=** Provide the maximum range in meters when the cargo units will be boarded within the carrier during boarding.

View File

@@ -0,0 +1,943 @@
--- **Core** - A* Pathfinding.
--
-- **Main Features:**
--
-- * Find path from A to B.
-- * Pre-defined as well as custom valid neighbour functions.
-- * Pre-defined as well as custom cost functions.
-- * Easy rectangular grid setup.
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Astar
-- @image CORE_Astar.png
--- ASTAR class.
-- @type ASTAR
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table nodes Table of nodes.
-- @field #number counter Node counter.
-- @field #number Nnodes Number of nodes.
-- @field #number nvalid Number of nvalid calls.
-- @field #number nvalidcache Number of cached valid evals.
-- @field #number ncost Number of cost evaluations.
-- @field #number ncostcache Number of cached cost evals.
-- @field #ASTAR.Node startNode Start node.
-- @field #ASTAR.Node endNode End node.
-- @field Core.Point#COORDINATE startCoord Start coordinate.
-- @field Core.Point#COORDINATE endCoord End coordinate.
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
-- @field #table CostArg Optional arguments passed to the cost function.
-- @extends Core.Base#BASE
--- *When nothing goes right... Go left!*
--
-- ===
--
-- # The ASTAR Concept
--
-- Pathfinding algorithm.
--
--
-- # Start and Goal
--
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
--
-- ## Start
--
-- The start
--
-- ## Goal
--
--
-- # Nodes
--
-- ## Rectangular Grid
--
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
--
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
--
-- ## Valid Surfaces
--
-- Certain unit types can only travel on certain surfaces types, for example
--
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
-- * Ground units can only traval on land.
--
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
--
-- ## Box Width (BoxHY)
--
-- The box width needs to be large enough to capture all paths you want to consider.
--
-- ## Space in X
--
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
--
-- ## Grid Spacing
--
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
-- However, if the value is too large, the algorithm might fail to get a valid path.
--
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
--
-- # Valid Neighbours
--
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
--
-- ## Line of Sight
--
-- For naval
--
--
-- # Heuristic Cost
--
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
--
--
-- # Calculate the Path
--
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
-- describe the optimal path from the start node to the end node.
--
-- By default, the start and end node are include in the table that is returned.
--
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
--
-- Common reasons that a path cannot be found are:
--
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
-- * There simply is no valid path ==> you are screwed :(
--
--
-- # Examples
--
-- ## Strait of Hormuz
--
-- Carrier Group finds its way through the Stait of Hormuz.
--
-- ##
--
--
--
-- @field #ASTAR
ASTAR = {
ClassName = "ASTAR",
Debug = nil,
lid = nil,
nodes = {},
counter = 1,
Nnodes = 0,
ncost = 0,
ncostcache = 0,
nvalid = 0,
nvalidcache = 0,
}
--- Node data.
-- @type ASTAR.Node
-- @field #number id Node id.
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
-- @field #number surfacetype Surface type.
-- @field #table valid Cached valid/invalid nodes.
-- @field #table cost Cached cost.
--- ASTAR infinity.
-- @field #number INF
ASTAR.INF=1/0
--- ASTAR class version.
-- @field #string version
ASTAR.version="0.4.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add more valid neighbour functions.
-- TODO: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ASTAR object.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:New()
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
self.lid="ASTAR | "
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set coordinate from where to start.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
-- @return #ASTAR self
function ASTAR:SetStartCoordinate(Coordinate)
self.startCoord=Coordinate
return self
end
--- Set coordinate where you want to go.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate end coordinate.
-- @return #ASTAR self
function ASTAR:SetEndCoordinate(Coordinate)
self.endCoord=Coordinate
return self
end
--- Create a node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
-- @return #ASTAR.Node The node.
function ASTAR:GetNodeFromCoordinate(Coordinate)
local node={} --#ASTAR.Node
node.coordinate=Coordinate
node.surfacetype=Coordinate:GetSurfaceType()
node.id=self.counter
node.valid={}
node.cost={}
self.counter=self.counter+1
return node
end
--- Add a node to the table of grid nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @return #ASTAR self
function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
return self
end
--- Add a node to the table of grid nodes specifying its coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
-- @return #ASTAR.Node The node.
function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node)
return node
end
--- Check if the coordinate of a node has is at a valid surface type.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
-- @return #boolean If true, surface type of node is valid.
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
if SurfaceTypes then
if type(SurfaceTypes)~="table" then
SurfaceTypes={SurfaceTypes}
end
for _,surface in pairs(SurfaceTypes) do
if surface==Node.surfacetype then
return true
end
end
return false
else
return true
end
end
--- Add a function to determine if a neighbour of a node is valid.
-- @param #ASTAR self
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
self.ValidNeighbourFunc=NeighbourFunction
self.ValidNeighbourArg={}
if arg then
self.ValidNeighbourArg=arg
end
return self
end
--- Set valid neighbours to require line of sight between two nodes.
-- @param #ASTAR self
-- @param #number CorridorWidth Width of LoS corridor in meters.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourDistance(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourRoad(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
return self
end
--- Set the function which calculates the "cost" to go from one to another node.
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
-- Very often the distance between nodes is a good measure for the cost.
-- @param #ASTAR self
-- @param #function CostFunction Function that returns the "cost".
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetCostFunction(CostFunction, ...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
--- Set heuristic cost to go from one node to another to be their 2D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Grid functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a rectangular grid of nodes between star and end coordinate.
-- The coordinate system is oriented along the line between start and end point.
-- @param #ASTAR self
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
-- @return #ASTAR self
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
-- Note that internally
-- x coordinate is z: x-->z Line from start to end
-- y coordinate is x: y-->x Perpendicular
-- Grid length and width.
local Dz=SpaceX or 10000
local Dx=BoxHY and BoxHY/2 or 20000
-- Increments.
local dz=deltaX or 2000
local dx=deltaY or dz
-- Heading from start to end coordinate.
local angle=self.startCoord:HeadingTo(self.endCoord)
--Distance between start and end.
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
-- Origin of map. Needed to translate back to wanted position.
local co=COORDINATE:New(0, 0, 0)
local do1=co:Get2DDistance(self.startCoord)
local ho1=co:HeadingTo(self.startCoord)
-- Start of grid.
local xmin=-Dx
local zmin=-Dz
-- Number of grid points.
local nz=dist/dz+1
local nx=2*Dx/dx+1
-- Debug info.
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
self:T(self.lid..text)
-- Loop over x and z coordinate to create a 2D grid.
for i=1,nx do
-- x coordinate perpendicular to z.
local x=xmin+dx*(i-1)
for j=1,nz do
-- z coordinate connecting start and end.
local z=zmin+dz*(j-1)
-- Rotate 2D.
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
-- Coordinate of the node.
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
-- Create a node at this coordinate.
local node=self:GetNodeFromCoordinate(c)
-- Check if node has valid surface type.
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
if MarkGrid then
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
end
-- Add node to grid.
self:AddNode(node)
end
end
end
-- Debug info.
local text=string.format("Done building grid!")
self:T2(self.lid..text)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Valid neighbour functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function to check if two nodes have line of sight (LoS).
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number corridor (Optional) Width of corridor in meters.
-- @return #boolean If true, two nodes have LoS.
function ASTAR.LoS(nodeA, nodeB, corridor)
local offset=1
local dx=corridor and corridor/2 or nil
local dy=dx
local cA=nodeA.coordinate:GetVec3()
local cB=nodeB.coordinate:GetVec3()
cA.y=offset
cB.y=offset
local los=land.isVisible(cA, cB)
if los and corridor then
-- Heading from A to B.
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
los=land.isVisible(Ap, Bp)
if los then
local Am=UTILS.VecTranslate(cA, dx, heading-90)
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
los=land.isVisible(Am, Bm)
end
end
return los
end
--- Function to check if two nodes have a road connection.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #boolean If true, two nodes are connected via a road.
function ASTAR.Road(nodeA, nodeB)
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
return true
else
return false
end
end
--- Function to check if distance between two nodes is less than a threshold distance.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number distmax Max distance in meters. Default is 2000 m.
-- @return #boolean If true, distance between the two nodes is below threshold.
function ASTAR.DistMax(nodeA, nodeB, distmax)
distmax=distmax or 2000
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
return dist<=distmax
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Heuristic cost functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic cost is given by the 2D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist2D(nodeA, nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
return dist
end
--- Heuristic cost is given by the 3D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist3D(nodeA, nodeB)
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
return dist
end
--- Heuristic cost is given by the distance between the nodes on road.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.DistRoad(nodeA, nodeB)
-- Get the path.
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
local dist=0
for i=2,#path do
local b=path[i] --DCS#Vec2
local a=path[i-1] --DCS#Vec2
dist=dist+UTILS.VecDist2D(a,b)
end
return dist
end
return math.huge
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find the closest node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate.
-- @return #ASTAR.Node Cloest node to the coordinate.
-- @return #number Distance to closest node in meters.
function ASTAR:FindClosestNode(Coordinate)
local distMin=math.huge
local closeNode=nil
for _,_node in pairs(self.nodes) do
local node=_node --#ASTAR.Node
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
distMin=dist
closeNode=node
end
end
return closeNode, distMin
end
--- Find the start node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindStartNode()
local node, dist=self:FindClosestNode(self.startCoord)
self.startNode=node
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
end
return self
end
--- Add a node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindEndNode()
local node, dist=self:FindClosestNode(self.endCoord)
self.endNode=node
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main A* pathfinding function
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
-- @param #ASTAR self
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
-- @return #table Table of nodes from start to finish.
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
-- Sets.
local openset = {}
local closedset = {}
local came_from = {}
local g_score = {}
local f_score = {}
openset[start.id]=true
local Nopen=1
-- Initial scores.
g_score[start.id]=0
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
-- Set start time.
local T0=timer.getAbsTime()
-- Debug message.
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
self:T(self.lid..text)
local Tstart=UTILS.GetOSTime()
-- Loop while we still have an open set.
while Nopen > 0 do
-- Get current node.
local current=self:_LowestFscore(openset, f_score)
-- Check if we are at the end node.
if current.id==goal.id then
local path=self:_UnwindPath({}, came_from, goal)
if not ExcludeEndNode then
table.insert(path, goal)
end
if ExcludeStartNode then
table.remove(path, 1)
end
local Tstop=UTILS.GetOSTime()
local dT=nil
if Tstart and Tstop then
dT=Tstop-Tstart
end
-- Debug message.
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
if dT then
text=text..string.format(", OS Time %.6f sec", dT)
end
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
self:T(self.lid..text)
return path
end
-- Move Node from open to closed set.
openset[current.id]=nil
Nopen=Nopen-1
closedset[current.id]=true
-- Get neighbour nodes.
local neighbors=self:_NeighbourNodes(current, nodes)
-- Loop over neighbours.
for _,neighbor in pairs(neighbors) do
if self:_NotIn(closedset, neighbor.id) then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
came_from[neighbor]=current
g_score[neighbor.id]=tentative_g_score
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
if self:_NotIn(openset, neighbor.id) then
-- Add to open set.
openset[neighbor.id]=true
Nopen=Nopen+1
end
end
end
end
end
-- Debug message.
local text=string.format("WARNING: Could NOT find valid path!")
self:E(self.lid..text)
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
return nil -- no valid path
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- A* pathfinding helper functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number "Cost" to go from node A to node B.
function ASTAR:_HeuristicCost(nodeA, nodeB)
-- Counter.
self.ncost=self.ncost+1
-- Get chached cost if available.
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA, nodeB)
end
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
return cost
end
--- Check if going from a node to a neighbour is possible.
-- @param #ASTAR self
-- @param #ASTAR.Node node A node.
-- @param #ASTAR.Node neighbor Neighbour node.
-- @return #boolean If true, transition between nodes is possible.
function ASTAR:_IsValidNeighbour(node, neighbor)
-- Counter.
self.nvalid=self.nvalid+1
local valid=node.valid[neighbor.id]
if valid~=nil then
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
self.nvalidcache=self.nvalidcache+1
return valid
end
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
else
valid=true
end
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid -- Symmetric problem.
return valid
end
--- Calculate 2D distance between two nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number Distance between nodes in meters.
function ASTAR:_DistNodes(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
--- Function that calculates the lowest F score.
-- @param #ASTAR self
-- @param #table set The set of nodes IDs.
-- @param #number f_score F score.
-- @return #ASTAR.Node Best node.
function ASTAR:_LowestFscore(set, f_score)
local lowest, bestNode = ASTAR.INF, nil
for nid,node in pairs(set) do
local score=f_score[nid]
if score<lowest then
lowest, bestNode = score, nid
end
end
return self.nodes[bestNode]
end
--- Function to get valid neighbours of a node.
-- @param #ASTAR self
-- @param #ASTAR.Node theNode The node.
-- @param #table nodes Possible neighbours.
-- @param #table Valid neighbour nodes.
function ASTAR:_NeighbourNodes(theNode, nodes)
local neighbors = {}
for _,node in pairs(nodes) do
if theNode.id~=node.id then
local isvalid=self:_IsValidNeighbour(theNode, node)
if isvalid then
table.insert(neighbors, node)
end
end
end
return neighbors
end
--- Function to check if a node is not in a set.
-- @param #ASTAR self
-- @param #table set Set of nodes.
-- @param #ASTAR.Node theNode The node to check.
-- @return #boolean If true, the node is not in the set.
function ASTAR:_NotIn(set, theNode)
return set[theNode]==nil
end
--- Unwind path function.
-- @param #ASTAR self
-- @param #table flat_path Flat path.
-- @param #table map Map.
-- @param #ASTAR.Node current_node The current node.
-- @return #table Unwinded path.
function ASTAR:_UnwindPath( flat_path, map, current_node )
if map [current_node] then
table.insert (flat_path, 1, map[current_node])
return self:_UnwindPath(flat_path, map, map[current_node])
else
return flat_path
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -229,7 +229,8 @@ FORMATION = {
-- @param #BASE self
-- @return #BASE
function BASE:New()
local self = routines.utils.deepCopy( self ) -- Create a new self instance
--local self = routines.utils.deepCopy( self ) -- Create a new self instance
local self = UTILS.DeepCopy(self)
_ClassID = _ClassID + 1
self.ClassID = _ClassID
@@ -462,132 +463,153 @@ do -- Event Handling
return self
end
-- Event handling function prototypes
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or auto cannon based weapon, those are handled by EVENT.ShootingStart.
-- Event handling function prototypes - Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventShot
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs whenever an object is hit by a weapon.
-- initiator : The unit object the fired the weapon.
-- weapon: Weapon object that hit the target.
-- target: The Object that was hit.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit object the fired the weapon
-- weapon: Weapon object that hit the target
-- target: The Object that was hit.
-- @function [parent=#BASE] OnEventHit
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft takes off from an airbase, farp, or ship.
-- initiator : The unit that took off.
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that tookoff
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships
-- @function [parent=#BASE] OnEventTakeoff
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft lands at an airbase, farp or ship
-- initiator : The unit that has landed.
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has landed
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships
-- @function [parent=#BASE] OnEventLand
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft crashes into the ground and is completely destroyed.
-- initiator : The unit that has crashed.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has crashed
-- @function [parent=#BASE] OnEventCrash
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a pilot ejects from an aircraft
-- initiator : The unit that has ejected
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has ejected
-- @function [parent=#BASE] OnEventEjection
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft connects with a tanker and begins taking on fuel.
-- initiator : The unit that is receiving fuel.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is receiving fuel.
-- @function [parent=#BASE] OnEventRefueling
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an object is dead.
-- initiator : The unit that is dead.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is dead.
-- @function [parent=#BASE] OnEventDead
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an object is completely destroyed.
-- initiator : The unit that is was destroyed.
--- Occurs when an Event for an object is triggered.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that triggered the event.
-- @function [parent=#BASE] OnEvent
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane.
-- initiator : The unit that the pilot has died in.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that the pilot has died in.
-- @function [parent=#BASE] OnEventPilotDead
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a ground unit captures either an airbase or a farp.
-- initiator : The unit that captured the base.
-- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that captured the base
-- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction.
-- @function [parent=#BASE] OnEventBaseCaptured
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mission starts.
--- Occurs when a mission starts
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventMissionStart
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mission ends.
--- Occurs when a mission ends
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventMissionEnd
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft is finished taking fuel.
-- initiator : The unit that was receiving fuel.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was receiving fuel.
-- @function [parent=#BASE] OnEventRefuelingStop
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any object is spawned into the mission.
-- initiator : The unit that was spawned.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was spawned
-- @function [parent=#BASE] OnEventBirth
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any system fails on a human controlled aircraft.
-- initiator : The unit that had the failure.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that had the failure
-- @function [parent=#BASE] OnEventHumanFailure
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft starts its engines.
-- initiator : The unit that is starting its engines..
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is starting its engines.
-- @function [parent=#BASE] OnEventEngineStartup
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft shuts down its engines.
-- initiator : The unit that is stopping its engines..
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is stopping its engines.
-- @function [parent=#BASE] OnEventEngineShutdown
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any player assumes direct control of a unit.
-- initiator : The unit that is being taken control of.
--- Occurs when any player assumes direct control of a unit. Note - not Mulitplayer safe. Use PlayerEnterAircraft.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is being taken control of.
-- @function [parent=#BASE] OnEventPlayerEnterUnit
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any player relieves control of a unit to the AI.
-- initiator : The unit that the player left.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that the player left.
-- @function [parent=#BASE] OnEventPlayerLeaveUnit
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), auto cannons, and machine guns.
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is doing the shooting.
-- target: The unit that is being targeted.
-- @function [parent=#BASE] OnEventShootingStart
@@ -595,25 +617,29 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event.
-- initiator : The unit that was doing the shooting.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was doing the shooting.
-- @function [parent=#BASE] OnEventShootingEnd
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a new mark was added.
-- MarkID: ID of the mark.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkAdded
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark was removed.
-- MarkID: ID of the mark.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkRemoved
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark text was changed.
-- MarkID: ID of the mark.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkChange
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
@@ -628,13 +654,15 @@ do -- Event Handling
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs.
--
-- * initiator: The unit that killed the target.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: The unit that killed the target
-- * target: Target Object
-- * weapon: Weapon Object
--
@@ -644,12 +672,14 @@ do -- Event Handling
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when the game thinks an object is destroyed.
--
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: The unit that is was destroyed.
--
-- @function [parent=#BASE] OnEventUnitLost
@@ -657,7 +687,8 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs shortly after the landing animation of an ejected pilot touching the ground and standing up. Event does not occur if the pilot lands in the water and sub combs to Davey Jones Locker.
--
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from.
-- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists.
-- * subplace: is always 0 for unknown reasons.
@@ -667,38 +698,45 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Paratrooper landing.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventParatrooperLanding
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Discard chair after ejection.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventDiscardChairAfterEjection
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Weapon add. Fires when entering a mission per pylon with the name of the weapon (double pylons not counted, infinite wep reload not counted.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventParatrooperLanding
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Trigger zone.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventTriggerZone
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Landing quality mark.
--- Landing quality mark.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventLandingQualityMark
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- BDA.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventBDA
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a player enters a slot and takes control of an aircraft.
-- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event.
-- initiator : The unit that is being taken control of.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event.
-- initiator : The unit that is being taken control of.
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
@@ -744,18 +782,35 @@ function BASE:CreateEventCrash( EventTime, Initiator, IniObjectCategory )
world.onEvent( Event )
end
--- Creation of a Crash Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventUnitLost(EventTime, Initiator)
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_UNIT_LOST,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
--- Creation of a Dead Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventDead( EventTime, Initiator )
self:F( { EventTime, Initiator } )
function BASE:CreateEventDead( EventTime, Initiator, IniObjectCategory )
self:F( { EventTime, Initiator, IniObjectCategory } )
local Event = {
id = world.event.S_EVENT_DEAD,
time = EventTime,
initiator = Initiator,
}
IniObjectCategory = IniObjectCategory,
}
world.onEvent( Event )
end
@@ -792,22 +847,21 @@ function BASE:CreateEventTakeoff( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event.
-- @param #BASE self
-- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered.
function BASE:CreateEventPlayerEnterAircraft( PlayerUnit )
self:F( { PlayerUnit } )
local Event = {
id = EVENTS.PlayerEnterAircraft,
time = timer.getTime(),
initiator = PlayerUnit:GetDCSObject(),
}
world.onEvent( Event )
end
-- TODO: Complete DCS#Event structure.
--- Creation of a `S_EVENT_PLAYER_ENTER_AIRCRAFT` event.
-- @param #BASE self
-- @param Wrapper.Unit#UNIT PlayerUnit The aircraft unit the player entered.
function BASE:CreateEventPlayerEnterAircraft( PlayerUnit )
self:F( { PlayerUnit } )
local Event = {
id = EVENTS.PlayerEnterAircraft,
time = timer.getTime(),
initiator = PlayerUnit:GetDCSObject()
}
world.onEvent(Event)
end
--- The main event handling function... This function captures all events generated for the class.
-- @param #BASE self
-- @param DCS#Event event
@@ -846,32 +900,38 @@ do -- Scheduling
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
-- Object name.
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
-- Debug info.
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
nil,
nil,
nil
)
self._.Schedules[#self._.Schedules + 1] = ScheduleID
-- FF this was wrong!
--[[
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
{ ... },
Start,
nil,
nil,
nil
)
]]
-- NOTE: MasterObject (first parameter) needs to be nil or it will be the first argument passed to the SchedulerFunction!
local ScheduleID = self.Scheduler:Schedule(nil, SchedulerFunction, {...}, Start)
self._.Schedules[#self._.Schedules+1] = ScheduleID
return self._.Schedules[#self._.Schedules]
end
@@ -883,7 +943,7 @@ do -- Scheduling
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
@@ -896,32 +956,33 @@ do -- Scheduling
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
-- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction!
local ScheduleID = self.Scheduler:Schedule(
self,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop,
4
)
self._.Schedules[#self._.Schedules + 1] = ScheduleID
nil,
SchedulerFunction,
{ ... },
Start,
Repeat,
RandomizeFactor,
Stop,
4
)
self._.Schedules[#self._.Schedules+1] = ScheduleID
return self._.Schedules[#self._.Schedules]
end
--- Stops the Schedule.
-- @param #BASE self
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
function BASE:ScheduleStop( SchedulerFunction )
-- @param #string SchedulerID (Optional) Scheduler ID to be stopped. If nil, all pending schedules are stopped.
function BASE:ScheduleStop( SchedulerID )
self:F3( { "ScheduleStop:" } )
if self.Scheduler then
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
--_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
_SCHEDULEDISPATCHER:Stop(self.Scheduler, SchedulerID)
end
end
@@ -1062,7 +1123,7 @@ end
--- Set tracing for a class
-- @param #BASE self
-- @param #string Class
-- @param #string Class Class name.
function BASE:TraceClass( Class )
_TraceClass[Class] = true
_TraceClassMethod[Class] = {}
@@ -1071,8 +1132,8 @@ end
--- Set tracing for a specific method of class
-- @param #BASE self
-- @param #string Class
-- @param #string Method
-- @param #string Class Class name.
-- @param #string Method Method.
function BASE:TraceClassMethod( Class, Method )
if not _TraceClassMethod[Class] then
_TraceClassMethod[Class] = {}

View File

@@ -17,7 +17,7 @@
--
-- After attaching a @{#BEACON} to your @{Wrapper.Positionable#POSITIONABLE}, you need to select the right function to activate the kind of beacon you want.
-- There are two types of BEACONs available : the (aircraft) TACAN Beacon and the general purpose Radio Beacon.
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very useful to simulate the battery time if your BEACON is
-- Note that in both case, you can set an optional parameter : the `BeaconDuration`. This can be very usefull to simulate the battery time if your BEACON is
-- attach to a cargo crate, for exemple.
--
-- ## Aircraft TACAN Beacon usage
@@ -265,13 +265,12 @@ function BEACON:ActivateLink4(Frequency, Morse, Duration)
-- Stop sheduler
if Duration then -- Schedule the stop of the BEACON if asked by the MD
self.Positionable:DeactivateLink4(Duration)
self.Positionable:CommandDeactivateLink4(Duration)
end
return self
end
--- DEPRECATED: Please use @{BEACON:ActivateTACAN}() instead.
-- Activates a TACAN BEACON on an Aircraft.
-- @param #BEACON self
@@ -430,7 +429,7 @@ function BEACON:RadioBeacon(FileName, Frequency, Modulation, Power, BeaconDurati
end
end
--- Stop the Radio Beacon
--- Stops the Radio Beacon
-- @param #BEACON self
-- @return #BEACON self
function BEACON:StopRadioBeacon()

View File

@@ -0,0 +1,295 @@
--- **Core** - Define any or all conditions to be evaluated.
--
-- **Main Features:**
--
-- * Add arbitrary numbers of conditon functions
-- * Evaluate *any* or *all* conditions
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Condition
-- @image Core_Conditon.png
--- CONDITON class.
-- @type CONDITION
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #boolean isAny General functions are evaluated as any condition.
-- @field #boolean negateResult Negeate result of evaluation.
-- @field #table functionsGen General condition functions.
-- @field #table functionsAny Any condition functions.
-- @field #table functionsAll All condition functions.
--
-- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* - William Shakespeare
--
-- ===
--
-- # The CONDITION Concept
--
--
--
-- @field #CONDITION
CONDITION = {
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
}
--- Condition function.
-- @type CONDITION.Function
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
--- CONDITION class version.
-- @field #string version
CONDITION.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Make FSM.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new CONDITION object.
-- @param #CONDITION self
-- @param #string Name (Optional) Name used in the logs.
-- @return #CONDITION self
function CONDITION:New(Name)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
self.name=Name or "Condition X"
self.lid=string.format("%s | ", self.name)
return self
end
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
-- @param #CONDITION self
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
-- @return #CONDITION self
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
--- Negate result.
-- @param #CONDITION self
-- @param #boolean Negate If `true`, result is negated else not.
-- @return #CONDITION self
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
--
-- @usage
-- local function isAequalB(a, b)
-- return a==b
-- end
--
-- myCondition:AddFunction(isAequalB, a, b)
--
-- @return #CONDITION self
function CONDITION:AddFunction(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsGen, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAny(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAny, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAll(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAll, condition)
return self
end
--- Evaluate conditon functions.
-- @param #CONDITION self
-- @param #boolean AnyTrue If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
-- @return #boolean Result of condition functions.
function CONDITION:Evaluate(AnyTrue)
-- Check if at least one function was given.
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
if self.negateResult then
return true
else
return false
end
end
-- Any condition for gen.
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
-- Is any?
local isAny=self:_EvalConditionsAny(self.functionsAny)
-- Is all?
local isAll=self:_EvalConditionsAll(self.functionsAll)
-- Result.
local result=isGen and isAny and isAll
-- Negate result.
if self.negateResult then
result=not result
end
-- Debug message.
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result)))
return result
end
--- Check if all given condition are true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
function CONDITION:_EvalConditionsAll(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any false will return false.
if not istrue then
return false
end
end
-- All conditions were true.
return true
end
--- Check if any of the given conditions is true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
function CONDITION:_EvalConditionsAny(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any true will return true.
if istrue then
return true
end
end
-- No condition was true.
if gotone then
return false
else
-- No functions passed.
return true
end
end
--- Create conditon function object.
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION.Function Condition function.
function CONDITION:_CreateCondition(Function, ...)
local condition={} --#CONDITION.Function
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -405,8 +405,8 @@ do -- FSM
Transition.To = To
-- Debug message.
self:T2( Transition )
--self:T3( Transition )
self._Transitions[Transition] = Transition
self:_eventmap( self.Events, Transition )
end
@@ -426,8 +426,8 @@ do -- FSM
-- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM.
-- @return Core.Fsm#FSM_PROCESS The SubFSM.
function FSM:AddProcess( From, Event, Process, ReturnEvents )
self:T( { From, Event } )
--self:T3( { From, Event } )
local Sub = {}
Sub.From = From
Sub.Event = Event
@@ -524,9 +524,9 @@ do -- FSM
Process._Scores[State] = Process._Scores[State] or {}
Process._Scores[State].ScoreText = ScoreText
Process._Scores[State].Score = Score
self:T( Process._Scores )
--self:T3( Process._Scores )
return Process
end
@@ -560,19 +560,19 @@ do -- FSM
-- @param #table Events Events.
-- @param #table EventStructure Event structure.
function FSM:_eventmap( Events, EventStructure )
local Event = EventStructure.Event
local __Event = "__" .. EventStructure.Event
self[Event] = self[Event] or self:_create_transition( Event )
self[__Event] = self[__Event] or self:_delayed_transition( Event )
-- Debug message.
self:T2( "Added methods: " .. Event .. ", " .. __Event )
Events[Event] = self.Events[Event] or { map = {} }
self:_add_to_map( Events[Event].map, EventStructure )
local Event = EventStructure.Event
local __Event = "__" .. EventStructure.Event
self[Event] = self[Event] or self:_create_transition(Event)
self[__Event] = self[__Event] or self:_delayed_transition(Event)
-- Debug message.
--self:T3( "Added methods: " .. Event .. ", " .. __Event )
Events[Event] = self.Events[Event] or { map = {} }
self:_add_to_map( Events[Event].map, EventStructure )
end
--- Sub maps.
@@ -784,8 +784,8 @@ do -- FSM
return function( self, DelaySeconds, ... )
-- Debug.
self:T2( "Delayed Event: " .. EventName )
self:T3( "Delayed Event: " .. EventName )
local CallID = 0
if DelaySeconds ~= nil then
@@ -802,23 +802,23 @@ do -- FSM
self._EventSchedules[EventName] = CallID
-- Debug output.
self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) )
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
else
self:T2( string.format( "NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds ) )
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
-- reschedule
end
else
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
self:T2( string.format( "Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring( CallID ) ) )
self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
end
else
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
end
-- Debug.
self:T2( { CallID = CallID } )
--self:T3( { CallID = CallID } )
end
end
@@ -841,7 +841,7 @@ do -- FSM
function FSM:_gosub( ParentFrom, ParentEvent )
local fsmtable = {}
if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then
self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
--self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
return self.subs[ParentFrom][ParentEvent]
else
return {}
@@ -887,8 +887,8 @@ do -- FSM
Map[From] = Event.To
end
end
self:T3( { Map, Event } )
--self:T3( { Map, Event } )
end
--- Get current state.
@@ -908,7 +908,7 @@ do -- FSM
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
-- @return #boolean If true, FSM is in this state.
function FSM:Is( State )
return self.current == State
end
@@ -916,8 +916,8 @@ do -- FSM
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
function FSM:is( state )
-- @return #boolean If true, FSM is in this state.
function FSM:is(state)
return self.current == state
end
@@ -1146,7 +1146,7 @@ do -- FSM_PROCESS
-- @param #FSM_PROCESS self
-- @return #FSM_PROCESS
function FSM_PROCESS:Copy( Controllable, Task )
self:T( { self:GetClassNameAndID() } )
--self:T3( { self:GetClassNameAndID() } )
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
@@ -1171,13 +1171,13 @@ do -- FSM_PROCESS
-- Copy End States
for EndStateID, EndState in pairs( self:GetEndStates() ) do
self:T( EndState )
--self:T3( EndState )
NewFsm:AddEndState( EndState )
end
-- Copy the score tables
for ScoreID, Score in pairs( self:GetScores() ) do
self:T( Score )
--self:T3( Score )
NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score )
end
@@ -1422,7 +1422,7 @@ do -- FSM_SET
-- @param #FSM_SET self
-- @return Core.Set#SET_BASE
function FSM_SET:Get()
return self.Controllable
return self.Set
end
function FSM_SET:_call_handler( step, trigger, params, EventName )

View File

@@ -0,0 +1,267 @@
--- **Core** - MarkerOps_Base.
--
-- **Main Features:**
--
-- * 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.
--
-- ===
--
-- ### Author: **Applevangelist**
--
-- Date: 5 May 2021
-- Last Update: Sep 2022
--
-- ===
---
-- @module Core.MarkerOps_Base
-- @image MOOSE_Core.JPG
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition.
--------------------------------------------------------------------------
--- MARKEROPS_BASE class.
-- @type MARKEROPS_BASE
-- @field #string ClassName Name of the class.
-- @field #string Tag Tag to identify commands.
-- @field #table Keywords Table of keywords to recognize.
-- @field #string version Version of #MARKEROPS_BASE.
-- @field #boolean Casesensitive Enforce case when identifying the Tag, i.e. tag ~= Tag
-- @extends Core.Fsm#FSM
--- *Fiat lux.* -- Latin proverb.
--
-- ===
--
-- # The MARKEROPS_BASE Concept
--
-- This class enable scripting text-based actions from markers.
--
-- @field #MARKEROPS_BASE
MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.1.0",
debug = false,
Casesensitive = true,
}
--- Function to instantiate a new #MARKEROPS_BASE object.
-- @param #MARKEROPS_BASE self
-- @param #string Tagname Name to identify us from the event text.
-- @param #table Keywords Table of keywords recognized from the event text.
-- @param #boolean Casesensitive (Optional) Switch case sensitive identification of Tagname. Defaults to true.
-- @return #MARKEROPS_BASE self
function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- Inherit FSM
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
-- Set some string id for output to DCS.log file.
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
self.Tag = Tagname or "mytag"-- #string
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
self.debug = false
self.Casesensitive = true
if Casesensitive and Casesensitive == false then
self.Casesensitive = false
end
-----------------------
--- FSM Transitions ---
-----------------------
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
-- start
self:I(self.lid..string.format("started for %s",self.Tag))
self:__Start(1)
return self
-------------------
-- PSEUDO Functions
-------------------
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
--- "Stop" trigger. Used to stop the function an unhandle events
-- @function [parent=#MARKEROPS_BASE] Stop
end
--- (internal) Handle events.
-- @param #MARKEROPS_BASE self
-- @param Core.Event#EVENTDATA Event
function MARKEROPS_BASE:OnEventMark(Event)
self:T({Event})
if Event == nil or Event.idx == nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
--position
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
-- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
-- Handle event
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
-- Handle event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
-- Hande event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
self:MarkDeleted()
end
end
end
end
--- (internal) Match tag.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #boolean
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches = false
if not self.Casesensitive then
local type = string.lower(self.Tag) -- #string
if string.find(string.lower(Eventtext),type) then
matches = true --event text contains tag
end
else
local type = self.Tag -- #string
if string.find(Eventtext,type) then
matches = true --event text contains tag
end
end
return matches
end
--- (internal) Match keywords table.
-- @param #MARKEROPS_BASE self
-- @param #string Eventtext Text added to the marker.
-- @return #table
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
local matchtable = {}
local keytable = self.Keywords
for _index,_word in pairs (keytable) do
if string.find(string.lower(Eventtext),string.lower(_word))then
table.insert(matchtable,_word)
end
end
return matchtable
end
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
self:T({self.lid,From,Event,To})
end
--- On enter "Stopped" event. Unsubscribe events.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:T({self.lid,From,Event,To})
-- unsubscribe from events
self:UnHandleEvent(EVENTS.MarkAdded)
self:UnHandleEvent(EVENTS.MarkChange)
self:UnHandleEvent(EVENTS.MarkRemoved)
end
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition End.
--------------------------------------------------------------------------

View File

@@ -239,6 +239,7 @@ do -- MENU_BASE
function MENU_BASE:GetMenu( MenuText )
return self.Menus[MenuText]
end
--- Sets a menu stamp for later prevention of menu removal.
-- @param #MENU_BASE self
-- @param MenuStamp
@@ -376,6 +377,7 @@ do -- MENU_MISSION
end
end
--- Refreshes a radio item for a mission
-- @param #MENU_MISSION self
-- @return #MENU_MISSION
@@ -813,6 +815,7 @@ do
end
end
--- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP self
-- @return #MENU_GROUP
@@ -829,6 +832,29 @@ do
return self
end
--- Refreshes a new radio item for a group and submenus, ordering by (numerical) MenuTag
-- @param #MENU_GROUP self
-- @return #MENU_GROUP
function MENU_GROUP:RefreshAndOrderByTag()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
local MenuTable = {}
for MenuText, Menu in pairs( self.Menus or {} ) do
local tag = Menu.MenuTag or math.random(1,10000)
MenuTable[#MenuTable+1] = {Tag=tag, Enty=Menu}
end
table.sort(MenuTable, function (k1, k2) return k1.tag < k2.tag end )
for _, Menu in pairs( MenuTable ) do
Menu.Entry:Refresh()
end
end
return self
end
--- Removes the sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuStamp
@@ -1180,4 +1206,3 @@ do
return self
end
end

View File

@@ -11,8 +11,7 @@
-- * Send messages to a coalition.
-- * Send messages to a specific group.
-- * Send messages to a specific unit or client.
--
--
--
-- ===
--
-- @module Core.Message
@@ -205,14 +204,14 @@ function MESSAGE:ToClient( Client, Settings )
local Unit = Client:GetClient()
if self.MessageDuration ~= 0 then
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
end
end
return self
local ClientGroupID = Client:GetClientGroupID()
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
--trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen)
end
end
return self
end
--- Sends a MESSAGE to a Group.
@@ -263,6 +262,30 @@ function MESSAGE:ToUnit( Unit, Settings )
return self
end
--- Sends a MESSAGE to a Unit.
-- @param #MESSAGE self
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
-- @return #MESSAGE Message object.
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
if Unit then
if self.MessageType then
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
end
return self
end
--- Sends a MESSAGE to the Blue coalition.
-- @param #MESSAGE self
-- @return #MESSAGE

View File

@@ -825,7 +825,11 @@ do -- COORDINATE
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
-- @return DCS#Vec3 DirectionVec3 The direction vector in Vec3 format.
function COORDINATE:GetDirectionVec3( TargetCoordinate )
return { x = TargetCoordinate.x - self.x, y = TargetCoordinate.y - self.y, z = TargetCoordinate.z - self.z }
if TargetCoordinate then
return { x = TargetCoordinate.x - self.x, y = TargetCoordinate.y - self.y, z = TargetCoordinate.z - self.z }
else
return { x=0,y=0,z=0}
end
end
@@ -874,6 +878,11 @@ do -- COORDINATE
-- Get the vector from A to B
local vec=UTILS.VecSubstract(ToCoordinate, self)
if f>1 then
local norm=UTILS.VecNorm(vec)
f=Fraction/norm
end
-- Scale the vector.
vec.x=f*vec.x
@@ -883,7 +892,9 @@ do -- COORDINATE
-- Move the vector to start at the end of A.
vec=UTILS.VecAdd(self, vec)
-- Create a new coordiante object.
local coord=COORDINATE:New(vec.x,vec.y,vec.z)
return coord
end
@@ -2267,7 +2278,7 @@ do -- COORDINATE
end
--- Creates a free form shape on the F10 map. The first point is the current COORDINATE. The remaining points need to be specified.
-- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 10 points** in total are supported.
-- **NOTE**: A free form polygon must have **at least three points** in total and currently only **up to 15 points** in total are supported.
-- @param #COORDINATE self
-- @param #table Coordinates Table of coordinates of the remaining points of the shape.
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
@@ -2320,8 +2331,28 @@ do -- COORDINATE
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==10 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10], Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==11 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10],
vecs[11],
Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==12 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10],
vecs[11], vecs[12],
Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==13 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10],
vecs[11], vecs[12], vecs[13],
Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==14 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10],
vecs[11], vecs[12], vecs[13], vecs[14],
Color, FillColor, LineType, ReadOnly, Text or "")
elseif #vecs==15 then
trigger.action.markupToAll(7, Coalition, MarkID, vecs[1], vecs[2], vecs[3], vecs[4], vecs[5], vecs[6], vecs[7], vecs[8], vecs[9], vecs[10],
vecs[11], vecs[12], vecs[13], vecs[14], vecs[15],
Color, FillColor, LineType, ReadOnly, Text or "")
else
self:E("ERROR: Currently a free form polygon can only have 10 points in total!")
self:E("ERROR: Currently a free form polygon can only have 15 points in total!")
-- Unfortunately, unpack(vecs) does not work! So no idea how to generalize this :(
trigger.action.markupToAll(7, Coalition, MarkID, unpack(vecs), Color, FillColor, LineType, ReadOnly, Text or "")
end
@@ -2751,7 +2782,7 @@ do -- COORDINATE
return "BR, " .. self:GetBRText( AngleRadians, Distance, Settings )
end
--- Return a BRAA string from a COORDINATE to the COORDINATE.
--- Return a BRA string from a COORDINATE to the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from.
-- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object.

View File

@@ -109,9 +109,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then
--env.info("FF Object Scheduler")
self.ObjectSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, ObjectScheduler = tostring( self.ObjectSchedulers[CallID] ), MasterObject = tostring( Scheduler.MasterObject ) } )
else
--env.info("FF Persistent Scheduler")
self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
end
@@ -121,8 +123,8 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.Schedule[Scheduler][CallID] = {} -- #SCHEDULEDISPATCHER.ScheduleData
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + (Start or 0)
self.Schedule[Scheduler][CallID].Start = Start + 0.1
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + 0.001
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
@@ -217,7 +219,8 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if ShowTrace then
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
else
@@ -314,7 +317,7 @@ end
--- Stop dispatcher.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID Call ID.
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
self:F2( { Stop = CallID, Scheduler = Scheduler } )

View File

@@ -1,7 +1,7 @@
--- **Core** - Prepares and handles the execution of functions over scheduled time (intervals).
--
-- ===
--
--
-- ## Features:
--
-- * Schedule functions over time,
@@ -13,9 +13,9 @@
-- ===
--
-- # Demo Missions
--
--
-- ### [SCHEDULER Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
--
--
-- ### [SCHEDULER Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SCH%20-%20Scheduler)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
@@ -237,7 +237,7 @@ end
-- @param #number Stop Time interval in seconds after which the scheduler will be stopped.
-- @param #number TraceLevel Trace level [0,3]. Default 3.
-- @param Core.Fsm#FSM Fsm Finite state model.
-- @return #table The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
self:T3( { SchedulerArguments } )
@@ -271,7 +271,7 @@ end
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
function SCHEDULER:Start( ScheduleID )
self:F3( { ScheduleID } )
self:T( string.format( "Starting scheduler ID=%s", tostring( ScheduleID ) ) )

File diff suppressed because it is too large Load Diff

View File

@@ -251,6 +251,7 @@ do -- SETTINGS
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 )
self:SetEraModern()
self:SetLocale("en")
return self
else
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@@ -282,7 +283,21 @@ do -- SETTINGS
function SETTINGS:SetMetric()
self.Metric = true
end
--- Sets the SETTINGS default text locale.
-- @param #SETTINGS self
-- @param #string Locale
function SETTINGS:SetLocale(Locale)
self.Locale = Locale or "en"
end
--- Gets the SETTINGS text locale.
-- @param #SETTINGS self
-- @return #string
function SETTINGS:GetLocale()
return self.Locale or _SETTINGS:GetLocale()
end
--- Gets if the SETTINGS is metric.
-- @param #SETTINGS self
-- @return #boolean true if metric.
@@ -300,7 +315,7 @@ do -- SETTINGS
-- @param #SETTINGS self
-- @return #boolean true if imperial.
function SETTINGS:IsImperial()
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsMetric())
return (self.Metric ~= nil and self.Metric == false) or (self.Metric == nil and _SETTINGS:IsImperial())
end
--- Sets the SETTINGS LL accuracy.

View File

@@ -29,9 +29,9 @@
-- * Enquiry methods to check on spawn status.
--
-- ===
--
--
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SPA%20-%20Spawning)
--
--
-- ===
--
-- ### [YouTube Playlist](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
@@ -1418,7 +1418,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
end
-- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
-- if self.Repeat then
-- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
-- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
-- end
end
@@ -2403,8 +2403,7 @@ end
-- @param #SPAWN self
-- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
@@ -2472,8 +2471,7 @@ end
-- @param #SPAWN self
-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
@@ -2487,8 +2485,7 @@ end
-- @param #SPAWN self
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground.
@@ -2511,8 +2508,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2()
@@ -2544,8 +2540,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
@@ -2599,8 +2594,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnStatic = STATIC:FindByName( StaticName )
@@ -2631,8 +2625,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil when nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnZone = ZONE:New( ZoneName )
@@ -2978,9 +2971,9 @@ end
function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
-- if not self.SpawnTemplate then
-- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
-- end
-- if not self.SpawnTemplate then
-- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
-- end
local SpawnTemplate
if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then
@@ -3372,12 +3365,16 @@ end
-- @return #boolean True = Continue Scheduler
function SPAWN:_SpawnCleanUpScheduler()
self:F( { "CleanUp Scheduler:", self.SpawnTemplatePrefix } )
local SpawnGroup, SpawnCursor = self:GetFirstAliveGroup()
self:T( { "CleanUp Scheduler:", SpawnGroup, SpawnCursor } )
local IsHelo = false
while SpawnGroup do
IsHelo = SpawnGroup:IsHelicopter()
local SpawnUnits = SpawnGroup:GetUnits()
for UnitID, UnitData in pairs( SpawnUnits ) do
@@ -3390,8 +3387,8 @@ function SPAWN:_SpawnCleanUpScheduler()
self:T( { SpawnUnitName, Stamp } )
if Stamp.Vec2 then
if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then
local NewVec2 = SpawnUnit:GetVec2()
if (SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1) or IsHelo then
local NewVec2 = SpawnUnit:GetVec2() or {x=0, y=0}
if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then
-- If the plane is not moving or dead , and is on the ground, assign it with a timestamp...
if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then
@@ -3409,8 +3406,8 @@ function SPAWN:_SpawnCleanUpScheduler()
Stamp.Time = nil
end
else
if SpawnUnit:InAir() == false then
Stamp.Vec2 = SpawnUnit:GetVec2()
if SpawnUnit:InAir() == false or (IsHelo and SpawnUnit:GetLife() <= 1) then
Stamp.Vec2 = SpawnUnit:GetVec2() or {x=0, y=0}
if (SpawnUnit:GetVelocityKMH() < 1) then
Stamp.Time = timer.getTime()
end

View File

@@ -0,0 +1,202 @@
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
--
-- ===
--
-- ## Main Features:
--
-- * A GetText for Moose
-- * Build a set of localized text entries, alongside their sounds and subtitles
-- * Aimed at class developers to offer localizable language support
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
--
-- ===
--
-- ### Author: **applevangelist**
-- ## Date: April 2022
--
-- ===
--
-- @module Core.TextAndSound
-- @image MOOSE.JPG
--- Text and Sound class.
-- @type TEXTANDSOUND
-- @field #string ClassName Name of this class.
-- @field #string version Versioning.
-- @field #string lid LID for log entries.
-- @field #string locale Default locale of this object.
-- @field #table entries Table of entries.
-- @field #string textclass Name of the class the texts belong to.
-- @extends Core.Base#BASE
---
--
-- @field #TEXTANDSOUND
TEXTANDSOUND = {
ClassName = "TEXTANDSOUND",
version = "0.0.1",
lid = "",
locale = "en",
entries = {},
textclass = "",
}
--- Text and Sound entry.
-- @type TEXTANDSOUND.Entry
-- @field #string Classname Name of the class this entry is for.
-- @field #string Locale Locale of this entry, defaults to "en".
-- @field #table Data The list of entries.
--- Text and Sound data
-- @type TEXTANDSOUND.Data
-- @field #string ID ID of this entry for retrieval.
-- @field #string Text Text of this entry.
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
-- @field #number Soundlength (optional) Length of the sound file in seconds.
-- @field #string Subtitle (optional) Subtitle for the sound file.
--- Instantiate a new object
-- @param #TEXTANDSOUND self
-- @param #string ClassName Name of the class this instance is providing texts for.
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:New(ClassName,Defaultlocale)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
self.textclass = ClassName or "none"
self.entries = {}
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = ClassName
initentry.Data = {}
initentry.Locale = self.locale
self.entries[self.locale] = initentry
self:I(self.lid .. "Instantiated.")
self:T({self.entries[self.locale]})
return self
end
--- Add an entry
-- @param #TEXTANDSOUND self
-- @param #string Locale Locale to set for this entry, e.g. "de".
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
-- @param #string Text Text for this entry.
-- @param #string Soundfile (Optional) Sound file name for this entry.
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
self:T(self.lid .. "AddEntry")
local locale = Locale or self.locale
local dataentry = {} -- #TEXTANDSOUND.Data
dataentry.ID = ID or "1"
dataentry.Text = Text or "none"
dataentry.Soundfile = Soundfile
dataentry.Soundlength = Soundlength or 0
dataentry.Subtitle = Subtitle
if not self.entries[locale] then
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = self.textclass -- class name entry
initentry.Data = {} -- data array
initentry.Locale = locale -- default locale
self.entries[locale] = initentry
end
self.entries[locale].Data[ID] = dataentry
self:T({self.entries[locale].Data})
return self
end
--- Get an entry
-- @param #TEXTANDSOUND self
-- @param #string ID The unique ID of the data to be retrieved.
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
-- @return #string Text Text or nil if not found and no fallback.
-- @return #string Soundfile Filename or nil if not found and no fallback.
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
function TEXTANDSOUND:GetEntry(ID,Locale)
self:T(self.lid .. "GetEntry")
local locale = Locale or self.locale
if not self.entries[locale] then
-- fall back to default "en"
locale = self.locale
end
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
if self.entries[locale] then
if self.entries[locale].Data then
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
if data then
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
elseif self.entries[self.locale].Data[ID] then
-- no matching entry, try default
local data = self.entries[self.locale].Data[ID]
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
end
end
else
return nil, nil, 0, nil
end
return Text,Soundfile,Soundlength,Subtitle
end
--- Get the default locale of this object
-- @param #TEXTANDSOUND self
-- @return #string locale
function TEXTANDSOUND:GetDefaultLocale()
self:T(self.lid .. "GetDefaultLocale")
return self.locale
end
--- Set default locale of this object
-- @param #TEXTANDSOUND self
-- @param #string locale
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:SetDefaultLocale(locale)
self:T(self.lid .. "SetDefaultLocale")
self.locale = locale or "en"
return self
end
--- Check if a locale exists
-- @param #TEXTANDSOUND self
-- @return #boolean outcome
function TEXTANDSOUND:HasLocale(Locale)
self:T(self.lid .. "HasLocale")
return self.entries[Locale] and true or false
end
--- Flush all entries to the log
-- @param #TEXTANDSOUND self
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:FlushToLog()
self:I(self.lid .. "Flushing entries:")
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
for _,_entry in pairs(self.entries) do
local entry = _entry -- #TEXTANDSOUND.Entry
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
self:I(text)
for _ID,_data in pairs(entry.Data) do
local data = _data -- #TEXTANDSOUND.Data
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
self:I(text)
end
end
return self
end
----------------------------------------------------------------
-- End TextAndSound
----------------------------------------------------------------

View File

@@ -34,8 +34,6 @@
--
-- ===
--
-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg)
--
-- # The TIMER Concept
--
-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
@@ -107,19 +105,17 @@ TIMER = {
--- Timer ID.
_TIMERID=0
--- Timer data base.
--_TIMERDB={}
--- TIMER class version.
-- @field #string version
TIMER.version="0.1.1"
TIMER.version="0.1.2"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-- TODO: Write docs.
-- TODO: Randomization.
-- TODO: Pause/unpause.
-- DONE: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -156,9 +152,6 @@ function TIMER:New(Function, ...)
-- Log id.
self.lid=string.format("TIMER UID=%d | ", self.uid)
-- Add to DB.
--_TIMERDB[self.uid]=self
return self
end
@@ -174,7 +167,7 @@ function TIMER:Start(Tstart, dT, Duration)
local Tnow=timer.getTime()
-- Start time in sec.
self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil
self.Tstart=Tstart and Tnow+math.max(Tstart, 0.001) or Tnow+0.001 -- one millisecond delay if Tstart=nil
-- Set time interval.
self.dT=dT
@@ -219,10 +212,7 @@ function TIMER:Stop(Delay)
-- Not running any more.
self.isrunning=false
-- Remove DB entry.
--_TIMERDB[self.uid]=nil
end
end
@@ -239,6 +229,15 @@ function TIMER:SetMaxFunctionCalls(Nmax)
return self
end
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
-- @param #TIMER self
-- @param #number dT Time interval in seconds.
-- @return #TIMER self
function TIMER:SetTimeInterval(dT)
self.dT=dT
return self
end
--- Check if the timer has been started and was not stopped.
-- @param #TIMER self
-- @return #boolean If `true`, the timer is running.

View File

@@ -59,7 +59,11 @@
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
-- @field #table FillColor Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
-- @field #number drawCoalition Draw coalition.
-- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID!
-- @field #table Table of any trigger zone properties from the ME. The key is the Name of the property, and the value is the property's Value.
-- @field #number Surface Type of surface. Only determined at the center of the zone!
-- @extends Core.Fsm#FSM
@@ -103,6 +107,11 @@
-- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color.
-- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color.
--
-- ## A zone might have additional Properties created in the DCS Mission Editor, which can be accessed:
--
-- *@{#ZONE_BASE.GetProperty}(): Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
-- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table.
--
-- @field #ZONE_BASE
ZONE_BASE = {
ClassName = "ZONE_BASE",
@@ -111,6 +120,8 @@ ZONE_BASE = {
DrawID=nil,
Color={},
ZoneID=nil,
Properties={},
Sureface=nil,
}
@@ -336,18 +347,41 @@ end
-- @param #ZONE_BASE self
-- @return #nil The bounding square.
function ZONE_BASE:GetBoundingSquare()
--return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 }
return nil
end
--- Get surface type of the zone.
-- @param #ZONE_BASE self
-- @return DCS#SurfaceType Type of surface.
function ZONE_BASE:GetSurfaceType()
local coord=self:GetCoordinate()
local surface=coord:GetSurfaceType()
return surface
end
--- Bound the zone boundaries with a tires.
-- @param #ZONE_BASE self
function ZONE_BASE:BoundZone()
self:F2()
end
--- Set draw coalition of zone.
-- @param #ZONE_BASE self
-- @param #number Coalition Coalition. Default -1.
-- @return #ZONE_BASE self
function ZONE_BASE:SetDrawCoalition(Coalition)
self.drawCoalition=Coalition or -1
return self
end
--- Get draw coalition of zone.
-- @param #ZONE_BASE self
-- @return #number Draw coaliton.
function ZONE_BASE:GetDrawCoalition()
return self.drawCoalition or -1
end
--- Set color of zone.
-- @param #ZONE_BASE self
-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`.
@@ -371,7 +405,7 @@ end
-- @param #ZONE_BASE self
-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
function ZONE_BASE:GetColor()
return self.Color
return self.Color or {1, 0, 0, 0.15}
end
--- Get RGB color of zone.
@@ -379,9 +413,10 @@ end
-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code.
function ZONE_BASE:GetColorRGB()
local rgb={}
rgb[1]=self.Color[1]
rgb[2]=self.Color[2]
rgb[3]=self.Color[3]
local Color=self:GetColor()
rgb[1]=Color[1]
rgb[2]=Color[2]
rgb[3]=Color[3]
return rgb
end
@@ -389,7 +424,55 @@ end
-- @param #ZONE_BASE self
-- @return #number Alpha value.
function ZONE_BASE:GetColorAlpha()
local alpha=self.Color[4]
local Color=self:GetColor()
local alpha=Color[4]
return alpha
end
--- Set fill color of zone.
-- @param #ZONE_BASE self
-- @param #table RGBcolor RGB color table. Default `{1, 0, 0}`.
-- @param #number Alpha Transparacy between 0 and 1. Default 0.15.
-- @return #ZONE_BASE self
function ZONE_BASE:SetFillColor(RGBcolor, Alpha)
RGBcolor=RGBcolor or {1, 0, 0}
Alpha=Alpha or 0.15
self.FillColor={}
self.FillColor[1]=RGBcolor[1]
self.FillColor[2]=RGBcolor[2]
self.FillColor[3]=RGBcolor[3]
self.FillColor[4]=Alpha
return self
end
--- Get fill color table of the zone.
-- @param #ZONE_BASE self
-- @return #table Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
function ZONE_BASE:GetFillColor()
return self.FillColor or {1, 0, 0, 0.15}
end
--- Get RGB fill color of zone.
-- @param #ZONE_BASE self
-- @return #table Table with three entries, e.g. {1, 0, 0}, which is the RGB color code.
function ZONE_BASE:GetFillColorRGB()
local rgb={}
local FillColor=self:GetFillColor()
rgb[1]=FillColor[1]
rgb[2]=FillColor[2]
rgb[3]=FillColor[3]
return rgb
end
--- Get transperency Alpha fill value of zone.
-- @param #ZONE_BASE self
-- @return #number Alpha value.
function ZONE_BASE:GetFillColorAlpha()
local FillColor=self:GetFillColor()
local alpha=FillColor[4]
return alpha
end
@@ -481,6 +564,26 @@ function ZONE_BASE:GetZoneMaybe()
end
end
-- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
-- @param #ZONE_BASE self
-- @param #string PropertyName The name of a the TriggerZone Property to be retrieved.
-- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent.
-- @usage
--
-- local PropertiesZone = ZONE:FindByName("Properties Zone")
-- local Property = "ExampleProperty"
-- local PropertyValue = PropertiesZone:GetProperty(Property)
--
function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
-- Returns the zone Properties table.
-- @param #ZONE_BASE self
-- @return #table The Key:Value table of TriggerZone properties of the zone.
function ZONE_BASE:GetAllProperties()
return self.Properties
end
--- The ZONE_RADIUS class, defined by a zone name, a location and a radius.
-- @type ZONE_RADIUS
@@ -823,8 +926,8 @@ end
-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}`
-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}`
-- @usage
-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
-- myzone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
-- local IsAttacked = myzone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {}
@@ -894,7 +997,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local SceneryName = ZoneObject:getName()
self.ScanData.Scenery[SceneryType] = self.ScanData.Scenery[SceneryType] or {}
self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject )
self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
self:T( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } )
end
end
@@ -1157,7 +1260,9 @@ end
-- @return #boolean true if the location is within the zone.
function ZONE_RADIUS:IsVec2InZone( Vec2 )
self:F2( Vec2 )
if not Vec2 then return false end
local ZoneVec2 = self:GetVec2()
if ZoneVec2 then
@@ -1175,7 +1280,7 @@ end
-- @return #boolean true if the point is within the zone.
function ZONE_RADIUS:IsVec3InZone( Vec3 )
self:F2( Vec3 )
if not Vec3 then return false end
if not Vec3 then return false end
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
@@ -1189,13 +1294,13 @@ end
-- @return DCS#Vec2 The random location within the zone.
function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
local Vec2 = self:GetVec2()
local _inner = inner or 0
local _outer = outer or self:GetRadius()
local Vec2 = self:GetVec2()
local _inner = inner or 0
local _outer = outer or self:GetRadius()
if surfacetypes and type(surfacetypes)~="table" then
if surfacetypes and type(surfacetypes)~="table" then
surfacetypes={surfacetypes}
end
end
local function _getpoint()
local point = {}
@@ -1215,10 +1320,10 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
return false
end
local point=_getpoint()
local point=_getpoint()
if surfacetypes then
local N=1 ; local Nmax=100 ; local gotit=false
if surfacetypes then
local N=1 ; local Nmax=100 ; local gotit=false
while gotit==false and N<=Nmax do
gotit=_checkSurface(point)
if gotit then
@@ -1230,7 +1335,7 @@ function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
end
end
return point
return point
end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
@@ -1282,8 +1387,8 @@ end
--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @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!
-- @return Core.Point#COORDINATE The random coordinate.
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
@@ -1295,7 +1400,86 @@ function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
return Coordinate
end
--- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building".
-- Does not find statics you might have placed there. **Note** This might be quite CPU intensive, use with care.
-- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0m.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
-- @param #number distance (Optional) Minumum distance from any building coordinate. Defaults to 100m.
-- @param #boolean markbuildings (Optional) Place markers on found buildings (if any).
-- @param #boolean markfinal (Optional) Place marker on the final coordinate (if any).
-- @return Core.Point#COORDINATE The random coordinate or `nil` if cannot be found in 1000 iterations.
function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,markbuildings,markfinal)
local dist = distance or 100
local objects = {}
if self.ScanData and self.ScanData.Scenery then
objects = self:GetScannedScenery()
else
self:Scan({Object.Category.SCENERY})
objects = self:GetScannedScenery()
end
local T0 = timer.getTime()
local T1 = timer.getTime()
local buildings = {}
if self.ScanData and self.ScanData.BuildingCoordinates then
buildings = self.ScanData.BuildingCoordinates
else
-- build table of buildings coordinates
for _,_object in pairs (objects) do
for _,_scen in pairs (_object) do
local scenery = _scen -- Wrapper.Scenery#SCENERY
local description=scenery:GetDesc()
if description and description.attributes and description.attributes.Buildings then
if markbuildings then
MARKER:New(scenery:GetCoordinate(),"Building"):ToAll()
end
buildings[#buildings+1] = scenery:GetCoordinate()
end
end
end
self.ScanData.BuildingCoordinates = buildings
end
-- max 1000 tries
local rcoord = nil
local found = false
local iterations = 0
for i=1,1000 do
iterations = iterations + 1
rcoord = self:GetRandomCoordinate(inner,outer)
found = false
for _,_coord in pairs (buildings) do
local coord = _coord -- Core.Point#COORDINATE
-- keep >50m dist from buildings
if coord:Get2DDistance(rcoord) > dist then
found = true
else
found = false
end
end
if found then
-- we have a winner!
if markfinal then
MARKER:New(rcoord,"FREE"):ToAll()
end
break
end
end
T1=timer.getTime()
self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %d",tostring(found),iterations,T1-T0))
if found then return rcoord else return nil end
end
--- @type ZONE
-- @extends #ZONE_RADIUS
@@ -1371,9 +1555,9 @@ function ZONE:New( ZoneName )
end
--- Find a zone in the _DATABASE using the name of the zone.
-- @param #ZONE_BASE self
-- @param #ZONE self
-- @param #string ZoneName The name of the zone.
-- @return #ZONE_BASE self
-- @return #ZONE self
function ZONE:FindByName( ZoneName )
local ZoneFound = _DATABASE:FindZone( ZoneName )
@@ -1609,7 +1793,7 @@ end
--- @type ZONE_POLYGON_BASE
-- --@field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}.
-- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}.
-- @extends #ZONE_BASE
@@ -1847,7 +2031,7 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound )
end
--- Draw the zone on the F10 map. **NOTE** Currently, only polygons with **exactly four points** are supported!
--- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported!
-- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All.
-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red.
@@ -1859,33 +2043,48 @@ end
-- @return #ZONE_POLYGON_BASE self
function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
if self._.Polygon and #self._.Polygon>=3 then
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1])
Coalition=Coalition or self:GetDrawCoalition()
-- Set draw coalition.
self:SetDrawCoalition(Coalition)
FillColor=FillColor or UTILS.DeepCopy(Color)
FillAlpha=FillAlpha or self:GetColorAlpha()
if #self._.Polygon==4 then
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
else
local Coordinates=self:GetVerticiesCoordinates()
table.remove(Coordinates, 1)
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
-- Set color.
self:SetColor(Color, Alpha)
FillColor=FillColor or self:GetFillColorRGB()
if not FillColor then UTILS.DeepCopy(Color) end
FillAlpha=FillAlpha or self:GetFillColorAlpha()
if not FillAlpha then FillAlpha=0.15 end
-- Set fill color.
self:SetFillColor(FillColor, FillAlpha)
if #self._.Polygon==4 then
local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2])
local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3])
local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4])
self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
else
local Coordinates=self:GetVerticiesCoordinates()
table.remove(Coordinates, 1)
self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
end
end
return self
end
@@ -1966,7 +2165,7 @@ end
-- @return #boolean true if the location is within the zone.
function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
self:F2( Vec2 )
if not Vec2 then return false end
if not Vec2 then return false end
local Next
local Prev
local InPolygon = false
@@ -1998,7 +2197,7 @@ function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
self:F2( Vec3 )
if not Vec3 then return false end
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
@@ -2218,6 +2417,206 @@ function ZONE_POLYGON:FindByName( ZoneName )
return ZoneFound
end
do -- ZONE_ELASTIC
--- @type ZONE_ELASTIC
-- @field #table points Points in 2D.
-- @field #table setGroups Set of GROUPs.
-- @field #table setOpsGroups Set of OPSGROUPS.
-- @field #table setUnits Set of UNITs.
-- @field #number updateID Scheduler ID for updating.
-- @extends #ZONE_POLYGON_BASE
--- The ZONE_ELASTIC class defines a dynamic polygon zone, where only the convex hull is used.
--
-- @field #ZONE_ELASTIC
ZONE_ELASTIC = {
ClassName="ZONE_ELASTIC",
points={},
setGroups={}
}
--- Constructor to create a ZONE_ELASTIC instance.
-- @param #ZONE_ELASTIC self
-- @param #string ZoneName Name of the zone.
-- @param DCS#Vec2 Points (Optional) Fixed points.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:New(ZoneName, Points)
local self=BASE:Inherit(self, ZONE_POLYGON_BASE:New(ZoneName, Points)) --#ZONE_ELASTIC
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
_EVENTDISPATCHER:CreateEventNewZone( self )
if Points then
self.points=Points
end
return self
end
--- Add a vertex (point) to the polygon.
-- @param #ZONE_ELASTIC self
-- @param DCS#Vec2 Vec2 Point in 2D (with x and y coordinates).
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddVertex2D(Vec2)
-- Add vec2 to points.
table.insert(self.points, Vec2)
return self
end
--- Add a vertex (point) to the polygon.
-- @param #ZONE_ELASTIC self
-- @param DCS#Vec3 Vec3 Point in 3D (with x, y and z coordinates). Only the x and z coordinates are used.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddVertex3D(Vec3)
-- Add vec2 from vec3 to points.
table.insert(self.points, {x=Vec3.x, y=Vec3.z})
return self
end
--- Add a set of groups. Positions of the group will be considered as polygon vertices when contructing the convex hull.
-- @param #ZONE_ELASTIC self
-- @param Core.Set#SET_GROUP SetGroup Set of groups.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddSetGroup(GroupSet)
-- Add set to table.
table.insert(self.setGroups, GroupSet)
return self
end
--- Update the convex hull of the polygon.
-- This uses the [Graham scan](https://en.wikipedia.org/wiki/Graham_scan).
-- @param #ZONE_ELASTIC self
-- @param #number Delay Delay in seconds before the zone is updated. Default 0.
-- @param #boolean Draw Draw the zone. Default `nil`.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:Update(Delay, Draw)
-- Debug info.
self:T(string.format("Updating ZONE_ELASTIC %s", tostring(self.ZoneName)))
-- Copy all points.
local points=UTILS.DeepCopy(self.points or {})
if self.setGroups then
for _,_setGroup in pairs(self.setGroups) do
local setGroup=_setGroup --Core.Set#SET_GROUP
for _,_group in pairs(setGroup.Set) do
local group=_group --Wrapper.Group#GROUP
if group and group:IsAlive() then
table.insert(points, group:GetVec2())
end
end
end
end
-- Update polygon verticies from points.
self._.Polygon=self:_ConvexHull(points)
if Draw~=false then
if self.DrawID or Draw==true then
self:UndrawZone()
self:DrawZone()
end
end
return self
end
--- Start the updating scheduler.
-- @param #ZONE_ELASTIC self
-- @param #number Tstart Time in seconds before the updating starts.
-- @param #number dT Time interval in seconds between updates. Default 60 sec.
-- @param #number Tstop Time in seconds after which the updating stops. Default `nil`.
-- @param #boolean Draw Draw the zone. Default `nil`.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:StartUpdate(Tstart, dT, Tstop, Draw)
self.updateID=self:ScheduleRepeat(Tstart, dT, 0, Tstop, ZONE_ELASTIC.Update, self, 0, Draw)
return self
end
--- Stop the updating scheduler.
-- @param #ZONE_ELASTIC self
-- @param #number Delay Delay in seconds before the scheduler will be stopped. Default 0.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:StopUpdate(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, ZONE_ELASTIC.StopUpdate, self)
else
if self.updateID then
self:ScheduleStop(self.updateID)
self.updateID=nil
end
end
return self
end
--- Create a convec hull.
-- @param #ZONE_ELASTIC self
-- @param #table pl Points
-- @return #table Points
function ZONE_ELASTIC:_ConvexHull(pl)
if #pl == 0 then
return {}
end
table.sort(pl, function(left,right)
return left.x < right.x
end)
local h = {}
-- Function: ccw > 0 if three points make a counter-clockwise turn, clockwise if ccw < 0, and collinear if ccw = 0.
local function ccw(a,b,c)
return (b.x - a.x) * (c.y - a.y) > (b.y - a.y) * (c.x - a.x)
end
-- lower hull
for i,pt in pairs(pl) do
while #h >= 2 and not ccw(h[#h-1], h[#h], pt) do
table.remove(h,#h)
end
table.insert(h,pt)
end
-- upper hull
local t = #h + 1
for i=#pl, 1, -1 do
local pt = pl[i]
while #h >= t and not ccw(h[#h-1], h[#h], pt) do
table.remove(h, #h)
end
table.insert(h, pt)
end
table.remove(h, #h)
return h
end
end
do -- ZONE_AIRBASE
--- @type ZONE_AIRBASE

View File

@@ -471,6 +471,22 @@ do -- Types
--@field #boolean lateActivated
--@field #boolean uncontrolled
--- DCS template data structure.
-- @type Template
-- @field #boolean uncontrolled Aircraft is uncontrolled.
-- @field #boolean lateActivation Group is late activated.
-- @field #number x 2D Position on x-axis in meters.
-- @field #number y 2D Position on y-axis in meters.
-- @field #table units Unit list.
--
--- Unit data structure.
--@type Template.Unit
--@field #string name Name of the unit.
--@field #number x
--@field #number y
--@field #number alt
end --
@@ -490,7 +506,6 @@ do -- Object
-- @field BASE
-- @field SCENERY
-- @field CARGO
--- @type Object.Desc
-- @extends #Desc
@@ -500,6 +515,10 @@ do -- Object
--- @function [parent=#Object] isExist
-- @param #Object self
-- @return #boolean
--- @function [parent=#Object] isActive
-- @param #Object self
-- @return #boolean
--- @function [parent=#Object] destroy
-- @param #Object self
@@ -1132,6 +1151,11 @@ do -- Unit
-- @function [parent=#Unit] getAmmo
-- @param #Unit self
-- @return #Unit.Ammo
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
-- @function [parent=#Unit] getDescentCapacity
-- @param #Unit self
-- @return #number Number of soldiers that embark.
--- Returns the unit sensors.
-- @function [parent=#Unit] getSensors
@@ -1350,7 +1374,9 @@ do -- AI
--- @type AI.Option.Ground
-- @field #AI.Option.Ground.id id
-- @field #AI.Option.Ground.val val
-- @field #AI.Option.Ground.mid mid
-- @field #AI.Option.Ground.mval mval
--
--- @type AI.Option.Naval
-- @field #AI.Option.Naval.id id
-- @field #AI.Option.Naval.val val
@@ -1373,6 +1399,11 @@ do -- AI
-- @field PROHIBIT_AG
-- @field MISSILE_ATTACK
-- @field PROHIBIT_WP_PASS_REPORT
-- @field OPTION_RADIO_USAGE_CONTACT
-- @field OPTION_RADIO_USAGE_ENGAGE
-- @field OPTION_RADIO_USAGE_KILL
-- @field JETT_TANKS_IF_EMPTY
-- @field FORCED_ATTACK
--- @type AI.Option.Air.id.FORMATION
-- @field LINE_ABREAST
@@ -1442,19 +1473,35 @@ do -- AI
--- @type AI.Option.Ground.id
-- @field NO_OPTION
-- @field ROE @{#AI.Option.Ground.val.ROE}
-- @field FORMATION
-- @field DISPERSE_ON_ATTACK true or false
-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE}
-- @field ENGAGE_AIR_WEAPONS
-- @field AC_ENGAGEMENT_RANGE_RESTRICTION
--- @type AI.Option.Ground.mid -- Moose added
-- @field RESTRICT_AAA_MIN 27
-- @field RESTRICT_AAA_MAX 29
-- @field RESTRICT_TARGETS @{#AI.Option.Ground.mval.ENGAGE_TARGETS} 28
--- @type AI.Option.Ground.val
-- @field #AI.Option.Ground.val.ROE ROE
-- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE
-- @field #AI.Option.Ground.val.ENGAGE_TARGETS RESTRICT_TARGETS
--- @type AI.Option.Ground.val.ROE
-- @field OPEN_FIRE
-- @field RETURN_FIRE
-- @field WEAPON_HOLD
--- @type AI.Option.Ground.mval -- Moose added
-- @field #AI.Option.Ground.mval.ENGAGE_TARGETS ENGAGE_TARGETS
--- @type AI.Option.Ground.mval.ENGAGE_TARGETS -- Moose added
-- @field ANY_TARGET -- 0
-- @field AIR_UNITS_ONLY -- 1
-- @field GROUND_UNITS_ONLY -- 2
--- @type AI.Option.Ground.val.ALARM_STATE
-- @field AUTO
-- @field GREEN

File diff suppressed because it is too large Load Diff

View File

@@ -213,7 +213,7 @@ do -- DESIGNATE
-- In order to prevent an overflow of designations due to many Detected Targets, there is a
-- Maximum Designations scope that is set in the DesignationObject.
--
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations put in scope of the DesignationObject.
-- The method @{#DESIGNATE.SetMaximumDesignations}() will put a limit on the amount of designations (target groups) put in scope of the DesignationObject.
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
--
-- # 4. Laser codes
@@ -562,7 +562,8 @@ do -- DESIGNATE
end
--- Set the maximum amount of designations.
--- Set the maximum amount of designations (target groups). This will put a limit on the amount of designations in scope.
-- Using the menu system, the player can "forget" a designation, so that gradually a new designation can be put in scope when detected.
-- @param #DESIGNATE self
-- @param #number MaximumDesignations
-- @return #DESIGNATE
@@ -602,7 +603,7 @@ do -- DESIGNATE
end
--- Set the maximum amount of markings FACs will do, per designated target group.
--- Set the maximum amount of markings FACs will do, per designated target group. This will limit the number of parallelly marked units of a target group.
-- @param #DESIGNATE self
-- @param #number MaximumMarkings Maximum markings FACs will do, per designated target group.
-- @return #DESIGNATE
@@ -911,8 +912,8 @@ do -- DESIGNATE
for DesignateIndex, Designating in pairs( self.Designating ) do
local DetectedItem = DetectedItems[DesignateIndex]
if DetectedItem then
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
DetectedReport:Add( string.rep( "-", 140 ) )
local Report = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup, nil, true ):Text( ", " )
DetectedReport:Add( string.rep( "-", 40 ) )
DetectedReport:Add( " - " .. Report )
if string.find( Designating, "L" ) then
DetectedReport:Add( " - " .. "Lasing Targets" )
@@ -1192,8 +1193,8 @@ do -- DESIGNATE
local MarkingCount = 0
local MarkedTypes = {}
local ReportTypes = REPORT:New()
local ReportLaserCodes = REPORT:New()
--local ReportTypes = REPORT:New()
--local ReportLaserCodes = REPORT:New()
TargetSetUnit:Flush( self )
@@ -1243,8 +1244,8 @@ do -- DESIGNATE
if not Recce then
self:F( "Lasing..." )
self.RecceSet:Flush( self)
--self.RecceSet:Flush( self)
for RecceGroupID, RecceGroup in pairs( self.RecceSet:GetSet() ) do
for UnitID, UnitData in pairs( RecceGroup:GetUnits() or {} ) do
@@ -1282,13 +1283,13 @@ do -- DESIGNATE
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
--RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
-- 5, self.AttackSet, DesignateName )
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
10, self.AttackSet, DesignateName )
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
--ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(RecceUnit.LaserCode)
--ReportLaserCodes:Add(RecceUnit.LaserCode)
return
end
else
@@ -1303,16 +1304,16 @@ do -- DESIGNATE
if Recce then
Recce:LaseOff()
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 5, self.AttackSet, self.DesignateName )
Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() "out of LOS. Cancelling lase!", 10, self.AttackSet, self.DesignateName )
end
else
--MarkingCount = MarkingCount + 1
local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
--ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(RecceUnit.LaserCode)
--ReportLaserCodes:Add(RecceUnit.LaserCode)
end
end
end
@@ -1322,19 +1323,19 @@ do -- DESIGNATE
local TargetUnitType = TargetUnit:GetTypeName()
if not MarkedTypes[TargetUnitType] then
MarkedTypes[TargetUnitType] = true
ReportTypes:Add(TargetUnitType)
--ReportTypes:Add(TargetUnitType)
end
ReportLaserCodes:Add(Recce.LaserCode)
--Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 5, self.AttackSet )
--ReportLaserCodes:Add(Recce.LaserCode)
Recce:MessageToSetGroup( self.DesignateName .. ": Marking " .. TargetUnit:GetTypeName() .. " with laser " .. Recce.LaserCode .. ".", 10, self.AttackSet )
end
end
end
end
)
local MarkedTypesText = ReportTypes:Text(', ')
local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
--local MarkedTypesText = ReportTypes:Text(', ')
--local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
--self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )

View File

@@ -653,131 +653,132 @@ do -- DETECTION_BASE
-- self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } )
-- Only process if the target is visible. Detection also returns invisible units.
-- if Detection.visible == true then
local DetectionAccepted = true
local DetectedObjectName = DetectedObject:getName()
local DetectedObjectType = DetectedObject:getTypeName()
local DetectedObjectVec3 = DetectedObject:getPoint()
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
local Distance = ((DetectedObjectVec3.x - DetectionGroupVec3.x) ^ 2 +
(DetectedObjectVec3.y - DetectionGroupVec3.y) ^ 2 +
(DetectedObjectVec3.z - DetectionGroupVec3.z) ^ 2) ^ 0.5 / 1000
local DetectedUnitCategory = DetectedObject:getDesc().category
-- self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
-- Calculate Acceptance
DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false
-- if Distance > 15000 then
-- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then
-- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then
-- DetectionAccepted = false
-- end
-- end
-- end
if self.AcceptRange and Distance * 1000 > self.AcceptRange then
DetectionAccepted = false
end
if self.AcceptZones then
local AnyZoneDetection = false
for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do
local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE
if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then
AnyZoneDetection = true
--if Detection.visible == true then
local DetectionAccepted = true
local DetectedObjectName = DetectedObject:getName()
local DetectedObjectType = DetectedObject:getTypeName()
local DetectedObjectVec3 = DetectedObject:getPoint()
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 +
( DetectedObjectVec3.y - DetectionGroupVec3.y )^2 +
( DetectedObjectVec3.z - DetectionGroupVec3.z )^2
) ^ 0.5 / 1000
local DetectedUnitCategory = DetectedObject:getDesc().category
--self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
-- Calculate Acceptance
DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false
-- if Distance > 15000 then
-- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then
-- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then
-- DetectionAccepted = false
-- end
-- end
-- end
if self.AcceptRange and Distance * 1000 > self.AcceptRange then
DetectionAccepted = false
end
if self.AcceptZones then
local AnyZoneDetection = false
for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do
local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE
if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then
AnyZoneDetection = true
end
end
if not AnyZoneDetection then
DetectionAccepted = false
end
end
if not AnyZoneDetection then
DetectionAccepted = false
end
end
if self.RejectZones then
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
DetectionAccepted = false
end
end
end
-- Calculate additional probabilities
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.DistanceProbability then
local DistanceFactor = Distance / 4
local DistanceProbabilityReversed = (1 - self.DistanceProbability) * DistanceFactor
local DistanceProbability = 1 - DistanceProbabilityReversed
DistanceProbability = DistanceProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
-- self:T( { Probability, DistanceProbability } )
if Probability > DistanceProbability then
DetectionAccepted = false
end
end
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.AlphaAngleProbability then
local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y }
local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x )
local Sinus = math.sin( AlphaAngle )
local AlphaAngleProbabilityReversed = (1 - self.AlphaAngleProbability) * (1 - Sinus)
local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed
AlphaAngleProbability = AlphaAngleProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
-- self:T( { Probability, AlphaAngleProbability } )
if Probability > AlphaAngleProbability then
DetectionAccepted = false
end
end
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.ZoneProbability then
for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do
self:F( { ZoneData } )
local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE
local ZoneProbability = ZoneData[2] -- #number
ZoneProbability = ZoneProbability * 30 / 300
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
local Probability = math.random() -- Selects a number between 0 and 1
-- self:T( { Probability, ZoneProbability } )
if Probability > ZoneProbability then
if self.RejectZones then
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
DetectionAccepted = false
break
end
end
end
end
if DetectionAccepted then
HasDetectedObjects = true
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
if TargetIsDetected and TargetIsDetected == true then
self.DetectedObjects[DetectedObjectName].IsDetected = TargetIsDetected
-- Calculate additional probabilities
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.DistanceProbability then
local DistanceFactor = Distance / 4
local DistanceProbabilityReversed = ( 1 - self.DistanceProbability ) * DistanceFactor
local DistanceProbability = 1 - DistanceProbabilityReversed
DistanceProbability = DistanceProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
--self:T( { Probability, DistanceProbability } )
if Probability > DistanceProbability then
DetectionAccepted = false
end
end
if TargetIsDetected and TargetIsVisible and TargetIsVisible == true then
self.DetectedObjects[DetectedObjectName].IsVisible = TargetIsDetected and TargetIsVisible
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.AlphaAngleProbability then
local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y }
local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x )
local Sinus = math.sin( AlphaAngle )
local AlphaAngleProbabilityReversed = ( 1 - self.AlphaAngleProbability ) * ( 1 - Sinus )
local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed
AlphaAngleProbability = AlphaAngleProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
--self:T( { Probability, AlphaAngleProbability } )
if Probability > AlphaAngleProbability then
DetectionAccepted = false
end
end
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.ZoneProbability then
for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do
self:F({ZoneData})
local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE
local ZoneProbability = ZoneData[2] -- #number
ZoneProbability = ZoneProbability * 30 / 300
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
local Probability = math.random() -- Selects a number between 0 and 1
--self:T( { Probability, ZoneProbability } )
if Probability > ZoneProbability then
DetectionAccepted = false
break
end
end
end
end
if DetectionAccepted then
HasDetectedObjects = true
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
if TargetIsDetected and TargetIsDetected == true then
self.DetectedObjects[DetectedObjectName].IsDetected = TargetIsDetected
end
if TargetIsDetected and TargetIsVisible and TargetIsVisible == true then
self.DetectedObjects[DetectedObjectName].IsVisible = TargetIsDetected and TargetIsVisible
end
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType
end
self.DetectedObjects[DetectedObjectName].KnowDistance = TargetKnowDistance -- Detection.distance -- TargetKnowDistance
self.DetectedObjects[DetectedObjectName].LastTime = (TargetIsDetected and TargetIsVisible == false) and TargetLastTime
@@ -2151,8 +2152,9 @@ do -- DETECTION_UNITS
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
-- @param Wrapper.Group#GROUP AttackGroup The group to generate the report for.
-- @param Core.Settings#SETTINGS Settings Message formatting settings to use.
-- @param #boolean ForceA2GCoordinate Set creation of A2G coordinate
-- @return Core.Report#REPORT The report of the detection items.
function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings )
function DETECTION_UNITS:DetectedItemReportSummary( DetectedItem, AttackGroup, Settings, ForceA2GCoordinate )
self:F( { DetectedItem = DetectedItem } )
local DetectedItemID = self:GetDetectedItemID( DetectedItem )
@@ -2187,7 +2189,11 @@ do -- DETECTION_UNITS
-- TODO: solve Index reference
local DetectedItemCoordinate = self:GetDetectedItemCoordinate( DetectedItem )
local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings )
if ForceA2GCoordinate then
DetectedItemCoordText = DetectedItemCoordinate:ToStringA2G(AttackGroup,Settings)
end
local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem )
local Report = REPORT:New()
@@ -2439,16 +2445,16 @@ do -- DETECTION_AREAS
-- A set with multiple detected zones will be created as there are groups of units detected.
--
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
--
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DETECTION_BASE} and
--
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
-- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}.
--
--
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
--
--
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}().
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index.
--
--
-- ## 4.4) Flare or Smoke detected units
--
-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place.
@@ -2489,7 +2495,7 @@ do -- DETECTION_AREAS
return self
end
--- Retrieve set of detected zones.
-- @param #DETECTION_AREAS self
-- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected.

View File

@@ -792,7 +792,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
-- Say notching headings.
BASE:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
--TODO: ALERT or INFO depending on whether this is a direct target.
--TODO: lauchalertall option.
@@ -1114,6 +1114,13 @@ end
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- FOX event handler for event birth.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventPlayerEnterAircraft(EventData)
end
--- FOX event handler for event birth.
-- @param #FOX self
-- @param Core.Event#EVENTDATA EventData
@@ -1155,7 +1162,7 @@ function FOX:OnEventBirth(EventData)
-- Add F10 radio menu for player.
if not self.menudisabled then
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
self:ScheduleOnce(0.1, FOX._AddF10Commands, self, _unitname)
end
-- Player data.

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,28 @@
--- **Functional** -- Train missile defence and deflection.
--
--
-- ===
--
-- ## Features:
--
--
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
-- * Provide alerts when a missile would have killed your aircraft.
-- * Provide alerts when the missile self destructs.
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
--
--
-- ===
--
--
-- ## Missions:
--
--
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
--
--
-- ===
--
--
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
--
--
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
--
--
-- * **Messages**: Menu to configure all messages.
-- * **Messages On**: Show all messages.
-- * **Messages Off**: Disable all messages.
@@ -45,23 +45,23 @@
-- * **Range Off**: Disable range information when a missile is fired to a target.
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
--
--
-- ===
--
--
-- ### Authors: **FlightControl**
--
--
-- ### Contributions:
--
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
--
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
-- * **132nd Squadron**: Testing and optimizing the logic.
--
--
-- ===
--
-- @module Functional.MissileTrainer
@@ -76,7 +76,7 @@
---
--
-- # Constructor:
--
--
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
--
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
@@ -84,7 +84,7 @@
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
--
-- # Initialization:
--
--
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
--
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
@@ -97,8 +97,8 @@
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
--
-- @field #MISSILETRAINER
--
-- @field #MISSILETRAINER
MISSILETRAINER = {
ClassName = "MISSILETRAINER",
TrackingMissiles = {},
@@ -167,7 +167,7 @@ end
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
-- @param #MISSILETRAINER self
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @return #MISSILETRAINER
function MISSILETRAINER:New( Distance, Briefing )
local self = BASE:Inherit( self, BASE:New() )
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self:F( "ForEach:" .. Client.UnitName )
-- Client:Alive( self._Alive, self )
-- end
--
self.DBClients:ForEachClient(
--
self.DBClients:ForEachClient(
function( Client )
self:F( "ForEach:" .. Client.UnitName )
Client:Alive( self._Alive, self )
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self.DB:ForEachClient(
-- --- @param Wrapper.Client#CLIENT Client
-- function( Client )
--
--
-- ... actions ...
--
--
-- end
-- )
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
self.DetailsRangeOnOff = true
self.DetailsBearingOnOff = true
self.MenusOnOff = true
self.TrackingMissiles = {}
@@ -293,7 +293,7 @@ end
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
-- @param #MISSILETRAINER self
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
self:F( TrackingFrequency )
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName )
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
if Client then
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
local Message = MESSAGE:New(
string.format( "%s launched a %s",
TrainerSourceUnit:GetTypeName(),
TrainerWeaponName
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
if self.AlertsToAll then
Message:ToAll()
else
Message:ToClient( Client )
end
end
local ClientID = Client:GetID()
self:T( ClientID )
local MissileData = {}
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
end
-- ALERTS PART
-- Loop for all Player Clients to check the alerts and deletion of missiles.
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
if Client and Client:IsAlive() then
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
if Distance <= self.Distance then
-- Hit alert
TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" )
local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData)
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
), 5, "Tracking" )
if self.AlertsToAll == true then
Message:ToAll()
else
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
-- TRACKING PART
-- For the current client, the missile range and bearing details are displayed To the Player Client.
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- Main Player Client loop
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
--self:T2( { Client:GetName() } )
ClientData.MessageToClient = ""
ClientData.MessageToAll = ""
-- Other Players Client loop
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
--self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
if ShowMessages == true then
local TrackingTo
TrackingTo = string.format( " -> %s",
TrainerWeaponTypeName
)
if ClientDataID == TrackingDataID then
if ClientData.MessageToClient == "" then
ClientData.MessageToClient = "Missiles to You:\n"
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
end
end
end
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )

View File

@@ -1044,6 +1044,3 @@ function PSEUDOATC:_myname(unitname)
return string.format("%s (%s)", csign, pname)
end

File diff suppressed because it is too large Load Diff

View File

@@ -45,7 +45,8 @@
-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)**
--
-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536), [Ciribob](https://forums.eagle.ru/member.php?u=112175)
--
-- ### SRS Additions: Applevangelist
--
-- ===
-- @module Functional.Range
-- @image Range.JPG
@@ -101,6 +102,10 @@
-- @field #boolean targetsheet If true, players can save their target sheets. Rangeboss will not work if targetsheets do not save.
-- @field #string targetpath Path where to save the target sheets.
-- @field #string targetprefix File prefix for target sheet files.
-- @field Sound.SRS#MSRS controlmsrs
-- @field Sound.SRS#MSRSQUEUE controlsrsQ
-- @field Sound.SRS#MSRS instructmsrs
-- @field Sound.SRS#MSRSQUEUE instructsrsQ
-- @extends Core.Fsm#FSM
--- *Don't only practice your art, but force your way into its secrets; art deserves that, for it and knowledge can raise man to the Divine.* - Ludwig van Beethoven
@@ -225,6 +230,11 @@
--
-- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function.
--
-- ## Voice output via SRS
--
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}(). Range control and instructor frequencies and voices can then be
-- set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}()
--
-- # Persistence
--
-- To automatically save bombing results to disk, use the @{#RANGE.SetAutosave}() function. Bombing results will be saved as csv file in your "Saved Games\DCS.openbeta\Logs" directory.
@@ -429,6 +439,25 @@ RANGE.TargetType = {
-- @field #string airframe Aircraft type of player.
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
-- @field #string date OS date.
-- @field #number attackHdg Attack heading in degrees.
-- @field #number attackVel Attack velocity in knots.
-- @field #number attackAlt Attack altitude in feet.
-- @field #string clock Time of the run.
-- @field #string rangename Name of the range.
--- Strafe result.
-- @type RANGE.StrafeResult
-- @field #string player Player name.
-- @field #string airframe Aircraft type of player.
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
-- @field #string date OS date.
-- @field #string name Name of the target pit.
-- @field #number roundsFired Number of rounds fired.
-- @field #number roundsHit Number of rounds that hit the target.
-- @field #number strafeAccuracy Accuracy of the run in percent.
-- @field #string clock Time of the run.
-- @field #string rangename Name of the range.
-- @field #boolean invalid Invalid pass.
--- Strafe result.
-- @type RANGE.StrafeResult
@@ -549,7 +578,7 @@ RANGE.MenuF10Root = nil
--- Range script version.
-- @field #string version
RANGE.version = "2.4.0"
RANGE.version = "2.5.0"
-- TODO list:
-- TODO: Verbosity level for messages.
@@ -569,17 +598,16 @@ RANGE.version = "2.4.0"
--- RANGE contructor. Creates a new RANGE object.
-- @param #RANGE self
-- @param #string rangename Name of the range. Has to be unique. Will we used to create F10 menu items etc.
-- @param #string RangeName Name of the range. Has to be unique. Will we used to create F10 menu items etc.
-- @return #RANGE RANGE object.
function RANGE:New( rangename )
BASE:F( { rangename = rangename } )
function RANGE:New( RangeName )
-- Inherit BASE.
local self = BASE:Inherit( self, FSM:New() ) -- #RANGE
-- Get range name.
-- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu.
self.rangename = rangename or "Practice Range"
self.rangename = RangeName or "Practice Range"
-- Log id.
self.id = string.format( "RANGE %s | ", self.rangename )
@@ -815,7 +843,7 @@ function RANGE:onafterStart()
end
-- Init range control.
if self.rangecontrolfreq then
if self.rangecontrolfreq and not self.useSRS then
-- Radio queue.
self.rangecontrol = RADIOQUEUE:New( self.rangecontrolfreq, nil, self.rangename )
@@ -841,7 +869,7 @@ function RANGE:onafterStart()
self.rangecontrol:Start( 1, 0.1 )
-- Init range control.
if self.instructorfreq then
if self.instructorfreq and not self.useSRS then
-- Radio queue.
self.instructor = RADIOQUEUE:New( self.instructorfreq, nil, self.rangename )
@@ -950,6 +978,19 @@ function RANGE:SetTargetSheet( path, prefix )
return self
end
--- Set FunkMan socket. Bombing and strafing results will be send to your Discord bot.
-- **Requires running FunkMan program**.
-- @param #RANGE self
-- @param #number Port Port. Default `10042`.
-- @param #string Host Host. Default "127.0.0.1".
-- @return #RANGE self
function RANGE:SetFunkManOn(Port, Host)
self.funkmanSocket=SOCKET:New(Port, Host)
return self
end
--- Set messages to examiner. The examiner will receive messages from all clients.
-- @param #RANGE self
-- @param #string examinergroupname Name of the group of the examiner.
@@ -1146,7 +1187,91 @@ function RANGE:TrackMissilesOFF()
return self
end
--- Enable range control and set frequency.
--- Use SRS Simple-Text-To-Speech for transmissions. No sound files necessary.
-- @param #RANGE self
-- @param #string PathToSRS Path to SRS directory.
-- @param #number Port SRS port. Default 5002.
-- @param #number Coalition Coalition side, e.g. coalition.side.BLUE or coalition.side.RED
-- @param #number Frequency Frequency to use, defaults to 256 (same as rangecontrol)
-- @param #number Modulation Modulation to use, defaults to radio.modulation.AM
-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0
-- @param #string PathToGoogleKey Path to Google TTS credentials.
-- @return #RANGE self
function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey)
if PathToSRS then
self.useSRS=true
self.controlmsrs=MSRS:New(PathToSRS, Frequency or 256, Modulation or radio.modulation.AM, Volume or 1.0)
self.controlmsrs:SetPort(Port)
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")
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
--- (SRS) Set range control frequency and voice.
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 256 MHz.
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
-- @param #string voice Voice.
-- @param #string culture Culture, defaults to "en-US".
-- @param #string gender Gender, defaults to "female".
-- @param #string relayunitname Name of the unit used for transmission location.
-- @return #RANGE self
function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender, relayunitname )
self.rangecontrolfreq = frequency or 256
self.controlmsrs:SetFrequencies(self.rangecontrolfreq)
self.controlmsrs:SetModulations(modulation or radio.modulation.AM)
self.controlmsrs:SetVoice(voice)
self.controlmsrs:SetCulture(culture or "en-US")
self.controlmsrs:SetGender(gender or "female")
self.rangecontrol = true
if relayunitname then
local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate()
self.rangecontrolrelayname = relayunitname
end
return self
end
--- (SRS) Set range instructor frequency and voice.
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 305 MHz.
-- @param #number modulation Modulation, defaults to radio.modulation.AM.
-- @param #string voice Voice.
-- @param #string culture Culture, defaults to "en-US".
-- @param #string gender Gender, defaults to "male".
-- @param #string relayunitname Name of the unit used for transmission location.
-- @return #RANGE self
function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gender, relayunitname )
self.instructorfreq = frequency or 305
self.instructmsrs:SetFrequencies(self.instructorfreq)
self.instructmsrs:SetModulations(modulation or radio.modulation.AM)
self.instructmsrs:SetVoice(voice)
self.instructmsrs:SetCulture(culture or "en-US")
self.instructmsrs:SetGender(gender or "male")
self.instructor = true
if relayunitname then
local unit = UNIT:FindByName(relayunitname)
local Coordinate = unit:GetCoordinate()
self.instructmsrs:SetCoordinate(Coordinate)
self.instructorrelayname = relayunitname
end
return self
end
--- Enable range control and set frequency (non-SRS).
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 256 MHz.
-- @param #string relayunitname Name of the unit used for transmission.
@@ -1157,7 +1282,7 @@ function RANGE:SetRangeControl( frequency, relayunitname )
return self
end
--- Enable instructor radio and set frequency.
--- Enable instructor radio and set frequency (non-SRS).
-- @param #RANGE self
-- @param #number frequency Frequency in MHz. Default 305 MHz.
-- @param #string relayunitname Name of the unit used for transmission.
@@ -1388,7 +1513,7 @@ function RANGE:AddBombingTargets( targetnames, goodhitrange, randommove )
elseif _isstatic == false then
local _unit = UNIT:FindByName( name )
self:T2( self.id .. string.format( "Adding unit bombing target %s with hit range %d.", name, goodhitrange, randommove ) )
self:AddBombingTargetUnit( _unit, goodhitrange )
self:AddBombingTargetUnit( _unit, goodhitrange, randommove )
else
self:E( self.id .. string.format( "ERROR! Could not find bombing target %s.", name ) )
end
@@ -1728,7 +1853,6 @@ function RANGE:OnEventHit( EventData )
self:_DisplayMessageToGroup( _unit, text )
self:T2( self.id .. text )
_currentTarget.pastfoulline = true
invalidStrafe = true -- Rangeboss Edit
end
end
@@ -1760,6 +1884,13 @@ function RANGE:OnEventHit( EventData )
end
end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param #table weapon Weapon
function RANGE:_TrackWeapon(weapon)
end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
-- @param #RANGE self
-- @param Core.Event#EVENTDATA EventData
@@ -1773,7 +1904,11 @@ function RANGE:OnEventShot( EventData )
if EventData.IniDCSUnit == nil then
return
end
if EventData.IniPlayerName == nil then
return
end
-- Weapon data.
local _weapon = EventData.Weapon:getTypeName() -- should be the same as Event.WeaponTypeName
local _weaponStrArray = UTILS.Split( _weapon, "%." )
@@ -1806,6 +1941,11 @@ function RANGE:OnEventShot( EventData )
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Attack parameters.
local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight()
local attackVel=_unit:GetVelocityKNOTS()
-- Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2
@@ -1848,7 +1988,6 @@ function RANGE:OnEventShot( EventData )
-- Check again in ~0.005 seconds ==> 200 checks per second.
return timer.getTime() + self.dtBombtrack
else
-----------------------------
@@ -1858,7 +1997,7 @@ function RANGE:OnEventShot( EventData )
-- Get closet target to last position.
local _closetTarget = nil -- #RANGE.BombTarget
local _distance = nil
local _closeCoord = nil
local _closeCoord = nil --Core.Point#COORDINATE
local _hitquality = "POOR"
-- Get callsign.
@@ -1886,6 +2025,7 @@ function RANGE:OnEventShot( EventData )
-- Loop over defined bombing targets.
for _, _bombtarget in pairs( self.bombingTargets ) do
local bombtarget=_bombtarget --#RANGE.BombTarget
-- Get target coordinate.
local targetcoord = self:_GetBombTargetCoordinate( _bombtarget )
@@ -1898,15 +2038,15 @@ function RANGE:OnEventShot( EventData )
-- Find closest target to last known position of the bomb.
if _distance == nil or _temp < _distance then
_distance = _temp
_closetTarget = _bombtarget
_closeCoord = targetcoord
_closetTarget = bombtarget
_closeCoord = targetcoord
if _distance <= 1.53 then -- Rangeboss Edit
_hitquality = "SHACK" -- Rangeboss Edit
elseif _distance <= 0.5 * _bombtarget.goodhitrange then -- Rangeboss Edit
elseif _distance <= 0.5 * bombtarget.goodhitrange then -- Rangeboss Edit
_hitquality = "EXCELLENT"
elseif _distance <= _bombtarget.goodhitrange then
elseif _distance <= bombtarget.goodhitrange then
_hitquality = "GOOD"
elseif _distance <= 2 * _bombtarget.goodhitrange then
elseif _distance <= 2 * bombtarget.goodhitrange then
_hitquality = "INEFFECTIVE"
else
_hitquality = "POOR"
@@ -1927,6 +2067,7 @@ function RANGE:OnEventShot( EventData )
local _results = self.bombPlayerResults[_playername]
local result = {} -- #RANGE.BombResult
result.command=SOCKET.DataType.BOMBRESULT
result.name = _closetTarget.name or "unknown"
result.distance = _distance
result.radial = _closeCoord:HeadingTo( impactcoord )
@@ -1934,11 +2075,17 @@ function RANGE:OnEventShot( EventData )
result.quality = _hitquality
result.player = playerData.playername
result.time = timer.getAbsTime()
result.clock = UTILS.SecondsToClock(result.time, true)
result.midate = UTILS.GetDCSMissionDate()
result.theatre = env.mission.theatre
result.airframe = playerData.airframe
result.roundsFired = 0 -- Rangeboss Edit
result.roundsHit = 0 -- Rangeboss Edit
result.roundsQuality = "N/A" -- Rangeboss Edit
result.rangename = self.rangename
result.attackHdg = attackHdg
result.attackVel = attackVel
result.attackAlt = attackAlt
-- Add to table.
table.insert( _results, result )
@@ -1953,7 +2100,12 @@ function RANGE:OnEventShot( EventData )
self:_DisplayMessageToGroup( _unit, _message, nil, false )
if self.rangecontrol then
self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration )
-- weapon impacted too far from the nearest target! No Score!
if self.useSRS then
self.controlsrsQ:NewTransmission(_message,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration )
end
end
else
@@ -2036,18 +2188,26 @@ end
function RANGE:onafterEnterRange( From, Event, To, player )
if self.instructor and self.rangecontrol then
-- Range control radio frequency split.
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
-- Radio message that player entered the range
self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
self.instructor:Number2Transmission( RF[1] )
if tonumber( RF[2] ) > 0 then
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
self.instructor:Number2Transmission( RF[2] )
if self.useSRS then
local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq)
local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq)
local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext,nil,self.instructmsrs,nil,1,{group},text,10)
else
-- Range control radio frequency split.
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
-- Radio message that player entered the range
-- You entered the bombing range. For hit assessment, contact the range controller at xy MHz
self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
self.instructor:Number2Transmission( RF[1] )
if tonumber( RF[2] ) > 0 then
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
self.instructor:Number2Transmission( RF[2] )
end
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
end
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
end
end
@@ -2061,7 +2221,14 @@ end
function RANGE:onafterExitRange( From, Event, To, player )
if self.instructor then
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath )
-- You left the bombing range zone. Have a nice day!
if self.useSRS then
local text = "You left the bombing range zone. Have a nice day!"
local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(text,nil,self.instructmsrs,nil,1,{group},text,10)
else
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath )
end
end
end
@@ -2078,48 +2245,77 @@ function RANGE:onafterImpact( From, Event, To, result, player )
-- Only display target name if there is more than one bomb target.
local targetname = nil
if #self.bombingTargets > 1 then
local targetname = result.name
targetname = result.name
end
-- Send message to player.
local text = string.format( "%s, impact %03d° for %d ft", player.playername, result.radial, UTILS.MetersToFeet( result.distance ) )
if targetname then
text = text .. string.format( " from bulls of target %s." )
text = text .. string.format( " from bulls of target %s.", targetname )
else
text = text .. "."
end
text = text .. string.format( " %s hit.", result.quality )
if self.rangecontrol then
self.rangecontrol:NewTransmission( RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration )
self.rangecontrol:Number2Transmission( string.format( "%03d", result.radial ), nil, 0.1 )
self.rangecontrol:NewTransmission( RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath )
self.rangecontrol:NewTransmission( RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath )
self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.MetersToFeet( result.distance ) ) )
self.rangecontrol:NewTransmission( RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath )
if result.quality == "POOR" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "INEFFECTIVE" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "GOOD" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "EXCELLENT" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5 )
if self.useSRS then
local group = player.client:GetGroup()
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10)
else
self.rangecontrol:NewTransmission( RANGE.Sound.RCImpact.filename, RANGE.Sound.RCImpact.duration, self.soundpath, nil, nil, text, self.subduration )
self.rangecontrol:Number2Transmission( string.format( "%03d", result.radial ), nil, 0.1 )
self.rangecontrol:NewTransmission( RANGE.Sound.RCDegrees.filename, RANGE.Sound.RCDegrees.duration, self.soundpath )
self.rangecontrol:NewTransmission( RANGE.Sound.RCFor.filename, RANGE.Sound.RCFor.duration, self.soundpath )
self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.MetersToFeet( result.distance ) ) )
self.rangecontrol:NewTransmission( RANGE.Sound.RCFeet.filename, RANGE.Sound.RCFeet.duration, self.soundpath )
if result.quality == "POOR" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCPoorHit.filename, RANGE.Sound.RCPoorHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "INEFFECTIVE" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCIneffectiveHit.filename, RANGE.Sound.RCIneffectiveHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "GOOD" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCGoodHit.filename, RANGE.Sound.RCGoodHit.duration, self.soundpath, nil, 0.5 )
elseif result.quality == "EXCELLENT" then
self.rangecontrol:NewTransmission( RANGE.Sound.RCExcellentHit.filename, RANGE.Sound.RCExcellentHit.duration, self.soundpath, nil, 0.5 )
end
end
end
-- Unit.
local unit = UNIT:FindByName( player.unitname )
-- Send message.
self:_DisplayMessageToGroup( unit, text, nil, true )
self:T( self.id .. text )
if player.unitname and not self.useSRS then
-- Get unit.
local unit = UNIT:FindByName( player.unitname )
-- Send message.
self:_DisplayMessageToGroup( unit, text, nil, true )
self:T( self.id .. text )
end
-- Save results.
if self.autosave then
self:Save()
end
-- Send result to FunkMan, which creates fancy MatLab figures and sends them to Discord via a bot.
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
--- Function called after strafing run.
-- @param #RANGE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #RANGE.PlayerData player Player data table.
-- @param #RANGE.StrafeResult result Result of run.
function RANGE:onafterStrafeResult( From, Event, To, player, result)
if self.funkmanSocket then
self.funkmanSocket:SendTable(result)
end
end
@@ -2175,7 +2371,7 @@ function RANGE:onafterSave( From, Event, To )
local target = result.name
local radial = result.radial
local quality = result.quality
local time = UTILS.SecondsToClock( result.time )
local time = UTILS.SecondsToClock(result.time, true)
local airframe = result.airframe
local date = "n/a"
if os then
@@ -2353,7 +2549,8 @@ end
--- Start smoking a coordinate with a delay.
-- @param #table _args Argements passed.
function RANGE._DelayedSmoke( _args )
trigger.action.smoke( _args.coord:GetVec3(), _args.color )
_args.coord:Smoke(_args.color)
--trigger.action.smoke( _args.coord:GetVec3(), _args.color )
end
--- Display top 10 stafing results of a specific player.
@@ -2381,7 +2578,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
-- Sort results table wrt number of hits.
local _sort = function( a, b )
return a.hits > b.hits
return a.roundsHit > b.roundsHit
end
table.sort( _results, _sort )
@@ -2398,7 +2595,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
-- Best result.
if _bestMsg == "" then
_bestMsg = string.format( "Hits %d - %s - %s", _result.hits, _result.zone.name, _result.text )
_bestMsg = string.format( "Hits %d - %s - %s", result.roundsHit, result.name, result.roundsQuality)
end
-- 10 runs
@@ -2443,15 +2640,15 @@ function RANGE:_DisplayStrafePitResults( _unitName )
-- Get the best result of the player.
local _best = nil
for _, _result in pairs( _results ) do
if _best == nil or _result.hits > _best.hits then
if _best == nil or _result.roundsHit > _best.roundsHit then
_best = _result
end
end
-- Add best result to table.
if _best ~= nil then
local text = string.format( "%s: Hits %i - %s - %s", _playerName, _best.hits, _best.zone.name, _best.text )
table.insert( _playerResults, { msg = text, hits = _best.hits } )
local text = string.format( "%s: Hits %i - %s - %s", _playerName, _best.roundsHit, _best.name, _best.roundsQuality )
table.insert( _playerResults, { msg = text, hits = _best.roundsHit } )
end
end
@@ -2603,7 +2800,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
-- Check if we have a player.
if unit and playername then
self:I(playername)
-- Message text.
local text = ""
@@ -2667,6 +2864,28 @@ function RANGE:_DisplayRangeInfo( _unitname )
text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt )
text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets )
text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets )
if self.instructor then
local alive = "N/A"
if self.instructorrelayname then
local relay = UNIT:FindByName( self.instructorrelayname )
if relay then
--alive = tostring( relay:IsAlive() )
alive = relay:IsAlive() and "ok" or "N/A"
end
end
text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive )
end
if self.rangecontrol then
local alive = "N/A"
if self.rangecontrolrelayname then
local relay = UNIT:FindByName( self.rangecontrolrelayname )
if relay then
alive = tostring( relay:IsAlive() )
alive = relay:IsAlive() and "ok" or "N/A"
end
end
text = text .. string.format( "Control %.3f MHz (Relay=%s)\n", self.rangecontrolfreq, alive )
end
text = text .. texthit
text = text .. textbomb
text = text .. textdelay
@@ -2947,11 +3166,18 @@ function RANGE:_CheckInZone( _unitName )
-- Send message.
self:_DisplayMessageToGroup( _unit, _msg, nil, true )
if self.rangecontrol then
self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath )
if self.useSRS then
local group = _unit:GetGroup()
local text = "You left the strafing zone too quickly! No score!"
--self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10)
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1)
else
-- You left the strafing zone too quickly! No score!
self.rangecontrol:NewTransmission( RANGE.Sound.RCLeftStrafePitTooQuickly.filename, RANGE.Sound.RCLeftStrafePitTooQuickly.duration, self.soundpath )
end
end
else
-- Get current ammo.
@@ -3015,19 +3241,26 @@ function RANGE:_CheckInZone( _unitName )
-- Message text.
local _text = string.format( "%s, hits on target %s: %d", self:_myname( _unitName ), _result.zone.name, _result.hits )
local ttstext = string.format( "%s, hits on target %s: %d.", self:_myname( _unitName ), _result.zone.name, _result.hits )
if shots and accur then
_text = _text .. string.format( "\nTotal rounds fired %d. Accuracy %.1f %%.", shots, accur )
ttstext = ttstext .. string.format( ". Total rounds fired %d. Accuracy %.1f percent.", shots, accur )
end
_text = _text .. string.format( "\n%s", resulttext )
ttstext = ttstext .. string.format( " %s", resulttext )
-- Send message.
self:_DisplayMessageToGroup( _unit, _text )
-- Strafe result.
local result = {} -- #RANGE.StrafeResult
result.command=SOCKET.DataType.STRAFERESULT
result.player=_playername
result.name=_result.zone.name or "unknown"
result.time = timer.getAbsTime()
result.clock = UTILS.SecondsToClock(result.time)
result.midate = UTILS.GetDCSMissionDate()
result.theatre = env.mission.theatre
result.roundsFired = shots
result.roundsHit = _result.hits
result.roundsQuality = resulttext
@@ -3046,16 +3279,20 @@ function RANGE:_CheckInZone( _unitName )
-- Voice over.
if self.rangecontrol then
self.rangecontrol:NewTransmission( RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath )
self.rangecontrol:Number2Transmission( string.format( "%d", _result.hits ) )
if shots and accur then
self.rangecontrol:NewTransmission( RANGE.Sound.RCTotalRoundsFired.filename, RANGE.Sound.RCTotalRoundsFired.duration, self.soundpath, nil, 0.2 )
self.rangecontrol:Number2Transmission( string.format( "%d", shots ), nil, 0.2 )
self.rangecontrol:NewTransmission( RANGE.Sound.RCAccuracy.filename, RANGE.Sound.RCAccuracy.duration, self.soundpath, nil, 0.2 )
self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.Round( accur, 0 ) ) )
self.rangecontrol:NewTransmission( RANGE.Sound.RCPercent.filename, RANGE.Sound.RCPercent.duration, self.soundpath )
if self.useSRS then
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission( RANGE.Sound.RCHitsOnTarget.filename, RANGE.Sound.RCHitsOnTarget.duration, self.soundpath )
self.rangecontrol:Number2Transmission( string.format( "%d", _result.hits ) )
if shots and accur then
self.rangecontrol:NewTransmission( RANGE.Sound.RCTotalRoundsFired.filename, RANGE.Sound.RCTotalRoundsFired.duration, self.soundpath, nil, 0.2 )
self.rangecontrol:Number2Transmission( string.format( "%d", shots ), nil, 0.2 )
self.rangecontrol:NewTransmission( RANGE.Sound.RCAccuracy.filename, RANGE.Sound.RCAccuracy.duration, self.soundpath, nil, 0.2 )
self.rangecontrol:Number2Transmission( string.format( "%d", UTILS.Round( accur, 0 ) ) )
self.rangecontrol:NewTransmission( RANGE.Sound.RCPercent.filename, RANGE.Sound.RCPercent.duration, self.soundpath )
end
self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 )
end
self.rangecontrol:NewTransmission( _sound.filename, _sound.duration, self.soundpath, nil, 0.5 )
end
-- Set strafe status to nil.
@@ -3094,7 +3331,11 @@ function RANGE:_CheckInZone( _unitName )
local _msg = string.format( "%s, rolling in on strafe pit %s.", self:_myname( _unitName ), target.name )
if self.rangecontrol then
self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath )
if self.useSRS then
self.controlsrsQ:NewTransmission(_msg,nil,self.controlmsrs,nil,1)
else
self.rangecontrol:NewTransmission( RANGE.Sound.RCRollingInOnStrafeTarget.filename, RANGE.Sound.RCRollingInOnStrafeTarget.duration, self.soundpath )
end
end
-- Send message.
@@ -3453,6 +3694,7 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
-- Group ID.
local _gid = _unit:GetGroup():GetID()
local _grp = _unit:GetGroup()
-- Get playername and player settings
local _, playername = self:_GetPlayerUnitAndName( _unit:GetName() )
@@ -3460,14 +3702,14 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
-- 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
trigger.action.outTextForGroup( _gid, _text, _time, _clear )
local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
end
-- Send message to examiner.
if self.examinergroupname ~= nil then
local _examinerid = GROUP:FindByName( self.examinergroupname ):GetID()
local _examinerid = GROUP:FindByName( self.examinergroupname )
if _examinerid then
trigger.action.outTextForGroup( _examinerid, _text, _time, _clear )
local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid)
end
end
end
@@ -3487,7 +3729,7 @@ function RANGE:_SmokeBombImpactOnOff( unitname )
self.PlayerSettings[playername].smokebombimpact = false
text = string.format( "%s, %s, smoking impact points of bombs is now OFF.", self.rangename, playername )
else
self.PlayerSettigs[playername].smokebombimpact = true
self.PlayerSettings[playername].smokebombimpact = true
text = string.format( "%s, %s, smoking impact points of bombs is now ON.", self.rangename, playername )
end
self:_DisplayMessageToGroup( unit, text, 5, false, true )
@@ -3508,7 +3750,7 @@ function RANGE:_SmokeBombDelayOnOff( unitname )
self.PlayerSettings[playername].delaysmoke = false
text = string.format( "%s, %s, delayed smoke of bombs is now OFF.", self.rangename, playername )
else
self.PlayerSettigs[playername].delaysmoke = true
self.PlayerSettings[playername].delaysmoke = true
text = string.format( "%s, %s, delayed smoke of bombs is now ON.", self.rangename, playername )
end
self:_DisplayMessageToGroup( unit, text, 5, false, true )
@@ -3820,6 +4062,7 @@ 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
end
@@ -3836,13 +4079,22 @@ end
-- @param #string unitname Name of the player unit.
function RANGE:_myname( unitname )
self:F2( unitname )
local pname = "Ghost 1 1"
local unit = UNIT:FindByName( unitname )
if unit then
local grp = unit:GetGroup()
if grp then
pname = grp:GetCustomCallSign(true,true)
end
end
--[[
local pname = unit:GetPlayerName()
-- local csign = unit:GetCallsign()
-- return string.format("%s (%s)", csign, pname)
return string.format( "%s", pname )
--]]
return pname
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -280,11 +280,11 @@ function SCORING:New( GameName )
-- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked).
self:SetFratricide( self.ScaleDestroyPenalty * 3 )
self.penaltyonfratricide = true
-- Default penalty when a player changes coalition.
self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty )
self.penaltyoncoalitionchange = true
self:SetDisplayMessagePrefix()
-- Event handlers
@@ -656,11 +656,12 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). " ..
self.CoalitionChangePenalty .. "Penalty points added.",
MESSAGE.Type.Information )
:ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1 * self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."Penalty points added.",
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
end
end
@@ -671,7 +672,7 @@ function SCORING:_AddPlayerFromUnit( UnitData )
self.Players[PlayerName].UNIT = UnitData
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
self.Players[PlayerName].ThreatType = UnitThreatType
-- TODO: make fratricide switchable
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then
if self.Players[PlayerName].PenaltyWarning < 1 then
@@ -1112,16 +1113,18 @@ function SCORING:_EventOnHit( Event )
if InitCoalition then -- A coalition object was hit, probably a static.
if InitCoalition == TargetCoalition then
-- TODO: Penalty according scale
Player.Penalty = Player.Penalty + 10 -- * self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + 10 -- * self.ScaleDestroyPenalty
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Update
)
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
Player.Score = Player.Score + 1
@@ -1651,18 +1654,19 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty,
ReportHits,
ReportDestroys,
ReportCoalitionChanges,
ReportGoals,
ReportMissions
)
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty,
ReportHits,
ReportDestroys,
ReportCoalitionChanges,
ReportGoals,
ReportMissions
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
end
end
@@ -1706,13 +1710,14 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty
)
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
PlayerName,
PlayerScore - PlayerPenalty,
PlayerScore,
PlayerPenalty
)
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
end
end

View File

@@ -79,6 +79,7 @@ SEAD = {
["Kh25"] = "Kh25",
["BGM_109"] = "BGM_109",
["AGM_154"] = "AGM_154",
["HY-2"] = "HY-2",
}
--- Missile enumerators - from DCS ME and Wikipedia
@@ -98,6 +99,7 @@ SEAD = {
["Kh25"] = {25, 0.8},
["BGM_109"] = {460, 0.705}, --in-game ~465kn
["AGM_154"] = {130, 0.61},
["HY-2"] = {90,1},
}
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.

View File

@@ -18,7 +18,7 @@
-- @module Functional.Shorad
-- @image Functional.Shorad.jpg
--
-- Date: July 2021
-- Date: Nov 2021
-------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE
@@ -41,6 +41,7 @@
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
-- @extends Core.Base#BASE
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
--
-- Simple Class for a more intelligent Short Range Air Defense System
@@ -131,6 +132,7 @@ do
["Kh29"] = "Kh29",
["Kh31"] = "Kh31",
["Kh66"] = "Kh66",
--["BGM_109"] = "BGM_109",
}
--- Instantiates a new SHORAD object
@@ -138,13 +140,13 @@ do
-- @param #string Name Name of this SHORAD
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
-- @param #number Radius Defense radius in meters, used to switch on groups
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
-- @retunr #SHORAD self
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, FSM:New() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
@@ -162,11 +164,17 @@ do
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
self:I("*** SHORAD - Started Version 0.2.8")
self:I("*** SHORAD - Started Version 0.3.1")
-- Set the string id for output to DCS.log file.
self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState()
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
-- Start State.
self:SetStartState("Running")
self:AddTransition("*", "WakeUpShorad", "*")
self:AddTransition("*", "CalculateHitZone", "*")
return self
end
@@ -310,7 +318,7 @@ do
local hit = false
if self.DefendHarms then
for _,_name in pairs (SHORAD.Harms) do
if string.find(WeaponName,_name,1) then hit = true end
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
@@ -326,7 +334,7 @@ do
local hit = false
if self.DefendMavs then
for _,_name in pairs (SHORAD.Mavs) do
if string.find(WeaponName,_name,1) then hit = true end
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
@@ -367,7 +375,7 @@ do
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
end
@@ -388,7 +396,7 @@ do
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
end
end
@@ -400,6 +408,7 @@ do
-- @return #boolean Returns true for a detection, else false
function SHORAD:_ShotIsDetected()
self:T(self.lid .. " _ShotIsDetected")
if self.debug then return true end
local IsDetected = false
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
local ActualDetection = math.random(1,100) -- value for this shot
@@ -423,7 +432,7 @@ do
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mymantis:AddShorad(myshorad,720)
-- mymantis:Start()
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
self:T(self.lid .. " WakeUpShorad")
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
local targetcat = TargetCat or Object.Category.UNIT
@@ -477,6 +486,76 @@ do
return self
end
--- (Internal) Calculate hit zone of an AGM-88
-- @param #SHORAD self
-- @param #table SEADWeapon DCS.Weapon object
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
-- @param #number height Height when the missile was fired
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
-- @return #SHORAD self
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
self:T("**** Calculating hit zone")
if SEADWeapon and SEADWeapon:isExist() then
--local pos = SEADWeapon:getPoint()
-- postion and height
local position = SEADWeapon:getPosition()
local mheight = height
-- heading
local wph = math.atan2(position.x.z, position.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
-- velocity
local wpndata = SEAD.HarmData["AGM_88"]
local mveloc = math.floor(wpndata[2] * 340.29)
local c1 = (2*mheight*9.81)/(mveloc^2)
local c2 = (mveloc^2) / 9.81
local Ropt = c2 * math.sqrt(c1+1)
if height <= 5000 then
Ropt = Ropt * 0.72
elseif height <= 7500 then
Ropt = Ropt * 0.82
elseif height <= 10000 then
Ropt = Ropt * 0.87
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
-- look at a couple of zones across the trajectory
for n=1,3 do
local dist = Ropt - ((n-1)*20000)
local predpos= pos0:Translate(dist,wph)
if predpos then
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset = self.Groupset
local tgtcoord = targetzone:GetRandomPointVec2()
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local _targetgroup = nil
local _targetgroupname = "none"
local _targetskill = "Random"
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
end
end
end
end
return self
end
--- Main function - work on the EventData
-- @param #SHORAD self
-- @param Core.Event#EVENTDATA EventData The event details table data set
@@ -504,13 +583,48 @@ do
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
-- get target data
local targetdata = EventData.Weapon:getTarget() -- Identify target
-- Is there target data?
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true) then
self:I("**** Tracking AGM-88 with no target data.")
local pos0 = EventData.IniUnit:GetCoordinate()
local fheight = EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
end
return self
end
local targetcat = targetdata:getCategory() -- Identify category
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
self:T({targetdata})
local targetunit = nil
if targetcat == Object.Category.UNIT then -- UNIT
targetunit = UNIT:Find(targetdata)
elseif targetcat == Object.Category.STATIC then -- STATIC
targetunit = STATIC:Find(targetdata)
--self:T("Static Target Data")
--self:T({targetdata:isExist()})
--self:T({targetdata:getPoint()})
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
--tgtcoord:MarkToAll("Missile Target",true)
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1 = tgtgrp1:GetCoordinate()
--tgtcoord1:MarkToAll("Close target SAM",true)
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2 = tgtgrp2:GetCoordinate()
--tgtcoord2:MarkToAll("Close target SHORAD",true)
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
else
targetunit = tgtgrp2
targetcat = Object.Category.UNIT
end
end
--local targetunitname = Unit.getName(targetdata) -- Unit name
if targetunit and targetunit:IsAlive() then
@@ -519,7 +633,11 @@ do
local targetgroup = nil
local targetgroupname = "none"
if targetcat == Object.Category.UNIT then
targetgroup = targetunit:GetGroup()
if targetunit.ClassName == "UNIT" then
targetgroup = targetunit:GetGroup()
elseif targetunit.ClassName == "GROUP" then
targetgroup = targetunit
end
targetgroupname = targetgroup:GetName() -- group name
elseif targetcat == Object.Category.STATIC then
targetgroup = targetunit
@@ -540,6 +658,7 @@ do
end
end
end
return self
end
--
end

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,12 @@ __Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Socket.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
__Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' )
__Moose.Include( 'Scripts/Moose/Core/Report.lua' )
__Moose.Include( 'Scripts/Moose/Core/Scheduler.lua' )
@@ -28,6 +31,8 @@ __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )

View File

@@ -52,7 +52,6 @@
--- ATIS class.
-- @type ATIS
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string theatre DCS map name.
-- @field #string airbasename The name of the airbase.
@@ -309,7 +308,6 @@
-- @field #ATIS
ATIS = {
ClassName = "ATIS",
Debug = false,
lid = nil,
theatre = nil,
airbasename = nil,
@@ -588,7 +586,7 @@ _ATIS = {}
--- ATIS class version.
-- @field #string version
ATIS.version = "0.9.6"
ATIS.version = "0.9.8"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -613,26 +611,26 @@ ATIS.version = "0.9.6"
--- Create a new ATIS class object for a specific aircraft carrier unit.
-- @param #ATIS self
-- @param #string airbasename Name of the airbase.
-- @param #number frequency Radio frequency in MHz. Default 143.00 MHz.
-- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators.
-- @return #ATIS self
function ATIS:New( airbasename, frequency, modulation )
function ATIS:New(AirbaseName, Frequency, Modulation)
-- Inherit everything from FSM class.
local self = BASE:Inherit( self, FSM:New() ) -- #ATIS
self.airbasename = airbasename
self.airbase = AIRBASE:FindByName( airbasename )
self.airbasename=AirbaseName
self.airbase=AIRBASE:FindByName(AirbaseName)
if self.airbase == nil then
self:E( "ERROR: Airbase %s for ATIS could not be found!", tostring( airbasename ) )
if self.airbase==nil then
self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(AirbaseName))
return nil
end
-- Default freq and modulation.
self.frequency = frequency or 143.00
self.modulation = modulation or 0
self.frequency=Frequency or 143.00
self.modulation=Modulation or 0
-- Get map.
self.theatre = env.mission.theatre
@@ -734,14 +732,6 @@ function ATIS:New( airbasename, frequency, modulation )
-- @param #string To To state.
-- @param #string Text Report text.
-- Debug trace.
if false then
self.Debug = true
BASE:TraceOnOff( true )
BASE:TraceClass( self.ClassName )
BASE:TraceLevel( 1 )
end
return self
end
@@ -755,7 +745,7 @@ end
-- @return #ATIS self
function ATIS:SetSoundfilesPath( path )
self.soundpath = tostring( path or "ATIS Soundfiles/" )
self:I( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
self:T( self.lid .. string.format( "Setting sound files path to %s", self.soundpath ) )
return self
end
@@ -766,7 +756,7 @@ end
-- @return #ATIS self
function ATIS:SetRadioRelayUnitName( unitname )
self.relayunitname = unitname
self:I( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) )
self:T( self.lid .. string.format( "Setting radio relay unit to %s", self.relayunitname ) )
return self
end
@@ -802,6 +792,15 @@ function ATIS:SetRunwayLength()
return self
end
--- Give information on runway length.
-- @param #ATIS self
-- @return #ATIS self
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
--- Give information on airfield elevation
-- @param #ATIS self
-- @return #ATIS self
@@ -852,7 +851,7 @@ function ATIS:SetRunwayHeadingsMagnetic( headings )
end
-- Add runway heading to table.
self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) )
self:T( self.lid .. string.format( "Adding user specified magnetic runway heading %s", heading ) )
table.insert( self.runwaymag, heading )
local h = self:GetRunwayWithoutLR( heading )
@@ -874,7 +873,7 @@ function ATIS:SetRunwayHeadingsMagnetic( headings )
end
-- Add inverse runway heading to table.
self:I( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) )
self:T( self.lid .. string.format( "Adding user specified magnetic runway heading %s (inverse)", head2 ) )
table.insert( self.runwaymag, head2 )
end
@@ -1128,16 +1127,22 @@ end
-- @param #string Voice Specific voice. Overrides `Gender` and `Culture`.
-- @param #number Port SRS port. Default 5002.
-- @return #ATIS self
function ATIS:SetSRS( PathToSRS, Gender, Culture, Voice, Port )
self.useSRS = true
self.msrs = MSRS:New( PathToSRS, self.frequency, self.modulation )
self.msrs:SetGender( Gender )
self.msrs:SetCulture( Culture )
self.msrs:SetVoice( Voice )
self.msrs:SetPort( Port )
self.msrs:SetCoalition( self:GetCoalition() )
if self.dTQueueCheck <= 10 then
self:SetQueueUpdateTime( 90 )
function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port)
if PathToSRS then
self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port)
self.msrs:SetCoalition(self:GetCoalition())
self.msrs:SetLabel("ATIS")
self.msrsQ = MSRSQUEUE:New("ATIS")
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
@@ -1179,32 +1184,34 @@ function ATIS:onafterStart( From, Event, To )
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 ) )
-- Start radio queue.
self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) )
-- Send coordinate is airbase coord.
self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() )
-- Set relay unit if we have one.
self.radioqueue:SetSenderUnitName( self.relayunitname )
-- Set radio power.
self.radioqueue:SetRadioPower( self.power )
-- Init numbers.
self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath )
self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath )
self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath )
self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath )
self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath )
self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath )
self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath )
self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath )
self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath )
self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath )
-- Start radio queue.
self.radioqueue:Start( 1, 0.1 )
if not self.useSRS then
self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) )
-- Send coordinate is airbase coord.
self.radioqueue:SetSenderCoordinate( self.airbase:GetCoordinate() )
-- Set relay unit if we have one.
self.radioqueue:SetSenderUnitName( self.relayunitname )
-- Set radio power.
self.radioqueue:SetRadioPower( self.power )
-- Init numbers.
self.radioqueue:SetDigit( 0, ATIS.Sound.N0.filename, ATIS.Sound.N0.duration, self.soundpath )
self.radioqueue:SetDigit( 1, ATIS.Sound.N1.filename, ATIS.Sound.N1.duration, self.soundpath )
self.radioqueue:SetDigit( 2, ATIS.Sound.N2.filename, ATIS.Sound.N2.duration, self.soundpath )
self.radioqueue:SetDigit( 3, ATIS.Sound.N3.filename, ATIS.Sound.N3.duration, self.soundpath )
self.radioqueue:SetDigit( 4, ATIS.Sound.N4.filename, ATIS.Sound.N4.duration, self.soundpath )
self.radioqueue:SetDigit( 5, ATIS.Sound.N5.filename, ATIS.Sound.N5.duration, self.soundpath )
self.radioqueue:SetDigit( 6, ATIS.Sound.N6.filename, ATIS.Sound.N6.duration, self.soundpath )
self.radioqueue:SetDigit( 7, ATIS.Sound.N7.filename, ATIS.Sound.N7.duration, self.soundpath )
self.radioqueue:SetDigit( 8, ATIS.Sound.N8.filename, ATIS.Sound.N8.duration, self.soundpath )
self.radioqueue:SetDigit( 9, ATIS.Sound.N9.filename, ATIS.Sound.N9.duration, self.soundpath )
-- Start radio queue.
self.radioqueue:Start( 1, 0.1 )
end
-- Handle airbase capture
-- Handle events.
self:HandleEvent( EVENTS.BaseCaptured )
@@ -1239,7 +1246,7 @@ function ATIS:onafterStatus( From, Event, To )
else
text = text .. string.format( ", Relay unit=%s (alive=%s)", tostring( self.relayunitname ), relayunitstatus )
end
self:I( self.lid .. text )
self:T( self.lid .. text )
self:__Status( -60 )
end
@@ -1378,7 +1385,8 @@ function ATIS:onafterBroadcast( From, Event, To )
--- Runway ---
--------------
local runway, rwyLeft = self:GetActiveRunway()
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
------------
--- Time ---
@@ -1444,8 +1452,8 @@ function ATIS:onafterBroadcast( From, Event, To )
-- Convert to °F.
if self.TDegF then
temperature = UTILS.CelsiusToFahrenheit( temperature )
dewpoint = UTILS.CelsiusToFahrenheit( dewpoint )
temperature=UTILS.CelsiusToFahrenheit(temperature)
dewpoint=UTILS.CelsiusToFahrenheit(dewpoint)
end
local TEMPERATURE = string.format( "%d", math.abs( temperature ) )
@@ -1740,10 +1748,10 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- Wind
-- Adding a space after each digit of WINDFROM to convert this to aviation-speak for TTS via SRS
-- Adding a space after each digit of WINDFROM to convert this to aviation-speak for TTS via SRS
if self.useSRS then
WINDFROM = string.gsub(WINDFROM,".", "%1 ")
end
end
if self.metric then
subtitle = string.format( "Wind from %s at %s m/s", WINDFROM, WINDSPEED )
else
@@ -2002,20 +2010,20 @@ function ATIS:onafterBroadcast( From, Event, To )
alltext = alltext .. ";\n" .. subtitle
-- Active runway.
local subtitle = string.format( "Active runway %s", runway )
if rwyLeft == true then
subtitle = subtitle .. " Left"
elseif rwyLeft == false then
subtitle = subtitle .. " Right"
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( runway )
if rwyLeft == true then
self:Transmission( ATIS.Sound.Left, 0.2 )
elseif rwyLeft == false then
self:Transmission( ATIS.Sound.Right, 0.2 )
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
@@ -2126,7 +2134,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- ILS
local ils = self:GetNavPoint( self.ils, runway, rwyLeft )
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
if ils then
subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency )
if not self.useSRS then
@@ -2144,7 +2152,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- Outer NDB
local ndb = self:GetNavPoint( self.ndbouter, runway, rwyLeft )
local ndb=self:GetNavPoint(self.ndbouter, runwayLanding, rwyLandingLeft)
if ndb then
subtitle = string.format( "Outer NDB frequency %.2f MHz", ndb.frequency )
if not self.useSRS then
@@ -2162,7 +2170,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- Inner NDB
local ndb = self:GetNavPoint( self.ndbinner, runway, rwyLeft )
local ndb=self:GetNavPoint(self.ndbinner, runwayLanding, rwyLandingLeft)
if ndb then
subtitle = string.format( "Inner NDB frequency %.2f MHz", ndb.frequency )
if not self.useSRS then
@@ -2201,7 +2209,7 @@ function ATIS:onafterBroadcast( From, Event, To )
-- TACAN
if self.tacan then
subtitle = string.format( "TACAN channel %dX Ray", self.tacan )
subtitle=string.format("TACAN channel %dX Ray", self.tacan)
if not self.useSRS then
self:Transmission( ATIS.Sound.TACANChannel, 1.0, subtitle )
self.radioqueue:Number2Transmission( tostring( self.tacan ), nil, 0.2 )
@@ -2221,7 +2229,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- PRMG
local ndb = self:GetNavPoint( self.prmg, runway, rwyLeft )
local ndb=self:GetNavPoint(self.prmg, runwayLanding, rwyLandingLeft)
if ndb then
subtitle = string.format( "PRMG channel %d", ndb.frequency )
if not self.useSRS then
@@ -2278,8 +2286,10 @@ function ATIS:onafterReport( From, Event, To, Text )
-- Debug output.
self:T( "SRS TTS: " .. text )
-- Play text-to-speech report.
self.msrs:PlayText( text )
-- Play text-to-speech report.
local duration = STTS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
--self.msrs:PlayText( text )
end
@@ -2348,39 +2358,19 @@ end
--- Get active runway runway.
-- @param #ATIS self
-- @param #boolean Takeoff If `true`, get runway for takeoff. Default is for landing.
-- @return #string Active runway, e.g. "31" for 310 deg.
-- @return #boolean Use Left=true, Right=false, or nil.
function ATIS:GetActiveRunway()
local coord = self.airbase:GetCoordinate()
local height = coord:GetLandHeight()
-- Get wind direction and speed in m/s.
local windFrom, windSpeed = coord:GetWind( height + 10 )
-- Get active runway data based on wind direction.
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
-- Active runway "31".
local runway = self:GetMagneticRunway( windFrom ) or runact.idx
-- Left or right in case there are two runways with the same heading.
local rwyLeft = nil
-- Check if user explicitly specified a runway.
if self.activerunway then
-- Get explicit runway heading if specified.
local runwayno = self:GetRunwayWithoutLR( self.activerunway )
if runwayno ~= "" then
runway = runwayno
end
-- Was "L"eft or "R"ight given?
rwyLeft = self:GetRunwayLR( self.activerunway )
function ATIS:GetActiveRunway(Takeoff)
local runway=nil --Wrapper.Airbase#AIRBASE.Runway
if Takeoff then
runway=self.airbase:GetActiveRunwayTakeoff()
else
runway=self.airbase:GetActiveRunwayLanding()
end
return runway, rwyLeft
return runway.name, runway.isLeft
end
--- Get runway from user supplied magnetic heading.
@@ -2446,7 +2436,7 @@ end
-- @return #string Runway heading without left or right, *e.g.* "31".
function ATIS:GetRunwayWithoutLR( runway )
local rwywo = runway:gsub( "%D+", "" )
-- self:I(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo))
-- self:T(string.format("FF runway=%s ==> rwywo=%s", runway, rwywo))
return rwywo
end

View File

@@ -33,6 +33,7 @@
-- * [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**]
@@ -53,7 +54,7 @@
--
-- At the moment, optimized parameters are available for the F/A-18C Hornet (Lot 20) and A-4E community mod as aircraft and the USS John C. Stennis as carrier.
--
-- The AV-8B Harrier, HMS Hermes, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and
-- The AV-8B Harrier, HMS Hermes, HMS Invincible, the USS Tarawa, USS America, HMAS Canberra, and Juan Carlos I are WIP. The AV-8B harrier and the LHA's and LHD can only be used together, i.e. these ships are the only carriers the harrier is supposed to land on and
-- no other fixed wing aircraft (human or AI controlled) are supposed to land on these ships. Currently only Case I is supported. Case II/III take slightly different steps from the CVN carrier.
-- However, if no offset is used for the holding radial this provides a very close representation of the V/STOL Case III, allowing for an approach to over the deck and a vertical landing.
--
@@ -1263,7 +1264,7 @@ AIRBOSS = {
--- Aircraft types capable of landing on carrier (human+AI).
-- @type AIRBOSS.AircraftCarrier
-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, USS Tarawa, USS America, and Juan Carlos I.
-- @field #string AV8B AV-8B Night Harrier. Works only with the HMS Hermes, HMS Invincible, USS Tarawa, USS America, and Juan Carlos I.
-- @field #string A4EC A-4E Community mod.
-- @field #string HORNET F/A-18C Lot 20 Hornet by Eagle Dynamics.
-- @field #string F14A F-14A by Heatblur.
@@ -1300,6 +1301,7 @@ AIRBOSS.AircraftCarrier={
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
-- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete]
-- @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]
-- @field #string AMERICA USS America (LHA-6) [V/STOL Carrier]
-- @field #string JCARLOS Juan Carlos I (L61) [V/STOL Carrier]
@@ -1314,6 +1316,7 @@ AIRBOSS.CarrierType = {
FORRESTAL = "Forrestal",
VINSON = "VINSON",
HERMES = "HERMES81",
INVINCIBLE = "hms_invincible",
TARAWA = "LHA_Tarawa",
AMERICA = "USS America LHA-6",
JCARLOS = "L61",
@@ -1988,6 +1991,9 @@ function AIRBOSS:New( carriername, alias )
elseif self.carriertype == AIRBOSS.CarrierType.HERMES then
-- Hermes parameters.
self:_InitHermes()
elseif self.carriertype == AIRBOSS.CarrierType.INVINCIBLE then
-- Invincible parameters.
self:_InitInvincible()
elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then
-- Tarawa parameters.
self:_InitTarawa()
@@ -2090,7 +2096,7 @@ function AIRBOSS:New( carriername, alias )
-- cL:FlareYellow()
-- Carrier specific.
if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then
if self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.INVINCIBLE or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.HERMES or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.TARAWA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.AMERICA or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.JCARLOS or self.carrier:GetTypeName() ~= AIRBOSS.CarrierType.CANBERRA then
-- Flare wires.
local w1 = stern:Translate( self.carrierparam.wire1, FB, true )
@@ -2823,7 +2829,7 @@ end
function AIRBOSS:SetGlideslopeErrorThresholds(_max,_min, High, HIGH, Low, LOW)
--Check if V/STOL Carrier
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
-- allow a larger GSE for V/STOL operations --Pene Testing
self.gle._max=_max or 0.7
@@ -2860,7 +2866,7 @@ end
function AIRBOSS:SetLineupErrorThresholds(_max,_min, Left, LeftMed, LEFT, Right, RightMed, RIGHT)
--Check if V/STOL Carrier -- Pene testing
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
-- V/STOL Values -- allow a larger LUE for V/STOL operations
self.lue._max=_max or 1.8
@@ -4457,8 +4463,48 @@ function AIRBOSS:_InitHermes()
self.BreakLate.name = "Late Break"
self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0.
self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks?
self.BreakLate.Zmin = -UTILS.NMToMeters( 0.25 ) -- Not more than 0.25 NM port.
self.BreakLate.Zmax = UTILS.NMToMeters( 0.5 ) -- Not more than 0.5 NM starboard.
self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port.
self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard.
self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat.
self.BreakLate.LimitXmax = nil
self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2
self.BreakLate.LimitZmax = nil
end
--- Init parameters for R05 HMS Invincible carrier.
-- @param #AIRBOSS self
function AIRBOSS:_InitInvincible()
-- Init Stennis as default.
self:_InitStennis()
-- Carrier Parameters.
self.carrierparam.sterndist = -105
self.carrierparam.deckheight = 12 -- From model viewer WL0.
-- Total size of the carrier (approx as rectangle).
self.carrierparam.totlength = 228.19
self.carrierparam.totwidthport = 20.5
self.carrierparam.totwidthstarboard = 24.5
-- Landing runway.
self.carrierparam.rwyangle = 0
self.carrierparam.rwylength = 215
self.carrierparam.rwywidth = 13
-- Wires.
self.carrierparam.wire1 = nil
self.carrierparam.wire2 = nil
self.carrierparam.wire3 = nil
self.carrierparam.wire4 = nil
-- Late break.
self.BreakLate.name = "Late Break"
self.BreakLate.Xmin = -UTILS.NMToMeters( 1 ) -- Not more than 1 NM behind the boat. Last check was at 0.
self.BreakLate.Xmax = UTILS.NMToMeters( 5 ) -- Not more than 5 NM in front of the boat. Enough for late breaks?
self.BreakLate.Zmin = -UTILS.NMToMeters( 1.6 ) -- Not more than 1.6 NM port.
self.BreakLate.Zmax = UTILS.NMToMeters( 1 ) -- Not more than 1 NM starboard.
self.BreakLate.LimitXmin = 0 -- Check and next step 0.8 NM port and in front of boat.
self.BreakLate.LimitXmax = nil
self.BreakLate.LimitZmin = -UTILS.NMToMeters( 0.5 ) -- 926 m port, closer than the stennis as abeam is 0.8-1.0 rather than 1.2
@@ -6293,7 +6339,7 @@ function AIRBOSS:_GetMarshalAltitude( stack, case )
p2 = Carrier:Translate( UTILS.NMToMeters( 1.5 ), hdg )
-- Tarawa,LHA,LHD Delta patterns.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
-- Pattern is directly overhead the carrier.
p1 = Carrier:Translate( UTILS.NMToMeters( 1.0 ), hdg + 90 )
@@ -8132,7 +8178,7 @@ function AIRBOSS:OnEventLand( EventData )
self:T( self.lid .. text )
-- Check carrier type.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
-- Power "Idle".
self:RadioTransmission( self.LSORadio, self.LSOCall.IDLE, false, 1, nil, true )
@@ -8167,7 +8213,7 @@ function AIRBOSS:OnEventLand( EventData )
-- AI unit landed --
--------------------
if self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then
if self.carriertype ~= AIRBOSS.CarrierType.INVINCIBLE or self.carriertype ~= AIRBOSS.CarrierType.HERMES or self.carriertype ~= AIRBOSS.CarrierType.TARAWA or self.carriertype ~= AIRBOSS.CarrierType.AMERICA or self.carriertype ~= AIRBOSS.CarrierType.JCARLOS or self.carriertype ~= AIRBOSS.CarrierType.CANBERRA then
-- Coordinate at landing event
local coord = EventData.IniUnit:GetCoordinate()
@@ -9205,7 +9251,7 @@ function AIRBOSS:_CheckForLongDownwind( playerData )
local limit = UTILS.NMToMeters( -1.6 )
-- For the tarawa, other LHA and LHD we give a bit more space.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
limit = UTILS.NMToMeters( -2.0 )
end
@@ -9292,7 +9338,7 @@ function AIRBOSS:_Ninety( playerData )
self:_PlayerHint( playerData )
-- Next step: wake.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
-- Harrier has no wake stop. It stays port of the boat.
self:_SetPlayerStep( playerData, AIRBOSS.PatternStep.FINAL )
else
@@ -9987,7 +10033,7 @@ function AIRBOSS:_GetSternCoord()
-- local stern=self:GetCoordinate()
-- Stern coordinate (sterndist<0). --Pene testing Case III
if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
-- CASE III V/STOL translation Due over deck approach if needed.
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true)
@@ -10628,7 +10674,7 @@ function AIRBOSS:_GetZoneRunwayBox()
return self.zoneRunwaybox
end
--- Get zone of primary abeam landing position of HMS Hermes, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters.
--- Get zone of primary abeam landing position of HMS Hermes, HMS Invincible, USS Tarawa, USS America and Juan Carlos. Box length 50 meters and width 30 meters.
--- Allow for Clear to land call from LSO approaching abeam the landing spot if stable as per NATOPS 00-80T
-- @param #AIRBOSS self
@@ -10733,7 +10779,7 @@ function AIRBOSS:_GetZoneHolding( case, stack )
self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", Post:GetVec2(), self.marshalradius )
-- Delta pattern.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
self.zoneHolding = ZONE_RADIUS:New( "CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters( 5 ) )
end
@@ -10785,7 +10831,7 @@ function AIRBOSS:_GetZoneCommence( case, stack )
-- Three position
local Three = self:GetCoordinate():Translate( D, hdg + 275 )
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
local Dx = UTILS.NMToMeters( 2.25 )
@@ -11076,7 +11122,7 @@ function AIRBOSS:_GetAltCarrier( unit )
return h
end
--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I and HMS Hermes it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot.
--- Get optimal landing position of the aircraft. Usually between second and third wire. In case of Tarawa, Canberrra, Juan Carlos and America we take the abeam landing spot 120 ft above and 21 ft abeam the 7.5 position, for the Juan Carlos I, HMS Invincible, and HMS Hermes and Invincible it is 120 ft above and 21 ft abeam the 5 position. For CASE III it is 120ft directly above the landing spot.
-- @param #AIRBOSS self
-- @return Core.Point#COORDINATE Optimal landing coordinate.
function AIRBOSS:_GetOptLandingCoordinate()
@@ -11091,7 +11137,7 @@ function AIRBOSS:_GetOptLandingCoordinate()
local FB=self:GetFinalBearing(false)
local case=self.case
-- set Case III V/STOL abeam landing spot over deck -- Pene Testing
if self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if self.carriertype==AIRBOSS.CarrierType.INVINCIBLE or self.carriertype==AIRBOSS.CarrierType.HERMES or self.carriertype==AIRBOSS.CarrierType.TARAWA or self.carriertype==AIRBOSS.CarrierType.AMERICA or self.carriertype==AIRBOSS.CarrierType.JCARLOS or self.carriertype==AIRBOSS.CarrierType.CANBERRA then
if case==3 then
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate())
@@ -11104,7 +11150,7 @@ function AIRBOSS:_GetOptLandingCoordinate()
self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true)
--stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90)
-- Alitude 120 ft.
-- Atlitude 120 ft.
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
end
@@ -11142,6 +11188,13 @@ function AIRBOSS:_GetLandingSpotCoordinate()
-- Primary landing spot 5
self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight )
elseif self.carriertype == AIRBOSS.CarrierType.INVINCIBLE then
-- Using spot 3 as the default
local hdg = self:GetHeading()
self.landingspotcoord:Translate( 69, hdg, true, true ):SetAltitude( self.carrierparam.deckheight )
-- This location looks good.
elseif self.carriertype == AIRBOSS.CarrierType.TARAWA then
-- Landing 100 ft abeam, 120 alt.
@@ -12083,7 +12136,7 @@ function AIRBOSS:_GS( step, n )
if n == -1 then
gp = AIRBOSS.GroovePos.IC
elseif n == 1 then
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
gp = AIRBOSS.GroovePos.AL
else
gp = AIRBOSS.GroovePos.IW
@@ -13962,7 +14015,7 @@ function AIRBOSS:_IsCarrierAircraft( unit )
-- Special case for Harrier which can only land on Tarawa, LHA and LHD.
if aircrafttype == AIRBOSS.AircraftCarrier.AV8B then
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
return true
else
return false
@@ -13970,7 +14023,7 @@ function AIRBOSS:_IsCarrierAircraft( unit )
end
-- Also only Harriers can land on the Tarawa, LHA and LHD.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if aircrafttype ~= AIRBOSS.AircraftCarrier.AV8B then
return false
end
@@ -17327,7 +17380,7 @@ function AIRBOSS:_MarkCaseZones( _unitName, flare )
end
-- Tarawa, LHA and LHD landing spots.
if self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
if self.carriertype == AIRBOSS.CarrierType.INVINCIBLE or self.carriertype == AIRBOSS.CarrierType.HERMES or self.carriertype == AIRBOSS.CarrierType.TARAWA or self.carriertype == AIRBOSS.CarrierType.AMERICA or self.carriertype == AIRBOSS.CarrierType.JCARLOS or self.carriertype == AIRBOSS.CarrierType.CANBERRA then
text = text .. "\n* abeam landing stop with RED flares"
-- Abeam landing spot zone.
local ALSPT = self:_GetZoneAbeamLandingSpot()

View File

@@ -39,6 +39,7 @@
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`.
-- @field Core.Set#SET_GROUP allheligroupset Set of CSAR heli groups.
-- @extends Core.Fsm#FSM
--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia)
@@ -129,9 +130,11 @@
-- mycsar.SRSVoice = nil -- SRS voice, relevant for Google TTS
-- mycsar.SRSGPathToCredentials = nil -- Path to your Google credentials json file, set this if you want to use Google TTS
-- mycsar.SRSVolume = 1 -- Volume, between 0 and 1
-- mycsar.SRSGender = "male" -- male or female voice
-- --
-- mycsar.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection. Requires mycsar.enableForAI to be set to true. --shagrat
-- mycsar.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
-- mycsar.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
--
-- ## 3. Results
--
@@ -228,6 +231,7 @@ CSAR = {
rescuedpilots = 0,
limitmaxdownedpilots = true,
maxdownedpilots = 10,
allheligroupset = nil,
}
--- Downed pilots info.
@@ -260,11 +264,12 @@ CSAR.AircraftType["Mi-24P"] = 8
CSAR.AircraftType["Mi-24V"] = 8
CSAR.AircraftType["Bell-47"] = 2
CSAR.AircraftType["UH-60L"] = 10
CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.6"
CSAR.version="1.0.9"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -411,19 +416,23 @@ function CSAR:New(Coalition, Template, Alias)
-- added 0.1.4
self.wetfeettemplate = nil
self.usewetfeet = false
-- added 0.1.8
self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
-- WARNING - here\'ll be dragons
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
-- needs SRS => 1.9.6 to work (works on the *server* side)
self.useSRS = false -- Use FF\'s SRS integration
self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!)
self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone" -- adjust your own path in your server(!)
self.SRSchannel = 300 -- radio channel
self.SRSModulation = radio.modulation.AM -- modulation
self.SRSport = 5002 -- port
self.SRSCulture = "en-GB"
self.SRSVoice = nil
self.SRSGPathToCredentials = nil
self.SRSVolume = 1
self.SRSVolume = 1.0 -- volume 0.0 to 1.0
self.SRSGender = "male" -- male or female
------------------------
--- Pseudo Functions ---
@@ -925,7 +934,16 @@ function CSAR:_EventHandler(EventData)
local _unit = _event.IniUnit
local _group = _event.IniGroup
if _unit:IsHelicopter() or _group:IsHelicopter() then
local function IsBronco(Group)
local grp = Group -- Wrapper.Group#GROUP
local typename = grp:GetTypeName()
self:T(typename)
if typename == "Bronco-OV-10A" then return true end
return false
end
if _unit:IsHelicopter() or _group:IsHelicopter() or IsBronco(_group) then
self:_AddMedevacMenuItem()
end
@@ -1580,21 +1598,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _overrid
end
-- integrate SRS
if _speak and self.useSRS then
local srstext = SOUNDTEXT:New(_text)
local path = self.SRSPath
local modulation = self.SRSModulation
local channel = self.SRSchannel
local msrs = MSRS:New(path,channel,modulation)
msrs:SetPort(self.SRSport)
msrs:SetLabel("CSAR")
msrs:SetCulture(self.SRSCulture)
msrs:SetCoalition(self.coalition)
msrs:SetVoice(self.SRSVoice)
if self.SRSGPathToCredentials then
msrs:SetGoogle(self.SRSGPathToCredentials)
end
msrs:SetVolume(self.SRSVolume)
msrs:PlaySoundText(srstext, 2)
self.SRSQueue:NewTransmission(_text,nil,self.msrs,nil,2)
end
return self
end
@@ -1894,7 +1898,7 @@ function CSAR:_AddMedevacMenuItem()
self:T(self.lid .. " _AddMedevacMenuItem")
local coalition = self.coalition
local allheligroupset = self.allheligroupset
local allheligroupset = self.allheligroupset -- Core.Set#SET_GROUP
local _allHeliGroups = allheligroupset:GetSetObjects()
-- rebuild units table
@@ -2025,10 +2029,13 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
end
if _group:IsAlive() then
local _radioUnit = _group:GetUnit(1)
local Frequency = _freq -- Freq in Hertz
local Sound = "l10n/DEFAULT/"..self.radioSound
trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight
local _radioUnit = _group:GetUnit(1)
if _radioUnit then
local Frequency = _freq -- Freq in Hertz
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, 1000) -- Beacon in MP only runs for exactly 30secs straight
end
end
return self
end
@@ -2103,7 +2110,11 @@ function CSAR:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
if self.useprefix then
if self.allowbronco then
local prefixes = self.csarPrefix or {}
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterStart()
elseif self.useprefix then
local prefixes = self.csarPrefix or {}
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart()
else
@@ -2113,6 +2124,24 @@ function CSAR:onafterStart(From, Event, To)
if self.wetfeettemplate then
self.usewetfeet = true
end
if self.useSRS then
local path = self.SRSPath
local modulation = self.SRSModulation
local channel = self.SRSchannel
self.msrs = MSRS:New(path,channel,modulation)
self.msrs:SetPort(self.SRSport)
self.msrs:SetLabel("CSAR")
self.msrs:SetCulture(self.SRSCulture)
self.msrs:SetCoalition(self.coalition)
self.msrs:SetVoice(self.SRSVoice)
self.msrs:SetGender(self.SRSGender)
if self.SRSGPathToCredentials then
self.msrs:SetGoogle(self.SRSGPathToCredentials)
end
self.msrs:SetVolume(self.SRSVolume)
self.msrs:SetLabel("CSAR")
self.SRSQueue = MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
return self
end

View File

@@ -31,7 +31,7 @@
-- @field #string tankergroupname Name of the late activated tanker template group.
-- @field Wrapper.Group#GROUP tanker Tanker group.
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon.
-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
-- @field #number TACANchannel TACAN channel. Default 1.
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
@@ -784,10 +784,11 @@ end
-- @param #RECOVERYTANKER self
-- @param #number channel TACAN channel. Default 1.
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
-- @return #RECOVERYTANKER self
function RECOVERYTANKER:SetTACAN(channel, morse)
function RECOVERYTANKER:SetTACAN(channel, morse, mode)
self.TACANchannel=channel or 1
self.TACANmode="Y"
self.TACANmode=mode or "Y"
self.TACANmorse=morse or "TKR"
self.TACANon=true
return self
@@ -1625,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then
-- Schedule TACAN activation.
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
else

View File

@@ -1,28 +1,28 @@
--- **Sound** - Radio transmissions.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * Provide radio functionality to broadcast radio transmissions.
--
--
-- What are radio communications in DCS?
--
--
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
--
--
-- How to supply DCS my own Sound Files?
--
--
-- * Your sound files need to be encoded in **.ogg** or .wav,
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
--
--
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
--
--
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
@@ -37,41 +37,41 @@
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
--
--
-- # RADIO usage
--
--
-- There are 3 steps to a successful radio transmission.
--
--
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
--
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
--
--
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
--
--
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
--
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
--
--
-- What is this power thing?
--
--
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on,
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- @type RADIO
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file played.
@@ -105,12 +105,12 @@ function RADIO:New(Positionable)
-- Inherit base
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil
end
@@ -137,19 +137,19 @@ end
-- @return #RADIO self
function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
self.FileName = FileName
return self
end
end
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
return self
end
@@ -161,34 +161,34 @@ end
-- @return #RADIO self
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency) == "number" then
-- If frequency is in range
-- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
--if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
-- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
local commandSetFrequency={
id = "SetFrequency",
params = {
frequency = self.Frequency,
modulation = self.Modulation,
}
}
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
-- end
--end
end
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
return self
end
@@ -215,13 +215,13 @@ end
-- @return #RADIO self
function RADIO:SetPower(Power)
self:F2(Power)
if type(Power) == "number" then
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
return self
end
@@ -249,7 +249,7 @@ end
-- -- create the broadcaster and attaches it a RADIO
-- local MyUnit = UNIT:FindByName("MyUnit")
-- local MyUnitRadio = MyUnit:GetRadio()
--
--
-- -- add a subtitle for the next transmission, which will be up for 10s
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
@@ -264,14 +264,14 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self.SubtitleDuration = SubtitleDuration
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
-- but it will work with a UNIT or a GROUP anyway.
-- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self
-- @param #string FileName Name of the sound file that will be transmitted.
@@ -281,20 +281,20 @@ end
-- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Power then self:SetPower(Power) end
if Loop then self:SetLoop(Loop) end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @param #string FileName Name of sound file.
@@ -316,20 +316,20 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
end
-- Set frequency.
if Frequency then
if Frequency then
self:SetFrequency(Frequency)
end
-- Set subtitle.
if Subtitle then
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
end
-- Set Looping.
if Loop then
if Loop then
self:SetLoop(Loop)
end
return self
end
@@ -346,7 +346,7 @@ end
-- @return #RADIO self
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
self:T("Broadcasting from a UNIT or a GROUP")
@@ -359,7 +359,7 @@ function RADIO:Broadcast(viatrigger)
subtitle = self.Subtitle,
loop = self.Loop,
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
@@ -368,7 +368,7 @@ function RADIO:Broadcast(viatrigger)
self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
end
return self
end
@@ -380,11 +380,11 @@ end
-- @return #RADIO self
function RADIO:StopBroadcast()
self:F()
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton

View File

@@ -1,9 +1,9 @@
--- **Core** - Makes the radio talk.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * Send text strings using a vocabulary that is converted in spoken language.
-- * Possiblity to implement multiple language.
--
@@ -15,10 +15,10 @@
-- @image Core_Radio.JPG
--- Makes the radio speak.
--
--
-- # RADIOSPEECH usage
--
--
--
--
-- @type RADIOSPEECH
-- @extends Core.RadioQueue#RADIOQUEUE
RADIOSPEECH = {
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
["70"] = { "70", 0.48 },
["80"] = { "80", 0.26 },
["90"] = { "90", 0.36 },
["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 },
["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 },
["chevy"] = { "chevy", 0.35 },
["colt"] = { "colt", 0.35 },
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
["meters"] = { "meters", 0.41 },
["mi"] = { "miles", 0.45 },
["feet"] = { "feet", 0.29 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["returning to base"] = { "returning_to_base", 0.85 },
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
@@ -143,24 +143,24 @@ RADIOSPEECH.Vocabulary.RU = {
["70"] = { "70", 0.68 },
["80"] = { "80", 0.84 },
["90"] = { "90", 0.71 },
["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["градусы"] = { "degrees", 0.5 },
["километры"] = { "kilometers", 0.65 },
@@ -170,10 +170,11 @@ RADIOSPEECH.Vocabulary.RU = {
["метров"] = { "meters", 0.41 },
["m"] = { "meters", 0.41 },
["ноги"] = { "feet", 0.37 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["возвращение на базу"] = { "returning_to_base", 1.40 },
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
["перехват боги"] = { "intercepting_bogeys", 1.22 },
@@ -199,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation)
-- Inherit base
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
self.Language = "EN"
self:BuildTree()
return self
end
@@ -261,7 +262,7 @@ end
function RADIOSPEECH:BuildTree()
self.Speech = {}
for Language, Sentences in pairs( self.Vocabulary ) do
self:I( { Language = Language, Sentences = Sentences })
self.Speech[Language] = {}
@@ -270,7 +271,7 @@ function RADIOSPEECH:BuildTree()
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
end
end
self:I( { Speech = self.Speech } )
return self
@@ -289,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
if Word then
if Word ~= "" and tonumber(Word) == nil then
@@ -301,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
if Speech[Word].Next == nil then
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
else
else
if RemainderSentence and RemainderSentence ~= "" then
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
end
@@ -309,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
end
return RemainderSentence
end
return OriginalSentence
return OriginalSentence
else
return ""
end
end
end
--- Speak a sentence.
@@ -332,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
if Digits then
if Digits ~= "" and tonumber( Digits ) ~= nil then
-- Construct numbers
local Number = tonumber( Digits )
local Multiple = nil
@@ -356,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
end
return RemainderSentence
end
return OriginalSentence
return OriginalSentence
else
return ""
end
@@ -373,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
self:I( { Sentence, Language } )
local Language = Language or "EN"
self:I( { Language = Language } )
-- If there is no node for Speech, then we start at the first nodes of the language.
local Speech = self.Speech[Language]
self:I( { Speech = Speech, Language = Language } )
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
repeat
Sentence = self:SpeakWords( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
Sentence = self:SpeakDigits( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
-- Sentence = self:SpeakSymbols( Sentence, Speech )
--
-- self:I( { Sentence = Sentence } )

View File

@@ -52,8 +52,6 @@
--
-- ===
--
-- ![Banner Image](..\Presentations\ATIS\ATIS_Main.png)
--
-- # The MSRS Concept
--
-- This class allows to broadcast sound files or text via Simple Radio Standalone (SRS).
@@ -143,7 +141,108 @@ MSRS = {
--- MSRS class version.
-- @field #string version
MSRS.version="0.0.6"
MSRS.version="0.1.0"
--- Voices
-- @type Voices
MSRS.Voices = {
Microsoft = {
["Hedda"] = "Microsoft Hedda Desktop", -- de-DE
["Hazel"] = "Microsoft Hazel Desktop", -- en-GB
["David"] = "Microsoft David Desktop", -- en-US
["Zira"] = "Microsoft Zira Desktop", -- en-US
["Hortense"] = "Microsoft Hortense Desktop", --fr-FR
},
Google = {
Standard = {
["en-AU-Standard-A"] = 'en-AU-Standard-A', -- [1] FEMALE
["en-AU-Standard-B"] = 'en-AU-Standard-B', -- [2] MALE
["en-AU-Standard-C"] = 'en-AU-Standard-C', -- [3] FEMALE
["en-AU-Standard-D"] = 'en-AU-Standard-D', -- [4] MALE
["en-IN-Standard-A"] = 'en-IN-Standard-A', -- [5] FEMALE
["en-IN-Standard-B"] = 'en-IN-Standard-B', -- [6] MALE
["en-IN-Standard-C"] = 'en-IN-Standard-C', -- [7] MALE
["en-IN-Standard-D"] = 'en-IN-Standard-D', -- [8] FEMALE
["en-GB-Standard-A"] = 'en-GB-Standard-A', -- [9] FEMALE
["en-GB-Standard-B"] = 'en-GB-Standard-B', -- [10] MALE
["en-GB-Standard-C"] = 'en-GB-Standard-C', -- [11] FEMALE
["en-GB-Standard-D"] = 'en-GB-Standard-D', -- [12] MALE
["en-GB-Standard-F"] = 'en-GB-Standard-F', -- [13] FEMALE
["en-US-Standard-A"] = 'en-US-Standard-A', -- [14] MALE
["en-US-Standard-B"] = 'en-US-Standard-B', -- [15] MALE
["en-US-Standard-C"] = 'en-US-Standard-C', -- [16] FEMALE
["en-US-Standard-D"] = 'en-US-Standard-D', -- [17] MALE
["en-US-Standard-E"] = 'en-US-Standard-E', -- [18] FEMALE
["en-US-Standard-F"] = 'en-US-Standard-F', -- [19] FEMALE
["en-US-Standard-G"] = 'en-US-Standard-G', -- [20] FEMALE
["en-US-Standard-H"] = 'en-US-Standard-H', -- [21] FEMALE
["en-US-Standard-I"] = 'en-US-Standard-I', -- [22] MALE
["en-US-Standard-J"] = 'en-US-Standard-J', -- [23] MALE
["fr-FR-Standard-A"] = "fr-FR-Standard-A", -- Female
["fr-FR-Standard-B"] = "fr-FR-Standard-B", -- Male
["fr-FR-Standard-C"] = "fr-FR-Standard-C", -- Female
["fr-FR-Standard-D"] = "fr-FR-Standard-D", -- Male
["fr-FR-Standard-E"] = "fr-FR-Standard-E", -- Female
["de-DE-Standard-A"] = "de-DE-Standard-A", -- Female
["de-DE-Standard-B"] = "de-DE-Standard-B", -- Male
["de-DE-Standard-C"] = "de-DE-Standard-C", -- Female
["de-DE-Standard-D"] = "de-DE-Standard-D", -- Male
["de-DE-Standard-E"] = "de-DE-Standard-E", -- Male
["de-DE-Standard-F"] = "de-DE-Standard-F", -- Female
["es-ES-Standard-A"] = "es-ES-Standard-A", -- Female
["es-ES-Standard-B"] = "es-ES-Standard-B", -- Male
["es-ES-Standard-C"] = "es-ES-Standard-C", -- Female
["es-ES-Standard-D"] = "es-ES-Standard-D", -- Female
["it-IT-Standard-A"] = "it-IT-Standard-A", -- Female
["it-IT-Standard-B"] = "it-IT-Standard-B", -- Female
["it-IT-Standard-C"] = "it-IT-Standard-C", -- Male
["it-IT-Standard-D"] = "it-IT-Standard-D", -- Male
},
Wavenet = {
["en-AU-Wavenet-A"] = 'en-AU-Wavenet-A', -- [1] FEMALE
["en-AU-Wavenet-B"] = 'en-AU-Wavenet-B', -- [2] MALE
["en-AU-Wavenet-C"] = 'en-AU-Wavenet-C', -- [3] FEMALE
["en-AU-Wavenet-D"] = 'en-AU-Wavenet-D', -- [4] MALE
["en-IN-Wavenet-A"] = 'en-IN-Wavenet-A', -- [5] FEMALE
["en-IN-Wavenet-B"] = 'en-IN-Wavenet-B', -- [6] MALE
["en-IN-Wavenet-C"] = 'en-IN-Wavenet-C', -- [7] MALE
["en-IN-Wavenet-D"] = 'en-IN-Wavenet-D', -- [8] FEMALE
["en-GB-Wavenet-A"] = 'en-GB-Wavenet-A', -- [9] FEMALE
["en-GB-Wavenet-B"] = 'en-GB-Wavenet-B', -- [10] MALE
["en-GB-Wavenet-C"] = 'en-GB-Wavenet-C', -- [11] FEMALE
["en-GB-Wavenet-D"] = 'en-GB-Wavenet-D', -- [12] MALE
["en-GB-Wavenet-F"] = 'en-GB-Wavenet-F', -- [13] FEMALE
["en-US-Wavenet-A"] = 'en-US-Wavenet-A', -- [14] MALE
["en-US-Wavenet-B"] = 'en-US-Wavenet-B', -- [15] MALE
["en-US-Wavenet-C"] = 'en-US-Wavenet-C', -- [16] FEMALE
["en-US-Wavenet-D"] = 'en-US-Wavenet-D', -- [17] MALE
["en-US-Wavenet-E"] = 'en-US-Wavenet-E', -- [18] FEMALE
["en-US-Wavenet-F"] = 'en-US-Wavenet-F', -- [19] FEMALE
["en-US-Wavenet-G"] = 'en-US-Wavenet-G', -- [20] FEMALE
["en-US-Wavenet-H"] = 'en-US-Wavenet-H', -- [21] FEMALE
["en-US-Wavenet-I"] = 'en-US-Wavenet-I', -- [22] MALE
["en-US-Wavenet-J"] = 'en-US-Wavenet-J', -- [23] MALE
["fr-FR-Wavenet-A"] = "fr-FR-Wavenet-A", -- Female
["fr-FR-Wavenet-B"] = "fr-FR-Wavenet-B", -- Male
["fr-FR-Wavenet-C"] = "fr-FR-Wavenet-C", -- Female
["fr-FR-Wavenet-D"] = "fr-FR-Wavenet-D", -- Male
["fr-FR-Wavenet-E"] = "fr-FR-Wavenet-E", -- Female
["de-DE-Wavenet-A"] = "de-DE-Wavenet-A", -- Female
["de-DE-Wavenet-B"] = "de-DE-Wavenet-B", -- Male
["de-DE-Wavenet-C"] = "de-DE-Wavenet-C", -- Female
["de-DE-Wavenet-D"] = "de-DE-Wavenet-D", -- Male
["de-DE-Wavenet-E"] = "de-DE-Wavenet-E", -- Male
["de-DE-Wavenet-F"] = "de-DE-Wavenet-F", -- Female
["es-ES-Wavenet-B"] = "es-ES-Wavenet-B", -- Male
["es-ES-Wavenet-C"] = "es-ES-Wavenet-C", -- Female
["es-ES-Wavenet-D"] = "es-ES-Wavenet-D", -- Female
["it-IT-Wavenet-A"] = "it-IT-Wavenet-A", -- Female
["it-IT-Wavenet-B"] = "it-IT-Wavenet-B", -- Female
["it-IT-Wavenet-C"] = "it-IT-Wavenet-C", -- Male
["it-IT-Wavenet-D"] = "it-IT-Wavenet-D", -- Male
} ,
},
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -181,7 +280,7 @@ function MSRS:New(PathToSRS, Frequency, Modulation, Volume)
self:SetCoalition()
self:SetLabel()
self:SetVolume()
self.lid = string.format("%s-%s | ",self.name,self.version)
self.lid = string.format("%s-%s | ", self.name, self.version)
if not io or not os then
self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
@@ -455,19 +554,8 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
-- Append file.
command=command..' --file="'..tostring(soundfile)..'"'
-- Execute command.
self:_ExecCommand(command)
--[[
command=command.." > bla.txt"
-- Debug output.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
]]
end
@@ -493,16 +581,6 @@ function MSRS:PlaySoundText(SoundText, Delay)
-- Execute command.
self:_ExecCommand(command)
--[[
command=command.." > bla.txt"
-- Debug putput.
self:I(string.format("MSRS PlaySoundfile command=%s", command))
-- Execute SRS command.
local x=os.execute(command)
]]
end
@@ -529,37 +607,48 @@ function MSRS:PlayText(Text, Delay)
-- Execute command.
self:_ExecCommand(command)
--[[
-- Check that length of command is max 255 chars or os.execute() will not work!
if string.len(command)>255 then
-- Create a tmp file.
local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat"
local script = io.open(filename, "w+")
script:write(command.." && exit")
script:close()
-- Play command.
command=string.format("\"%s\"", filename)
-- Play file in 0.05 seconds
timer.scheduleFunction(os.execute, command, timer.getTime()+0.05)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
else
-- Debug output.
self:I(string.format("MSRS Text command=%s", command))
end
-- Execute SRS command.
local x=os.execute(command)
return self
end
--- Play text message via STTS with explicitly specified options.
-- @param #MSRS self
-- @param #string Text Text message.
-- @param #number Delay Delay in seconds, before the message is played.
-- @param #table Frequencies Radio frequencies.
-- @param #table Modulations Radio modulations.
-- @param #string Gender Gender.
-- @param #string Culture Culture.
-- @param #string Voice Voice.
-- @param #number Volume Volume.
-- @param #string Label Label.
-- @return #MSRS self
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label)
else
-- Ensure table.
if Frequencies and type(Frequencies)~="table" then
Frequencies={Frequencies}
end
-- Ensure table.
if Modulations and type(Modulations)~="table" then
Modulations={Modulations}
end
-- Get command line.
local command=self:_GetCommand(Frequencies, Modulations, nil, Gender, Voice, Culture, Volume, nil, nil, Label)
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(Text))
end
-- Execute command.
self:_ExecCommand(command)
]]
end
return self
@@ -760,6 +849,353 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
return command
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Manages radio transmissions.
--
-- The purpose of the MSRSQUEUE class is to manage SRS text-to-speech (TTS) messages using the MSRS class.
-- This can be used to submit multiple TTS messages and the class takes care that they are transmitted one after the other (and not overlapping).
--
-- @type MSRSQUEUE
-- @field #string ClassName Name of the class "MSRSQUEUE".
-- @field #string lid ID for dcs.log.
-- @field #table queue The queue of transmissions.
-- @field #string alias Name of the radio queue.
-- @field #number dt Time interval in seconds for checking the radio queue.
-- @field #number Tlast Time (abs) when the last transmission finished.
-- @field #boolean checking If `true`, the queue update function is scheduled to be called again.
-- @extends Core.Base#BASE
MSRSQUEUE = {
ClassName = "MSRSQUEUE",
Debugmode = nil,
lid = nil,
queue = {},
alias = nil,
dt = nil,
Tlast = nil,
checking = nil,
}
--- Radio queue transmission data.
-- @type MSRSQUEUE.Transmission
-- @field #string text Text to be transmitted.
-- @field Sound.SRS#MSRS msrs MOOSE SRS object.
-- @field #number duration Duration in seconds.
-- @field #table subgroups Groups to send subtitle to.
-- @field #string subtitle Subtitle of the transmission.
-- @field #number subduration Duration of the subtitle being displayed.
-- @field #number frequency Frequency.
-- @field #number modulation Modulation.
-- @field #number Tstarted Mission time (abs) in seconds when the transmission started.
-- @field #boolean isplaying If true, transmission is currently playing.
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
-- @field #number interval Interval in seconds before next transmission.
--- Create a new MSRSQUEUE object for a given radio frequency/modulation.
-- @param #MSRSQUEUE self
-- @param #string alias (Optional) Name of the radio queue.
-- @return #MSRSQUEUE self The MSRSQUEUE object.
function MSRSQUEUE:New(alias)
-- Inherit base
local self=BASE:Inherit(self, BASE:New()) --#MSRSQUEUE
self.alias=alias or "My Radio"
self.dt=1.0
self.lid=string.format("MSRSQUEUE %s | ", self.alias)
return self
end
--- Clear the radio queue.
-- @param #MSRSQUEUE self
-- @return #MSRSQUEUE self The MSRSQUEUE object.
function MSRSQUEUE:Clear()
self:I(self.lid.."Clearning MSRSQUEUE")
self.queue={}
return self
end
--- Add a transmission to the radio queue.
-- @param #MSRSQUEUE self
-- @param #MSRSQUEUE.Transmission transmission The transmission data table.
-- @return #MSRSQUEUE self
function MSRSQUEUE:AddTransmission(transmission)
-- Init.
transmission.isplaying=false
transmission.Tstarted=nil
-- Add to queue.
table.insert(self.queue, transmission)
-- Start checking.
if not self.checking then
self:_CheckRadioQueue()
end
return self
end
--- Create a new transmission and add it to the radio queue.
-- @param #MSRSQUEUE self
-- @param #string text Text to play.
-- @param #number duration Duration in seconds the file lasts. Default is determined by number of characters of the text message.
-- @param Sound.SRS#MSRS msrs MOOSE SRS object.
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @param #table subgroups Groups that should receive the subtiltle.
-- @param #string subtitle Subtitle displayed when the message is played.
-- @param #number subduration Duration [sec] of the subtitle being displayed. Default 5 sec.
-- @param #number frequency Radio frequency if other than MSRS default.
-- @param #number modulation Radio modulation if other then MSRS default.
-- @return #MSRSQUEUE.Transmission Radio transmission table.
function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation)
-- Sanity checks.
if not text then
self:E(self.lid.."ERROR: No text specified.")
return nil
end
if type(text)~="string" then
self:E(self.lid.."ERROR: Text specified is NOT a string.")
return nil
end
-- Create a new transmission object.
local transmission={} --#MSRSQUEUE.Transmission
transmission.text=text
transmission.duration=duration or STTS.getSpeechTime(text)
transmission.msrs=msrs
transmission.Tplay=tstart or timer.getAbsTime()
transmission.subtitle=subtitle
transmission.interval=interval or 0
transmission.frequency=frequency
transmission.modulation=modulation
transmission.subgroups=subgroups
if transmission.subtitle then
transmission.subduration=subduration or transmission.duration
else
transmission.subduration=0 --nil
end
-- Add transmission to queue.
self:AddTransmission(transmission)
return transmission
end
--- Broadcast radio message.
-- @param #MSRSQUEUE self
-- @param #MSRSQUEUE.Transmission transmission The transmission.
function MSRSQUEUE:Broadcast(transmission)
if transmission.frequency then
transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, Gender, Culture, Voice, Volume, Label)
else
transmission.msrs:PlayText(transmission.text)
end
local function texttogroup(gid)
-- Text to group.
trigger.action.outTextForGroup(gid, transmission.subtitle, transmission.subduration, true)
end
if transmission.subgroups and #transmission.subgroups>0 then
for _,_group in pairs(transmission.subgroups) do
local group=_group --Wrapper.Group#GROUP
if group and group:IsAlive() then
local gid=group:GetID()
self:ScheduleOnce(4, texttogroup, gid)
end
end
end
end
--- Calculate total transmission duration of all transmission in the queue.
-- @param #MSRSQUEUE self
-- @return #number Total transmission duration.
function MSRSQUEUE:CalcTransmisstionDuration()
local Tnow=timer.getAbsTime()
local T=0
for _,_transmission in pairs(self.queue) do
local transmission=_transmission --#MSRSQUEUE.Transmission
if transmission.isplaying then
-- Playing for dt seconds.
local dt=Tnow-transmission.Tstarted
T=T+transmission.duration-dt
else
T=T+transmission.duration
end
end
return T
end
--- Check radio queue for transmissions to be broadcasted.
-- @param #MSRSQUEUE self
-- @param #number delay Delay in seconds before checking.
function MSRSQUEUE:_CheckRadioQueue(delay)
-- Transmissions in queue.
local N=#self.queue
-- Debug info.
self:T2(self.lid..string.format("Check radio queue %s: delay=%.3f sec, N=%d, checking=%s", self.alias, delay or 0, N, tostring(self.checking)))
if delay and delay>0 then
-- Delayed call.
self:ScheduleOnce(delay, MSRSQUEUE._CheckRadioQueue, self)
-- Checking on.
self.checking=true
else
-- Check if queue is empty.
if N==0 then
-- Debug info.
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking", self.alias))
-- Queue is now empty. Nothing to else to do. We start checking again, if a transmission is added.
self.checking=false
return
end
-- Get current abs time.
local time=timer.getAbsTime()
-- Checking on.
self.checking=true
-- Set dt.
local dt=self.dt
local playing=false
local next=nil --#MSRSQUEUE.Transmission
local remove=nil
for i,_transmission in ipairs(self.queue) do
local transmission=_transmission --#MSRSQUEUE.Transmission
-- Check if transmission time has passed.
if time>=transmission.Tplay then
-- Check if transmission is currently playing.
if transmission.isplaying then
-- Check if transmission is finished.
if time>=transmission.Tstarted+transmission.duration then
-- Transmission over.
transmission.isplaying=false
-- Remove ith element in queue.
remove=i
-- Store time last transmission finished.
self.Tlast=time
else -- still playing
-- Transmission is still playing.
playing=true
dt=transmission.duration-(time-transmission.Tstarted)
end
else -- not playing yet
local Tlast=self.Tlast
if transmission.interval==nil then
-- Not playing ==> this will be next.
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
-- We got a transmission or one with an interval that is not due yet. No need for anything else.
if next or Tlast then
break
end
end
else
-- Transmission not due yet.
end
end
-- Found a new transmission.
if next~=nil and not playing then
-- Debug info.
self:T(self.lid..string.format("Broadcasting text=\"%s\" at T=%.3f", next.text, time))
-- Call SRS.
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
dt=next.duration
end
-- Remove completed call from queue.
if remove then
-- Remove from queue.
table.remove(self.queue, remove)
N=N-1
-- Check if queue is empty.
if #self.queue==0 then
-- Debug info.
self:T(self.lid..string.format("Check radio queue %s empty ==> disable checking", self.alias))
self.checking=false
return
end
end
-- Check queue.
self:_CheckRadioQueue(dt)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -144,7 +144,7 @@ do -- UserSound
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active group named "PlayerUnit", a human player.
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
--
function USERSOUND:ToUnit( Unit, Delay )

View File

@@ -216,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
self:ReportSummary( EventGroup )
--self:ReportSummary( EventGroup )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
@@ -560,9 +560,11 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false
if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop( self.AssignTasks )
self:ScheduleStop()
-- FF this is not the schedule ID
--self:ScheduleStop( self.AssignTasks )
end
end

View File

@@ -683,6 +683,7 @@ do -- TASK_CARGO_DISPATCHER
-- If no TaskPrefix is given, then "Transport" will be used as the prefix.
-- @param Core.SetCargo#SET_CARGO SetCargo The SetCargo to be transported.
-- @param #string Briefing The briefing of the task transport to be shown to the player.
-- @param #boolean Silent If true don't send a message that a new task is available.
-- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT
-- @usage
--
@@ -705,10 +706,12 @@ do -- TASK_CARGO_DISPATCHER
-- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object.
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
--
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing )
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing, Silent )
self.TransportCount = self.TransportCount + 1
local verbose = Silent or false
local TaskName = string.format( ( TaskPrefix or "Transport" ) .. ".%03d", self.TransportCount )
self.Transport[TaskName] = {}
@@ -717,7 +720,7 @@ do -- TASK_CARGO_DISPATCHER
self.Transport[TaskName].Task = nil
self.Transport[TaskName].TaskPrefix = TaskPrefix
self:ManageTasks()
self:ManageTasks(verbose)
return self.Transport[TaskName] and self.Transport[TaskName].Task
end
@@ -785,10 +788,11 @@ do -- TASK_CARGO_DISPATCHER
--- Assigns tasks to the @{Core.Set#SET_GROUP}.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #boolean Silent Announce new task (nil/false) or not (true).
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_CARGO_DISPATCHER:ManageTasks()
function TASK_CARGO_DISPATCHER:ManageTasks(Silent)
self:F()
local verbose = Silent and true
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
@@ -897,7 +901,7 @@ do -- TASK_CARGO_DISPATCHER
local TaskText = TaskReport:Text(", ")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and not verbose then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end
end

View File

@@ -2,7 +2,7 @@
--
-- **Specific features:**
--
-- * Creates a task to transport @{Cargo.Cargo} to and between deployment zones.
-- * Creates a task to transport #Cargo.Cargo to and between deployment zones.
-- * Derived from the TASK_CARGO class, which is derived from the TASK class.
-- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled.
-- * Co-operation tasking, so a player joins a group of players executing the same task.
@@ -44,7 +44,7 @@
--
-- ===
--
-- Please read through the @{Tasking.Task_Cargo} process to understand the mechanisms of tasking and cargo tasking and handling.
-- Please read through the #Tasking.Task_Cargo process to understand the mechanisms of tasking and cargo tasking and handling.
--
-- Enjoy!
-- FC
@@ -57,7 +57,7 @@
do -- TASK_CARGO_TRANSPORT
--- @type TASK_CARGO_TRANSPORT
-- @type TASK_CARGO_TRANSPORT
-- @extends Tasking.Task_CARGO#TASK_CARGO
--- Orchestrates the task for players to transport cargo to or between deployment zones.
@@ -76,7 +76,7 @@ do -- TASK_CARGO_TRANSPORT
--
-- ## 1.1) Create a command center.
--
-- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
-- First you need to create a command center using the Tasking.CommandCenter#COMMANDCENTER.New constructor.
--
-- local CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" ) -- Create the CommandCenter.
@@ -85,7 +85,7 @@ do -- TASK_CARGO_TRANSPORT
--
-- Tasks work in a mission, which groups these tasks to achieve a joint mission goal.
-- A command center can govern multiple missions.
-- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor.
-- Create a new mission, using the Tasking.Mission#MISSION.New constructor.
--
-- -- Declare the Mission for the Command Center.
-- local Mission = MISSION
@@ -99,7 +99,7 @@ do -- TASK_CARGO_TRANSPORT
-- ## 1.3) Create the transport cargo task.
--
-- So, now that we have a command center and a mission, we now create the transport task.
-- We create the transport task using the @{#TASK_CARGO_TRANSPORT.New}() constructor.
-- We create the transport task using the #TASK_CARGO_TRANSPORT.New constructor.
--
-- Because a transport task will not generate the cargo itself, you'll need to create it first.
-- The cargo in this case will be the downed pilot!
@@ -118,7 +118,7 @@ do -- TASK_CARGO_TRANSPORT
-- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set.
-- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Cargo", "Engineer Team 1", 500 )
--
-- What is also needed, is to have a set of @{Core.Group}s defined that contains the clients of the players.
-- What is also needed, is to have a set of Core.Groups defined that contains the clients of the players.
--
-- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players.
-- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart()
@@ -139,48 +139,48 @@ do -- TASK_CARGO_TRANSPORT
-- By doing this, cargo transport tasking will become a dynamic experience.
--
--
-- # 2) Create a task using the @{Tasking.Task_Cargo_Dispatcher} module.
-- # 2) Create a task using the Tasking.Task_Cargo_Dispatcher module.
--
-- Actually, it is better to **GENERATE** these tasks using the @{Tasking.Task_Cargo_Dispatcher} module.
-- Using the dispatcher module, transport tasks can be created much more easy.
-- Actually, it is better to **GENERATE** these tasks using the Tasking.Task_Cargo_Dispatcher module.
-- Using the dispatcher module, transport tasks can be created easier.
--
-- Find below an example how to use the TASK_CARGO_DISPATCHER class:
--
--
-- -- Find the HQ group.
-- HQ = GROUP:FindByName( "HQ", "Bravo" )
-- -- Find the HQ group.
-- HQ = GROUP:FindByName( "HQ", "Bravo" )
--
-- -- Create the command center with the name "Lima".
-- CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" )
-- -- Create the command center with the name "Lima".
-- CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" )
--
-- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition.
-- Mission = MISSION
-- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE )
-- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition.
-- Mission = MISSION
-- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE )
--
-- -- Create the SET of GROUPs containing clients (players) that will transport the cargo.
-- -- These are have a name that start with "Transport" and are of the "blue" coalition.
-- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart()
-- -- Create the SET of GROUPs containing clients (players) that will transport the cargo.
-- -- These are have a name that start with "Transport" and are of the "blue" coalition.
-- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart()
--
--
-- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups.
-- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups )
-- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups.
-- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups )
--
--
-- -- Here we declare the SET of CARGOs called "Workmaterials".
-- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart()
-- -- Here we declare the SET of CARGOs called "Workmaterials".
-- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart()
--
-- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set.
-- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on.
-- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 )
-- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 )
-- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 )
-- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 )
-- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 )
-- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set.
-- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on.
-- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 )
-- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 )
-- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 )
-- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 )
-- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 )
--
-- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher.
-- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." )
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
-- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher.
-- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." )
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
--
-- # 3) Handle cargo task events.
--
@@ -189,7 +189,7 @@ do -- TASK_CARGO_TRANSPORT
-- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions:
--
-- * **Copy / Paste** the code section into your script.
-- * **Change** the CLASS literal to the task object name you have in your script.
-- * **Change** the "myclass" literal to the task object name you have in your script.
-- * Within the function, you can now **write your own code**!
-- * **IntelliSense** will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored,
-- but you need to declare them as they are automatically provided by the event handling system of MOOSE.
@@ -210,14 +210,13 @@ do -- TASK_CARGO_TRANSPORT
-- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup.
-- You can use this event handler to post messages to players, or provide status updates etc.
--
-- --- CargoPickedUp event handler OnAfter for CLASS.
-- -- @param #CLASS self
-- --- CargoPickedUp event handler OnAfter for "myclass".
-- -- @param #string From A string that contains the "*from state name*" when the event was triggered.
-- -- @param #string Event A string that contains the "*event name*" when the event was triggered.
-- -- @param #string To A string that contains the "*to state name*" when the event was triggered.
-- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo.
-- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object!
-- function CLASS:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo )
-- function myclass:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo )
--
-- -- Write here your own code.
--
@@ -240,15 +239,14 @@ do -- TASK_CARGO_TRANSPORT
-- You can use this event handler to post messages to players, or provide status updates etc.
--
--
-- --- CargoDeployed event handler OnAfter for CLASS.
-- -- @param #CLASS self
-- --- CargoDeployed event handler OnAfter foR "myclass".
-- -- @param #string From A string that contains the "*from state name*" when the event was triggered.
-- -- @param #string Event A string that contains the "*event name*" when the event was triggered.
-- -- @param #string To A string that contains the "*to state name*" when the event was triggered.
-- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo.
-- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object!
-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
-- function CLASS:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone )
-- function myclass:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone )
--
-- -- Write here your own code.
--

View File

@@ -497,7 +497,7 @@ ENUMS.ReportingName =
Mudhen = "F-15E",
Viper = "F-16",
Phantom = "F-4E",
Tiger = "F-5", -- was thinking to name this MiG-25 ;)
Tiger = "F-5", -- was thinkg to name this MiG-25 ;)
Sabre = "F-86",
Hornet = "A-18", -- avoiding the slash
Hawk = "Hawk",
@@ -509,7 +509,7 @@ ENUMS.ReportingName =
Atlas = "A400",
Lancer = "B1-B",
Stratofortress = "B-52H",
Hercules = "C-130",
Hercules = "C-130",
Super_Hercules = "Hercules",
Globemaster = "C-17",
Greyhound = "C-2A",

View File

@@ -20,11 +20,11 @@ do
-- @type FIFO
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string version Version of FiFo
-- @field #number counter
-- @field #number pointer
-- @field #table stackbypointer
-- @field #table stackbyid
-- @field #string version Version of FiFo.
-- @field #number counter Counter.
-- @field #number pointer Pointer.
-- @field #table stackbypointer Stack by pointer.
-- @field #table stackbyid Stack by ID.
-- @extends Core.Base#BASE
---
@@ -45,12 +45,12 @@ FIFO = {
stackbyid = {}
}
--- Instantiate a new FIFO Stack
--- Instantiate a new FIFO Stack.
-- @param #FIFO self
-- @return #FIFO self
function FIFO:New()
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
local self=BASE:Inherit(self, BASE:New()) --#FIFO
self.pointer = 0
self.counter = 0
self.stackbypointer = {}
@@ -62,7 +62,7 @@ function FIFO:New()
return self
end
--- Empty FIFO Stack
--- Empty FIFO Stack.
-- @param #FIFO self
-- @return #FIFO self
function FIFO:Clear()
@@ -77,7 +77,7 @@ function FIFO:Clear()
return self
end
--- FIFO Push Object to Stack
--- FIFO Push Object to Stack.
-- @param #FIFO self
-- @param #table Object
-- @param #string UniqueID (optional) - will default to current pointer + 1. Note - if you intend to use `FIFO:GetIDStackSorted()` keep the UniqueID numerical!
@@ -97,7 +97,7 @@ function FIFO:Push(Object,UniqueID)
return self
end
--- FIFO Pull Object from Stack
--- FIFO Pull Object from Stack.
-- @param #FIFO self
-- @return #table Object or nil if stack is empty
function FIFO:Pull()

View File

@@ -7,7 +7,7 @@
-- ### Author: **TAW CougarNL**, *funkyfranky*
--
-- @module Utilities.PROFILER
-- @image MOOSE.JPG
-- @image Utils_Profiler.jpg
--- PROFILER class.
-- @type PROFILER
@@ -24,7 +24,8 @@
-- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value.
-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler".
-- @field #string fileNameSuffix Output file name prefix, e.g. "txt"
--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? *
--- *The emperor counsels simplicity.* *First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature?*
--
-- ===
--
@@ -33,11 +34,24 @@
-- # The PROFILER Concept
--
-- Profile your lua code. This tells you, which functions are called very often and which consume most real time.
-- With this information you can optimize the performance of your code.
-- With this information you can optimize the perfomance of your code.
--
-- # Prerequisites
--
-- The modules **os** and **lfs** need to be de-sanitized.
-- The modules **os**, **io** and **lfs** need to be desanizied. Comment out the lines
--
-- --sanitizeModule('os')
-- --sanitizeModule('io')
-- --sanitizeModule('lfs')
--
-- in your *"DCS World OpenBeta/Scripts/MissionScripting.lua"* file.
--
-- But be aware that these changes can make you system vulnerable to attacks.
--
-- # Disclaimer
--
-- **Profiling itself is CPU expensive!** Don't use this when you want to fly or host a mission.
--
--
-- # Start
--
@@ -123,12 +137,12 @@ function PROFILER.Start( Delay, Duration )
go = false
end
if not io then
env.error( "ERROR: Profiler needs io to be de-sanitized!" )
go = false
env.error("ERROR: Profiler needs io to be desanitized!")
go=false
end
if not lfs then
env.error( "ERROR: Profiler needs lfs to be de-sanitized!" )
go = false
env.error("ERROR: Profiler needs lfs to be desanitized!")
go=false
end
if not go then
return
@@ -139,11 +153,11 @@ function PROFILER.Start( Delay, Duration )
else
-- Set start time.
PROFILER.TstartGame = timer.getTime()
PROFILER.TstartOS = os.clock()
PROFILER.TstartGame=timer.getTime()
PROFILER.TstartOS=os.clock()
-- Add event handler.
world.addEventHandler( PROFILER.eventHandler )
world.addEventHandler(PROFILER.eventHandler)
-- Info in log.
env.info( '############################ Profiler Started ############################' )
@@ -152,18 +166,19 @@ function PROFILER.Start( Delay, Duration )
else
env.info( string.format( "- Will be stopped when mission ends" ) )
end
env.info( string.format( "- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS ) )
env.info( string.format( "- Total function time threshold %.3f sec", PROFILER.ThreshTtot ) )
env.info( string.format( "- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename( PROFILER.fileNameSuffix ) ) )
env.info( string.format( "- Output file \"%s\" in CSV format", PROFILER.getfilename( "csv" ) ) )
env.info( '###############################################################################' )
env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS))
env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot))
env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix)))
env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv")))
env.info('###############################################################################')
-- Message on screen
local duration = Duration or 600
trigger.action.outText( "### Profiler running ###", duration )
local duration=Duration or 600
trigger.action.outText("### Profiler running ###", duration)
-- Set hook.
debug.sethook( PROFILER.hook, "cr" )
debug.sethook(PROFILER.hook, "cr")
-- Auto stop profiler.
if Duration then
@@ -181,20 +196,29 @@ function PROFILER.Stop( Delay )
if Delay and Delay > 0 then
BASE:ScheduleOnce( Delay, PROFILER.Stop )
end
end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay, PROFILER.Stop)
else
-- Remove hook.
debug.sethook()
-- Run time game.
local runTimeGame = timer.getTime() - PROFILER.TstartGame
local runTimeGame=timer.getTime()-PROFILER.TstartGame
-- Run time real OS.
local runTimeOS = os.clock() - PROFILER.TstartOS
local runTimeOS=os.clock()-PROFILER.TstartOS
-- Show info.
PROFILER.showInfo( runTimeGame, runTimeOS )
PROFILER.showInfo(runTimeGame, runTimeOS)
end
@@ -213,34 +237,34 @@ end
--- Debug hook.
-- @param #table event Event.
function PROFILER.hook( event )
function PROFILER.hook(event)
local f = debug.getinfo( 2, "f" ).func
local f=debug.getinfo(2, "f").func
if event == 'call' then
if event=='call' then
if PROFILER.Counters[f] == nil then
if PROFILER.Counters[f]==nil then
PROFILER.Counters[f] = 1
PROFILER.dInfo[f] = debug.getinfo( 2, "Sn" )
PROFILER.Counters[f]=1
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
if PROFILER.fTimeTotal[f] == nil then
PROFILER.fTimeTotal[f] = 0
if PROFILER.fTimeTotal[f]==nil then
PROFILER.fTimeTotal[f]=0
end
else
PROFILER.Counters[f] = PROFILER.Counters[f] + 1
end
if PROFILER.fTime[f] == nil then
PROFILER.fTime[f] = os.clock()
if PROFILER.fTime[f]==nil then
PROFILER.fTime[f]=os.clock()
end
elseif (event == 'return') then
elseif (event=='return') then
if PROFILER.fTime[f] ~= nil then
PROFILER.fTimeTotal[f] = PROFILER.fTimeTotal[f] + (os.clock() - PROFILER.fTime[f])
PROFILER.fTime[f] = nil
if PROFILER.fTime[f]~=nil then
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
PROFILER.fTime[f]=nil
end
end
@@ -259,9 +283,9 @@ end
-- @return #number Function time in seconds.
function PROFILER.getData( func )
local n = PROFILER.dInfo[func]
local n=PROFILER.dInfo[func]
if n.what == "C" then
if n.what=="C" then
return n.name, "?", "?", PROFILER.fTimeTotal[func]
end
@@ -282,20 +306,20 @@ end
function PROFILER.showTable( data, f, runTimeGame )
-- Loop over data.
for i = 1, #data do
local t = data[i] -- #PROFILER.Data
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps = t.count / runTimeGame
local cps=t.count/runTimeGame
local threshCPS = cps >= PROFILER.ThreshCPS
local threshTot = t.tm >= PROFILER.ThreshTtot
local threshCPS=cps>=PROFILER.ThreshCPS
local threshTot=t.tm>=PROFILER.ThreshTtot
if threshCPS and threshTot then
-- Output
local text = string.format( "%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm / runTimeGame * 100, t.tm / t.count, tostring( t.src ), tostring( t.line ) )
PROFILER._flog( f, text )
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
PROFILER._flog(f, text)
end
end
@@ -312,19 +336,19 @@ function PROFILER.printCSV( data, runTimeGame )
local g = io.open( file, 'w' )
-- Header.
local text = "Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write( text .. "\r\n" )
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write(text.."\r\n")
-- Loop over data.
for i = 1, #data do
local t = data[i] -- #PROFILER.Data
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps = t.count / runTimeGame
-- Output
local txt = string.format( "%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm / runTimeGame * 100, t.tm / t.count, tostring( t.src ), tostring( t.line ) )
g:write( txt .. "\r\n" )
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
g:write(txt.."\r\n")
end
@@ -335,15 +359,15 @@ end
--- Write info to output file.
-- @param #string ext Extension.
-- @return #string File name.
function PROFILER.getfilename( ext )
function PROFILER.getfilename(ext)
local dir = lfs.writedir() .. [[Logs\]]
local dir=lfs.writedir()..[[Logs\]]
ext = ext or PROFILER.fileNameSuffix
ext=ext or PROFILER.fileNameSuffix
local file = dir .. PROFILER.fileNamePrefix .. "." .. ext
local file=dir..PROFILER.fileNamePrefix.."."..ext
if not UTILS.FileExists( file ) then
if not UTILS.FileExists(file) then
return file
end
@@ -365,34 +389,39 @@ end
function PROFILER.showInfo( runTimeGame, runTimeOS )
-- Output file.
local file = PROFILER.getfilename( PROFILER.fileNameSuffix )
local f = io.open( file, 'w' )
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
local f=io.open(file, 'w')
-- Gather data.
local Ttot = 0
local Calls = 0
local Ttot=0
local Calls=0
local t = {}
local t={}
local tcopy = nil -- #PROFILER.Data
local tserialize = nil -- #PROFILER.Data
local tforgen = nil -- #PROFILER.Data
local tpairs = nil -- #PROFILER.Data
local tcopy=nil --#PROFILER.Data
local tserialize=nil --#PROFILER.Data
local tforgen=nil --#PROFILER.Data
local tpairs=nil --#PROFILER.Data
for func, count in pairs( PROFILER.Counters ) do
local s, src, line, tm = PROFILER.getData( func )
for func, count in pairs(PROFILER.Counters) do
if PROFILER.logUnknown == true then
if s == nil then
s = "<Unknown>"
end
local s,src,line,tm=PROFILER.getData(func)
if PROFILER.logUnknown==true then
if s==nil then s="<Unknown>" end
end
if s ~= nil then
if s~=nil then
-- Profile data.
local T = { func = s, src = src, line = line, count = count, tm = tm } -- #PROFILER.Data
local T=
{ func=s,
src=src,
line=line,
count=count,
tm=tm,
} --#PROFILER.Data
-- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data.
if s == "_copy" then
@@ -406,119 +435,113 @@ function PROFILER.showInfo( runTimeGame, runTimeOS )
if tserialize == nil then
tserialize = T
else
tserialize.count = tserialize.count + T.count
tserialize.tm = tserialize.tm + T.tm
tserialize.count=tserialize.count+T.count
tserialize.tm=tserialize.tm+T.tm
end
elseif s == "(for generator)" then
if tforgen == nil then
tforgen = T
elseif s=="(for generator)" then
if tforgen==nil then
tforgen=T
else
tforgen.count = tforgen.count + T.count
tforgen.tm = tforgen.tm + T.tm
tforgen.count=tforgen.count+T.count
tforgen.tm=tforgen.tm+T.tm
end
elseif s == "pairs" then
if tpairs == nil then
tpairs = T
elseif s=="pairs" then
if tpairs==nil then
tpairs=T
else
tpairs.count = tpairs.count + T.count
tpairs.tm = tpairs.tm + T.tm
tpairs.count=tpairs.count+T.count
tpairs.tm=tpairs.tm+T.tm
end
else
table.insert( t, T )
end
-- Total function time.
Ttot = Ttot + tm
Ttot=Ttot+tm
-- Total number of calls.
Calls = Calls + count
Calls=Calls+count
end
end
-- Add special cases.
-- Add special cases.
if tcopy then
table.insert( t, tcopy )
end
if tserialize then
table.insert( t, tserialize )
table.insert(t, tserialize)
end
if tforgen then
table.insert( t, tforgen )
end
if tpairs then
table.insert( t, tpairs )
table.insert(t, tpairs)
end
env.info( '############################ Profiler Stopped ############################' )
env.info( string.format( "* Runtime Game : %s = %d sec", UTILS.SecondsToClock( runTimeGame, true ), runTimeGame ) )
env.info( string.format( "* Runtime Real : %s = %d sec", UTILS.SecondsToClock( runTimeOS, true ), runTimeOS ) )
env.info( string.format( "* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock( Ttot, true ), Ttot, Ttot / runTimeGame * 100 ) )
env.info( string.format( "* Total functions : %d", #t ) )
env.info( string.format( "* Total func calls : %d", Calls ) )
env.info( string.format( "* Writing to file : \"%s\"", file ) )
env.info( string.format( "* Writing to file : \"%s\"", PROFILER.getfilename( "csv" ) ) )
env.info( "##############################################################################" )
env.info('############################ Profiler Stopped ############################')
env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
env.info(string.format("* Total functions : %d", #t))
env.info(string.format("* Total func calls : %d", Calls))
env.info(string.format("* Writing to file : \"%s\"", file))
env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv")))
env.info("##############################################################################")
-- Sort by total time.
table.sort( t, function( a, b )
return a.tm > b.tm
end )
table.sort(t, function(a,b) return a.tm>b.tm end)
-- Write data.
PROFILER._flog( f, "" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "" )
PROFILER._flog( f, "-------------------------" )
PROFILER._flog( f, "---- Profiler Report ----" )
PROFILER._flog( f, "-------------------------" )
PROFILER._flog( f, "" )
PROFILER._flog( f, string.format( "* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock( runTimeGame, true ), runTimeGame ) )
PROFILER._flog( f, string.format( "* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock( runTimeOS, true ), runTimeOS ) )
PROFILER._flog( f, string.format( "* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock( Ttot, true ), Ttot, Ttot / runTimeGame * 100 ) )
PROFILER._flog( f, "" )
PROFILER._flog( f, string.format( "* Total functions = %d", #t ) )
PROFILER._flog( f, string.format( "* Total func calls = %d", Calls ) )
PROFILER._flog( f, "" )
PROFILER._flog( f, string.format( "* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS ) )
PROFILER._flog( f, string.format( "* Total func time threshold = %.3f sec", PROFILER.ThreshTtot ) )
PROFILER._flog( f, "" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "" )
PROFILER.showTable( t, f, runTimeGame )
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"---- Profiler Report ----")
PROFILER._flog(f,"-------------------------")
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Total functions = %d", #t))
PROFILER._flog(f,string.format("* Total func calls = %d", Calls))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS))
PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot))
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Sort by number of calls.
table.sort( t, function( a, b )
return a.tm / a.count > b.tm / b.count
end )
table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end)
-- Detailed data.
PROFILER._flog( f, "" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "" )
PROFILER._flog( f, "--------------------------------------" )
PROFILER._flog( f, "---- Data Sorted by Time per Call ----" )
PROFILER._flog( f, "--------------------------------------" )
PROFILER._flog( f, "" )
PROFILER.showTable( t, f, runTimeGame )
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Time per Call ----")
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Sort by number of calls.
table.sort( t, function( a, b )
return a.count > b.count
end )
table.sort(t, function(a,b) return a.count>b.count end)
-- Detailed data.
PROFILER._flog( f, "" )
PROFILER._flog( f, "************************************************************************************************************************" )
PROFILER._flog( f, "" )
PROFILER._flog( f, "------------------------------------" )
PROFILER._flog( f, "---- Data Sorted by Total Calls ----" )
PROFILER._flog( f, "------------------------------------" )
PROFILER._flog( f, "" )
PROFILER.showTable( t, f, runTimeGame )
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"---- Data Sorted by Total Calls ----")
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Closing.
PROFILER._flog( f, "" )

View File

@@ -494,330 +494,343 @@ routines.ground = {}
routines.fixedWing = {}
routines.heli = {}
routines.ground.buildWP = function( point, overRideForm, overRideSpeed )
routines.ground.buildWP = function(point, overRideForm, overRideSpeed)
local wp = {}
wp.x = point.x
local wp = {}
wp.x = point.x
if point.z then
wp.y = point.z
else
wp.y = point.y
end
local form, speed
if point.z then
wp.y = point.z
else
wp.y = point.y
end
local form, speed
if point.speed and not overRideSpeed then
wp.speed = point.speed
elseif type( overRideSpeed ) == 'number' then
wp.speed = overRideSpeed
else
wp.speed = routines.utils.kmphToMps( 20 )
end
if point.speed and not overRideSpeed then
wp.speed = point.speed
elseif type(overRideSpeed) == 'number' then
wp.speed = overRideSpeed
else
wp.speed = routines.utils.kmphToMps(20)
end
if point.form and not overRideForm then
form = point.form
else
form = overRideForm
end
if point.form and not overRideForm then
form = point.form
else
form = overRideForm
end
if not form then
wp.action = 'Cone'
else
form = string.lower( form )
if form == 'off_road' or form == 'off road' then
wp.action = 'Off Road'
elseif form == 'on_road' or form == 'on road' then
wp.action = 'On Road'
elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest' then
wp.action = 'Rank'
elseif form == 'cone' then
wp.action = 'Cone'
elseif form == 'diamond' then
wp.action = 'Diamond'
elseif form == 'vee' then
wp.action = 'Vee'
elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then
wp.action = 'EchelonL'
elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then
wp.action = 'EchelonR'
else
wp.action = 'Cone' -- if nothing matched
end
end
if not form then
wp.action = 'Cone'
else
form = string.lower(form)
if form == 'off_road' or form == 'off road' then
wp.action = 'Off Road'
elseif form == 'on_road' or form == 'on road' then
wp.action = 'On Road'
elseif form == 'rank' or form == 'line_abrest' or form == 'line abrest' or form == 'lineabrest'then
wp.action = 'Rank'
elseif form == 'cone' then
wp.action = 'Cone'
elseif form == 'diamond' then
wp.action = 'Diamond'
elseif form == 'vee' then
wp.action = 'Vee'
elseif form == 'echelon_left' or form == 'echelon left' or form == 'echelonl' then
wp.action = 'EchelonL'
elseif form == 'echelon_right' or form == 'echelon right' or form == 'echelonr' then
wp.action = 'EchelonR'
else
wp.action = 'Cone' -- if nothing matched
end
end
wp.type = 'Turning Point'
return wp
wp.type = 'Turning Point'
return wp
end
routines.fixedWing.buildWP = function( point, WPtype, speed, alt, altType )
routines.fixedWing.buildWP = function(point, WPtype, speed, alt, altType)
local wp = {}
wp.x = point.x
local wp = {}
wp.x = point.x
if point.z then
wp.y = point.z
else
wp.y = point.y
end
if point.z then
wp.y = point.z
else
wp.y = point.y
end
if alt and type( alt ) == 'number' then
wp.alt = alt
else
wp.alt = 2000
end
if alt and type(alt) == 'number' then
wp.alt = alt
else
wp.alt = 2000
end
if altType then
altType = string.lower( altType )
if altType == 'radio' or 'agl' then
wp.alt_type = 'RADIO'
elseif altType == 'baro' or 'asl' then
wp.alt_type = 'BARO'
end
else
wp.alt_type = 'RADIO'
end
if altType then
altType = string.lower(altType)
if altType == 'radio' or 'agl' then
wp.alt_type = 'RADIO'
elseif altType == 'baro' or 'asl' then
wp.alt_type = 'BARO'
end
else
wp.alt_type = 'RADIO'
end
if point.speed then
speed = point.speed
end
if point.speed then
speed = point.speed
end
if point.type then
WPtype = point.type
end
if point.type then
WPtype = point.type
end
if not speed then
wp.speed = routines.utils.kmphToMps( 500 )
else
wp.speed = speed
end
if not speed then
wp.speed = routines.utils.kmphToMps(500)
else
wp.speed = speed
end
if not WPtype then
wp.action = 'Turning Point'
else
WPtype = string.lower( WPtype )
if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
wp.action = 'Fly Over Point'
elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
wp.action = 'Turning Point'
else
wp.action = 'Turning Point'
end
end
if not WPtype then
wp.action = 'Turning Point'
else
WPtype = string.lower(WPtype)
if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
wp.action = 'Fly Over Point'
elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
wp.action = 'Turning Point'
else
wp.action = 'Turning Point'
end
end
wp.type = 'Turning Point'
return wp
wp.type = 'Turning Point'
return wp
end
routines.heli.buildWP = function( point, WPtype, speed, alt, altType )
routines.heli.buildWP = function(point, WPtype, speed, alt, altType)
local wp = {}
wp.x = point.x
local wp = {}
wp.x = point.x
if point.z then
wp.y = point.z
else
wp.y = point.y
end
if point.z then
wp.y = point.z
else
wp.y = point.y
end
if alt and type( alt ) == 'number' then
wp.alt = alt
else
wp.alt = 500
end
if alt and type(alt) == 'number' then
wp.alt = alt
else
wp.alt = 500
end
if altType then
altType = string.lower( altType )
if altType == 'radio' or 'agl' then
wp.alt_type = 'RADIO'
elseif altType == 'baro' or 'asl' then
wp.alt_type = 'BARO'
end
else
wp.alt_type = 'RADIO'
end
if altType then
altType = string.lower(altType)
if altType == 'radio' or 'agl' then
wp.alt_type = 'RADIO'
elseif altType == 'baro' or 'asl' then
wp.alt_type = 'BARO'
end
else
wp.alt_type = 'RADIO'
end
if point.speed then
speed = point.speed
end
if point.speed then
speed = point.speed
end
if point.type then
WPtype = point.type
end
if point.type then
WPtype = point.type
end
if not speed then
wp.speed = routines.utils.kmphToMps( 200 )
else
wp.speed = speed
end
if not speed then
wp.speed = routines.utils.kmphToMps(200)
else
wp.speed = speed
end
if not WPtype then
wp.action = 'Turning Point'
else
WPtype = string.lower( WPtype )
if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
wp.action = 'Fly Over Point'
elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
wp.action = 'Turning Point'
else
wp.action = 'Turning Point'
end
end
if not WPtype then
wp.action = 'Turning Point'
else
WPtype = string.lower(WPtype)
if WPtype == 'flyover' or WPtype == 'fly over' or WPtype == 'fly_over' then
wp.action = 'Fly Over Point'
elseif WPtype == 'turningpoint' or WPtype == 'turning point' or WPtype == 'turning_point' then
wp.action = 'Turning Point'
else
wp.action = 'Turning Point'
end
end
wp.type = 'Turning Point'
return wp
wp.type = 'Turning Point'
return wp
end
routines.groupToRandomPoint = function( vars )
local group = vars.group -- Required
local point = vars.point -- required
local radius = vars.radius or 0
local innerRadius = vars.innerRadius
local form = vars.form or 'Cone'
local heading = vars.heading or math.random() * 2 * math.pi
local headingDegrees = vars.headingDegrees
local speed = vars.speed or routines.utils.kmphToMps( 20 )
routines.groupToRandomPoint = function(vars)
local group = vars.group --Required
local point = vars.point --required
local radius = vars.radius or 0
local innerRadius = vars.innerRadius
local form = vars.form or 'Cone'
local heading = vars.heading or math.random()*2*math.pi
local headingDegrees = vars.headingDegrees
local speed = vars.speed or routines.utils.kmphToMps(20)
local useRoads
if not vars.disableRoads then
useRoads = true
else
useRoads = false
end
local path = {}
local useRoads
if not vars.disableRoads then
useRoads = true
else
useRoads = false
end
if headingDegrees then
heading = headingDegrees * math.pi / 180
end
local path = {}
if heading >= 2 * math.pi then
heading = heading - 2 * math.pi
end
if headingDegrees then
heading = headingDegrees*math.pi/180
end
local rndCoord = routines.getRandPointInCircle( point, radius, innerRadius )
if heading >= 2*math.pi then
heading = heading - 2*math.pi
end
local offset = {}
local posStart = routines.getLeadPos( group )
local rndCoord = routines.getRandPointInCircle(point, radius, innerRadius)
offset.x = routines.utils.round( math.sin( heading - (math.pi / 2) ) * 50 + rndCoord.x, 3 )
offset.z = routines.utils.round( math.cos( heading + (math.pi / 2) ) * 50 + rndCoord.y, 3 )
path[#path + 1] = routines.ground.buildWP( posStart, form, speed )
local offset = {}
local posStart = routines.getLeadPos(group)
if useRoads == true and ((point.x - posStart.x) ^ 2 + (point.z - posStart.z) ^ 2) ^ 0.5 > radius * 1.3 then
path[#path + 1] = routines.ground.buildWP( { ['x'] = posStart.x + 11, ['z'] = posStart.z + 11 }, 'off_road', speed )
path[#path + 1] = routines.ground.buildWP( posStart, 'on_road', speed )
path[#path + 1] = routines.ground.buildWP( offset, 'on_road', speed )
else
path[#path + 1] = routines.ground.buildWP( { ['x'] = posStart.x + 25, ['z'] = posStart.z + 25 }, form, speed )
end
offset.x = routines.utils.round(math.sin(heading - (math.pi/2)) * 50 + rndCoord.x, 3)
offset.z = routines.utils.round(math.cos(heading + (math.pi/2)) * 50 + rndCoord.y, 3)
path[#path + 1] = routines.ground.buildWP(posStart, form, speed)
path[#path + 1] = routines.ground.buildWP( offset, form, speed )
path[#path + 1] = routines.ground.buildWP( rndCoord, form, speed )
routines.goRoute( group, path )
if useRoads == true and ((point.x - posStart.x)^2 + (point.z - posStart.z)^2)^0.5 > radius * 1.3 then
path[#path + 1] = routines.ground.buildWP({['x'] = posStart.x + 11, ['z'] = posStart.z + 11}, 'off_road', speed)
path[#path + 1] = routines.ground.buildWP(posStart, 'on_road', speed)
path[#path + 1] = routines.ground.buildWP(offset, 'on_road', speed)
else
path[#path + 1] = routines.ground.buildWP({['x'] = posStart.x + 25, ['z'] = posStart.z + 25}, form, speed)
end
path[#path + 1] = routines.ground.buildWP(offset, form, speed)
path[#path + 1] = routines.ground.buildWP(rndCoord, form, speed)
routines.goRoute(group, path)
return
end
routines.groupRandomDistSelf = function( gpData, dist, form, heading, speed )
local pos = routines.getLeadPos( gpData )
local fakeZone = {}
fakeZone.radius = dist or math.random( 300, 1000 )
fakeZone.point = { x = pos.x, y, pos.y, z = pos.z }
routines.groupToRandomZone( gpData, fakeZone, form, heading, speed )
routines.groupRandomDistSelf = function(gpData, dist, form, heading, speed)
local pos = routines.getLeadPos(gpData)
local fakeZone = {}
fakeZone.radius = dist or math.random(300, 1000)
fakeZone.point = {x = pos.x, y = pos.y, z = pos.z}
routines.groupToRandomZone(gpData, fakeZone, form, heading, speed)
return
end
routines.groupToRandomZone = function( gpData, zone, form, heading, speed )
if type( gpData ) == 'string' then
gpData = Group.getByName( gpData )
end
routines.groupToRandomZone = function(gpData, zone, form, heading, speed)
if type(gpData) == 'string' then
gpData = Group.getByName(gpData)
end
if type( zone ) == 'string' then
zone = trigger.misc.getZone( zone )
elseif type( zone ) == 'table' and not zone.radius then
zone = trigger.misc.getZone( zone[math.random( 1, #zone )] )
end
if type(zone) == 'string' then
zone = trigger.misc.getZone(zone)
elseif type(zone) == 'table' and not zone.radius then
zone = trigger.misc.getZone(zone[math.random(1, #zone)])
end
if speed then
speed = routines.utils.kmphToMps( speed )
end
if speed then
speed = routines.utils.kmphToMps(speed)
end
local vars = {}
vars.group = gpData
vars.radius = zone.radius
vars.form = form
vars.headingDegrees = heading
vars.speed = speed
vars.point = routines.utils.zoneToVec3( zone )
local vars = {}
vars.group = gpData
vars.radius = zone.radius
vars.form = form
vars.headingDegrees = heading
vars.speed = speed
vars.point = routines.utils.zoneToVec3(zone)
routines.groupToRandomPoint( vars )
routines.groupToRandomPoint(vars)
return
end
routines.isTerrainValid = function( coord, terrainTypes ) -- vec2/3 and enum or table of acceptable terrain types
if coord.z then
coord.y = coord.z
end
local typeConverted = {}
routines.isTerrainValid = function(coord, terrainTypes) -- vec2/3 and enum or table of acceptable terrain types
if coord.z then
coord.y = coord.z
end
local typeConverted = {}
if type( terrainTypes ) == 'string' then -- if its a string it does this check
for constId, constData in pairs( land.SurfaceType ) do
if string.lower( constId ) == string.lower( terrainTypes ) or string.lower( constData ) == string.lower( terrainTypes ) then
table.insert( typeConverted, constId )
end
end
elseif type( terrainTypes ) == 'table' then -- if its a table it does this check
for typeId, typeData in pairs( terrainTypes ) do
for constId, constData in pairs( land.SurfaceType ) do
if string.lower( constId ) == string.lower( typeData ) or string.lower( constData ) == string.lower( typeId ) then
table.insert( typeConverted, constId )
end
end
end
end
for validIndex, validData in pairs( typeConverted ) do
if land.getSurfaceType( coord ) == land.SurfaceType[validData] then
return true
end
end
return false
if type(terrainTypes) == 'string' then -- if its a string it does this check
for constId, constData in pairs(land.SurfaceType) do
if string.lower(constId) == string.lower(terrainTypes) or string.lower(constData) == string.lower(terrainTypes) then
table.insert(typeConverted, constId)
end
end
elseif type(terrainTypes) == 'table' then -- if its a table it does this check
for typeId, typeData in pairs(terrainTypes) do
for constId, constData in pairs(land.SurfaceType) do
if string.lower(constId) == string.lower(typeData) or string.lower(constData) == string.lower(typeId) then
table.insert(typeConverted, constId)
end
end
end
end
for validIndex, validData in pairs(typeConverted) do
if land.getSurfaceType(coord) == land.SurfaceType[validData] then
return true
end
end
return false
end
routines.groupToPoint = function( gpData, point, form, heading, speed, useRoads )
if type( point ) == 'string' then
point = trigger.misc.getZone( point )
end
if speed then
speed = routines.utils.kmphToMps( speed )
end
routines.groupToPoint = function(gpData, point, form, heading, speed, useRoads)
if type(point) == 'string' then
point = trigger.misc.getZone(point)
end
if speed then
speed = routines.utils.kmphToMps(speed)
end
local vars = {}
vars.group = gpData
vars.form = form
vars.headingDegrees = heading
vars.speed = speed
vars.disableRoads = useRoads
vars.point = routines.utils.zoneToVec3( point )
routines.groupToRandomPoint( vars )
local vars = {}
vars.group = gpData
vars.form = form
vars.headingDegrees = heading
vars.speed = speed
vars.disableRoads = useRoads
vars.point = routines.utils.zoneToVec3(point)
routines.groupToRandomPoint(vars)
return
end
routines.getLeadPos = function( group )
if type( group ) == 'string' then -- group name
group = Group.getByName( group )
end
local units = group:getUnits()
routines.getLeadPos = function(group)
if type(group) == 'string' then -- group name
group = Group.getByName(group)
end
local leader = units[1]
if not leader then -- SHOULD be good, but if there is a bug, this code future-proofs it then.
local lowestInd = math.huge
for ind, unit in pairs( units ) do
if ind < lowestInd then
lowestInd = ind
leader = unit
end
end
end
if leader and Unit.isExist( leader ) then -- maybe a little too paranoid now...
return leader:getPosition().p
end
local units = group:getUnits()
local leader = units[1]
if not leader then -- SHOULD be good, but if there is a bug, this code future-proofs it then.
local lowestInd = math.huge
for ind, unit in pairs(units) do
if ind < lowestInd then
lowestInd = ind
leader = unit
end
end
end
if leader and Unit.isExist(leader) then -- maybe a little too paranoid now...
return leader:getPosition().p
end
end
--[[ vars for routines.getMGRSString:

View File

@@ -126,8 +126,11 @@ end
-- So length of msg / 8.3 = number of seconds needed to read it. rounded down to 8 chars per sec map function:
--
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
--
function STTS.getSpeechTime( length, speed, isGoogle )
--
-- @param #number length can also be passed as #string
-- @param #number speed Defaults to 1.0
-- @param #boolean isGoogle We're using Google TTS
function STTS.getSpeechTime(length,speed,isGoogle)
local maxRateRatio = 3
@@ -153,7 +156,7 @@ function STTS.getSpeechTime( length, speed, isGoogle )
length = string.len( length )
end
return math.ceil( length / cps )
return length/cps --math.ceil(length/cps)
end
--- Text to speech function.

View File

@@ -0,0 +1,152 @@
--- **Utilities** - Socket.
--
-- **Main Features:**
--
-- * Creates UDP Sockets
-- * Send messages to Discord
-- * Compatible with [FunkMan](https://github.com/funkyfranky/FunkMan)
-- * Compatible with [DCSServerBot](https://github.com/Special-K-s-Flightsim-Bots/DCSServerBot)
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Utilities.Socket
-- @image Utilities_Socket.png
--- SOCKET class.
-- @type SOCKET
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table socket The socket.
-- @field #number port The port.
-- @field #string host The host.
-- @field #table json JSON.
-- @extends Core.Fsm#FSM
--- **At times I feel like a socket that remembers its tooth.** -- Saul Bellow
--
-- ===
--
-- # The SOCKET Concept
--
-- Create a UDP socket server. It enables you to send messages to discord servers via discord bots.
--
-- **Note** that you have to **de-sanitize** `require` and `package` in your `MissionScripting.lua` file, which is in your `DCS/Scripts` folder.
--
--
-- @field #SOCKET
SOCKET = {
ClassName = "SOCKET",
verbose = 0,
lid = nil,
}
--- Data type. This is the keyword the socket listener uses.
-- @field #string TEXT Plain text.
-- @field #string BOMBRESULT Range bombing.
-- @field #string STRAFERESULT Range strafeing result.
-- @field #string LSOGRADE Airboss LSO grade.
SOCKET.DataType={
TEXT="moose_text",
BOMBRESULT="moose_bomb_result",
STRAFERESULT="moose_strafe_result",
LSOGRADE="moose_lso_grade",
}
--- SOCKET class version.
-- @field #string version
SOCKET.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot!
-- TODO: Messages as spoiler.
-- TODO: Send images?
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new SOCKET object.
-- @param #SOCKET self
-- @param #number Port UDP port. Default `10042`.
-- @param #string Host Host. Default `"127.0.0.1"`.
-- @return #SOCKET self
function SOCKET:New(Port, Host)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) --#SOCKET
package.path = package.path..";.\\LuaSocket\\?.lua;"
package.cpath = package.cpath..";.\\LuaSocket\\?.dll;"
self.socket = require("socket")
self.port=Port or 10042
self.host=Host or "127.0.0.1"
self.json=loadfile("Scripts\\JSON.lua")()
self.UDPSendSocket=self.socket.udp()
self.UDPSendSocket:settimeout(0)
return self
end
--- Set port.
-- @param #SOCKET self
-- @param #number Port Port. Default 10042.
-- @return #SOCKET self
function SOCKET:SetPort(Port)
self.port=Port or 10042
end
--- Set host.
-- @param #SOCKET self
-- @param #string Host Host. Default `"127.0.0.1"`.
-- @return #SOCKET self
function SOCKET:SetHost(Host)
self.host=Host or "127.0.0.1"
end
--- Send a table.
-- @param #SOCKET self
-- @param #table Table Table to send.
-- @return #SOCKET self
function SOCKET:SendTable(Table)
local json= self.json:encode(Table)
-- Debug info.
self:T("Json table:")
self:T(json)
-- Send data.
self.socket.try(self.UDPSendSocket:sendto(json, self.host, self.port))
return self
end
--- Send a text message.
-- @param #SOCKET self
-- @param #string Text Test message.
-- @return #SOCKET self
function SOCKET:SendText(Text)
local message={}
message.command = SOCKET.DataType.TEXT
message.text = Text
self:SendTable(message)
return self
end

View File

@@ -1155,7 +1155,7 @@ function UTILS.VecHdg(a)
end
--- Calculate "heading" of a 2D vector in the X-Y plane.
-- @param DCS#Vec2 a Vector in "D with x, y components.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @return #number Heading in degrees in [0,360).
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y, a.x))
@@ -1725,10 +1725,16 @@ end
-- @return #number Os time in seconds.
function UTILS.GetOSTime()
if os then
return os.clock()
local ts = 0
local t = os.date("*t")
local s = t.sec
local m = t.min * 60
local h = t.hour * 3600
ts = s+m+h
return ts
else
return nil
end
return nil
end
--- Shuffle a table accoring to Fisher Yeates algorithm
@@ -1843,6 +1849,11 @@ function UTILS.IsLoadingDoorOpen( unit_name )
return true -- no doors on this one ;)
end
if type_name == "Bronco-OV-10A" then
BASE:T(unit_name .. " front door(s) are open")
return true -- no doors on this one ;)
end
return false
end -- nil
@@ -1872,7 +1883,7 @@ function UTILS.GenerateVHFrequencies()
-- known and sorted map-wise NDBs in kHz
local _skipFrequencies = {
214,274,291.5,295,297.5,
300.5,304,307,309.5,311,312,312.5,316,
300.5,304,305,307,309.5,311,312,312.5,316,
320,324,328,329,330,332,336,337,
342,343,348,351,352,353,358,
363,365,368,372.5,374,
@@ -2295,7 +2306,7 @@ end
-- @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.
-- @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 }`
-- 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)
local spawn = true
if Spawn == false then spawn = false end
@@ -2318,10 +2329,10 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
local posz = tonumber(dataset[6])
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local group=nil
local data = { groupname=groupname, size=size, coordinate=coordinate }
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template }
table.insert(datatable,data)
if spawn then
local group = SPAWN:New(groupname)
local group = SPAWN:New(template)
:InitDelayOff()
:OnSpawnGroup(
function(spwndgrp)

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,17 @@
--- The CLIENT class
-- @type CLIENT
-- @field #string ClassName Name of the class.
-- @field #string ClientName Name of the client.
-- @field #string ClientBriefing Briefing.
-- @field #function ClientCallBack Callback function.
-- @field #table ClientParameters Parameters of the callback function.
-- @field #number ClientGroupID Group ID of the client.
-- @field #string ClientGroupName Group name.
-- @field #boolean ClientAlive Client alive.
-- @field #boolean ClientAlive2 Client alive 2.
-- @field #table Players Player table.
-- @field Core.Point#COORDINATE SpawnCoord Spawn coordinate from the template.
-- @extends Wrapper.Unit#UNIT

View File

@@ -671,7 +671,7 @@ 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 ID of the unit the ICLS system is attached to. Useful if more units are in one group.
-- @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.
-- @return #CONTROLLABLE self
@@ -725,6 +725,34 @@ function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
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)
else
self:SetCommand(CommandActivateLink4)
end
return self
end
--- Deactivate the active beacon of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the beacon is deactivated.
@@ -734,8 +762,10 @@ function CONTROLLABLE:CommandDeactivateBeacon( Delay )
-- Command to deactivate
local CommandDeactivateBeacon = { id = 'DeactivateBeacon', params = {} }
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandDeactivateBeacon, { self }, Delay )
local CommandDeactivateBeacon={id='DeactivateBeacon', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateBeacon, {self}, Delay)
else
self:SetCommand( CommandDeactivateBeacon )
end
@@ -743,6 +773,24 @@ function CONTROLLABLE:CommandDeactivateBeacon( 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
--- Deactivate the ICLS of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
@@ -1424,7 +1472,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E
DCSTask = {
id = 'Escort',
params = {
groupId = FollowControllable:GetID(),
groupId = FollowControllable and FollowControllable:GetID() or nil,
pos = Vec3,
lastWptIndexFlag = LastWaypointIndex and true or false,
lastWptIndex = LastWaypointIndex,
@@ -1453,11 +1501,11 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
id = 'FireAtPoint',
params = {
point = Vec2,
x=Vec2.x,
y=Vec2.y,
x = Vec2.x,
y = Vec2.y,
zoneRadius = Radius,
radius = Radius,
expendQty = 100, -- dummy value
expendQty = 1, -- dummy value
expendQtyEnabled = false,
alt_type = ASL and 0 or 1,
},
@@ -1476,7 +1524,8 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
DCSTask.params.weaponType = WeaponType
end
-- self:I(DCSTask)
--env.info("FF fireatpoint")
--BASE:I(DCSTask)
return DCSTask
end
@@ -1569,6 +1618,28 @@ function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes,
return DCSTask
end
--- (AIR) Enroute anti-ship task.
-- @param #CONTROLLABLE self
-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Ships"}`.
-- @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:EnRouteTaskAntiShip(TargetTypes, Priority)
local DCSTask = {
id = 'EngageTargets',
key = "AntiShip",
--auto = false,
--enabled = true,
params = {
targetTypes = TargetTypes or {"Ships"},
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
-- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked.
@@ -1835,7 +1906,7 @@ end
do -- Patrol methods
--- (GROUND) Patrol iteratively using the waypoints the for the (parent) group.
--- (GROUND) Patrol iteratively using the waypoints of the (parent) group.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRoute()
@@ -1866,12 +1937,13 @@ do -- Patrol methods
end
end
local Waypoint = Waypoints[1]
local Speed = Waypoint.speed or (20 / 3.6)
local From = FromCoord:WaypointGround( Speed )
local From = FromCoord:WaypointGround( Speed )
if IsSub then
From = FromCoord:WaypointNaval( Speed, Waypoint.alt )
From = FromCoord:WaypointNaval( Speed, Waypoint.alt )
end
table.insert( Waypoints, 1, From )
@@ -2023,22 +2095,21 @@ do -- Patrol methods
end
--- Return a Mission task to follow a given route defined by Points.
--- Return a "Misson" task to follow a given route defined by Points.
-- @param #CONTROLLABLE self
-- @param #table Points A table of route points.
-- @return DCS#Task
-- @return DCS#Task DCS mission task. Has entries `.id="Mission"`, `params`, were params has entries `airborne` and `route`, which is a table of `points`.
function CONTROLLABLE:TaskRoute( Points )
self:F2( Points )
local DCSTask = {
id = 'Mission',
params = {
airborne = self:IsAir(),
airborne = self:IsAir(), -- This is important to make aircraft land without respawning them (which was a long standing DCS issue).
route = {points = Points},
},
}
self:T3( { DCSTask } )
return DCSTask
end
@@ -3746,10 +3817,10 @@ end
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
-- @param #CONTROLLABLE self
-- @param #number speed Speed of the controllable, default 20
-- @param #number radius Radius of the relocation zone, default 500
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
-- @param #number speed Speed of the controllable, default 20
-- @param #number radius Radius of the relocation zone, default 500
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road"
-- @return #CONTROLLABLE self
function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, shortcut, formation )
@@ -3800,7 +3871,7 @@ end
--- Returns if the unit is a submarine.
-- @param #POSITIONABLE self
-- @return #boolean Submarines attributes result.
function POSITIONABLE:IsSubmarine()
function CONTROLLABLE:IsSubmarine()
self:F2()
local DCSUnit = self:GetDCSObject()
@@ -3817,9 +3888,10 @@ function POSITIONABLE:IsSubmarine()
return nil
end
--- Sets the controlled group to go at the specified speed in meters per second.
-- @param #CONTROLLABLE self
-- @param #number Speed Speed in meters per second.
-- @param #number Speed Speed in meters per second
-- @param #boolean Keep (Optional) When set to true, will maintain the speed on passing waypoints. If not present or false, the controlled group will return to the speed as defined by their route.
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetSpeed(Speed, Keep)

View File

@@ -187,6 +187,7 @@ GROUPTEMPLATE.Takeoff = {
-- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets.
-- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute.
-- @field #string GROUND_INFANTRY Ground infantry assets.
-- @field #string GROUND_IFV Ground Infantry Fighting Vehicle.
-- @field #string GROUND_ARTILLERY Artillery assets.
-- @field #string GROUND_TANK Tanks (modern or old).
-- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently.
@@ -213,6 +214,7 @@ GROUP.Attribute = {
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
@@ -529,26 +531,32 @@ function GROUP:HasAttribute(attribute, all)
-- Get all units of the group.
local _units=self:GetUnits()
local _allhave=true
local _onehas=false
if _units then
for _,_unit in pairs(_units) do
local _unit=_unit --Wrapper.Unit#UNIT
if _unit then
local _hastit=_unit:HasAttribute(attribute)
if _hastit==true then
_onehas=true
else
_allhave=false
end
end
local _allhave=true
local _onehas=false
for _,_unit in pairs(_units) do
local _unit=_unit --Wrapper.Unit#UNIT
if _unit then
local _hastit=_unit:HasAttribute(attribute)
if _hastit==true then
_onehas=true
else
_allhave=false
end
end
end
if all==true then
return _allhave
else
return _onehas
end
end
if all==true then
return _allhave
else
return _onehas
end
return nil
end
--- Returns the maximum speed of the group.
@@ -563,16 +571,19 @@ function GROUP:GetSpeedMax()
local Units=self:GetUnits()
local speedmax=0
local speedmax=nil
for _,unit in pairs(Units) do
local unit=unit --Wrapper.Unit#UNIT
local speed=unit:GetSpeedMax()
if speedmax==0 then
speedmax=speed
elseif speed<speedmax then
if speedmax==nil or speed<speedmax then
speedmax=speed
end
--env.info(string.format("FF unit %s: speed=%.1f, speedmax=%.1f", unit:GetName(), speed, speedmax))
end
return speedmax
@@ -658,6 +669,12 @@ function GROUP:GetPlayerUnits()
return nil
end
--- Check if an (air) group is a client or player slot. Information is retrieved from the group template.
-- @param #GROUP self
-- @return #boolean If true, group is associated with a client or player slot.
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. .
@@ -669,40 +686,28 @@ function GROUP:GetUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnit = DCSGroup:getUnit( UnitNumber )
local UnitFound = UNIT:Find(DCSUnit)
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)
if UnitFound then
return UnitFound
end
end
return UnitFound
end
return nil
end
--- Check if an (air) group is a client or player slot. Information is retrieved from the group template.
-- @param #GROUP self
-- @return #boolean If true, group is associated with a client or player slot.
function GROUP:IsPlayer()
-- Get group.
-- local group=self:GetGroup()
-- Units of template group.
local units=self:GetTemplate().units
-- Get numbers.
for _,unit in pairs(units) do
-- Check if unit name matach and skill is Client or Player.
if unit.name==self:GetName() and (unit.skill=="Client" or unit.skill=="Player") then
return true
end
end
return false
end
--- Returns the DCS Unit with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
@@ -1055,8 +1060,9 @@ end
-- @param Wrapper.Group#GROUP self
-- @return Core.Point#COORDINATE The COORDINATE of the GROUP.
function GROUP:GetCoordinate()
local Units = self:GetUnits() or {}
local Units = self:GetUnits() or {}
for _,_unit in pairs(Units) do
local FirstUnit = _unit -- Wrapper.Unit#UNIT
@@ -1203,7 +1209,8 @@ end
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
-- @return #number Number of missiles left.
-- @return #number Number of artillery shells left (with explosive mass, included in shells; shells can also be machine gun ammo)
function GROUP:GetAmmunition()
self:F( self.ControllableName )
@@ -1214,6 +1221,7 @@ function GROUP:GetAmmunition()
local Nrockets=0
local Nmissiles=0
local Nbombs=0
local Narti=0
if DCSControllable then
@@ -1222,19 +1230,19 @@ function GROUP:GetAmmunition()
local Unit = UnitData -- Wrapper.Unit#UNIT
-- Get ammo of the unit
local ntot, nshells, nrockets, nbombs, nmissiles = Unit:GetAmmunition()
local ntot, nshells, nrockets, nbombs, nmissiles, narti = Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
Nbombs=Nbombs+nbombs
Narti=Narti+narti
end
end
return Ntot, Nshells, Nrockets, Nbombs, Nmissiles
return Ntot, Nshells, Nrockets, Nbombs, Nmissiles, Narti
end
@@ -2372,14 +2380,15 @@ function GROUP:GetAttribute()
--- Ground ---
--------------
-- Ground
local apc=self:HasAttribute("Infantry carriers")
local apc=self:HasAttribute("APC")
local truck=self:HasAttribute("Trucks") and self:GetCategory()==Group.Category.GROUND
local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks")
local aaa=self:HasAttribute("AAA")
local aaa=self:HasAttribute("AAA") and (not self:HasAttribute("SAM elements"))
local ewr=self:HasAttribute("EWR")
local sam=self:HasAttribute("SAM elements") and (not self:HasAttribute("AAA"))
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements") or self:HasAttribute("Optical Tracker")
-- Train
local train=self:GetCategory()==Group.Category.TRAIN
@@ -2422,10 +2431,12 @@ function GROUP:GetAttribute()
attribute=GROUP.Attribute.GROUND_ARTILLERY
elseif tank then
attribute=GROUP.Attribute.GROUND_TANK
elseif ifv then
attribute=GROUP.Attribute.GROUND_IFV
elseif apc then
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then
@@ -2716,94 +2727,85 @@ function GROUP:GetHighestThreat()
return nil, nil
end
--do -- Smoke
--- Get TTS friendly, optionally customized callsign mainly for **player groups**. A customized callsign is taken from the #GROUP name, after an optional '#' sign, e.g. "Aerial 1-1#Ghostrider" resulting in "Ghostrider 9", or,
-- if that isn't available, from the playername, as set in the mission editor main screen under Logbook, after an optional '|' sign (actually, more of a personal call sign), e.g. "Apple|Moose" results in "Moose 9 1". Options see below.
-- @param #GROUP self
-- @param #boolean ShortCallsign Return a shortened customized callsign, i.e. "Ghostrider 9" and not "Ghostrider 9 1"
-- @param #boolean Keepnumber (Player only) Return customized callsign, incl optional numbers at the end, e.g. "Aerial 1-1#Ghostrider 109" results in "Ghostrider 109", if you want to e.g. use historical US Navy Callsigns
-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Does not apply if using customized
-- callsigns from playername or group name.
-- @return #string Callsign
-- @usage
-- -- Set Custom CAP Flight Callsigns for use with TTS
-- mygroup:GetCustomCallSign(true,false,{
-- Devil = 'Bengal',
-- Snake = 'Winder',
-- Colt = 'Camelot',
-- Enfield = 'Victory',
-- Uzi = 'Evil Eye'
-- })
--
----- Signal a flare at the position of the GROUP.
---- @param #GROUP self
---- @param Utilities.Utils#FLARECOLOR FlareColor
--function GROUP:Flare( FlareColor )
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), FlareColor , 0 )
--end
-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group:
--
----- Signal a white flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareWhite()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White , 0 )
--end
-- 'Victory 9'
--
----- Signal a yellow flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareYellow()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow , 0 )
--end
--
----- Signal a green flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareGreen()
-- self:F2()
-- trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green , 0 )
--end
--
----- Signal a red flare at the position of the GROUP.
---- @param #GROUP self
--function GROUP:FlareRed()
-- self:F2()
-- local Vec3 = self:GetVec3()
-- if Vec3 then
-- trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 )
-- end
--end
--
----- Smoke the GROUP.
---- @param #GROUP self
--function GROUP:Smoke( SmokeColor, Range )
-- self:F2()
-- if Range then
-- trigger.action.smoke( self:GetRandomVec3( Range ), SmokeColor )
-- else
-- trigger.action.smoke( self:GetVec3(), SmokeColor )
-- end
--
--end
--
----- Smoke the GROUP Green.
---- @param #GROUP self
--function GROUP:SmokeGreen()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green )
--end
--
----- Smoke the GROUP Red.
---- @param #GROUP self
--function GROUP:SmokeRed()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red )
--end
--
----- Smoke the GROUP White.
---- @param #GROUP self
--function GROUP:SmokeWhite()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White )
--end
--
----- Smoke the GROUP Orange.
---- @param #GROUP self
--function GROUP:SmokeOrange()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange )
--end
--
----- Smoke the GROUP Blue.
---- @param #GROUP self
--function GROUP:SmokeBlue()
-- self:F2()
-- trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue )
--end
--
--
--
--end
--
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
--self:I("GetCustomCallSign")
local callsign = "Ghost 1"
if self:IsAlive() then
local IsPlayer = self:IsPlayer()
local shortcallsign = self:GetCallsign() or "unknown91" -- e.g.Uzi91, but we want Uzi 9 1
local callsignroot = string.match(shortcallsign, '(%a+)') -- Uzi
--self:I("CallSign = " .. callsignroot)
local groupname = self:GetName()
local callnumber = string.match(shortcallsign, "(%d+)$" ) or "91" -- 91
local callnumbermajor = string.char(string.byte(callnumber,1)) -- 9
local callnumberminor = string.char(string.byte(callnumber,2)) -- 1
local personalized = false
if IsPlayer and string.find(groupname,"#") then
-- personalized flight name in group naming
if Keepnumber then
shortcallsign = string.match(groupname,"#(.+)") -- Ghostrider 219
else
shortcallsign = string.match(groupname,"#%s*([%a]+)") -- Ghostrider
end
personalized = true
elseif IsPlayer and string.find(self:GetPlayerName(),"|") then
-- personalized flight name in group naming
shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") -- Ghostrider
personalized = true
end
if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
end
if personalized then
-- player personalized callsign
-- remove trailing/leading spaces
shortcallsign=string.gsub(shortcallsign,"^%s*","")
shortcallsign=string.gsub(shortcallsign,"%s*$","")
if Keepnumber then
return shortcallsign -- Ghostrider 219
elseif ShortCallsign then
callsign = shortcallsign.." "..callnumbermajor -- Ghostrider 9
else
callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1
end
return callsign
end
-- AI or not personalized
if ShortCallsign then
callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9
else
callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 9 1
end
--self:I("Generated Callsign = " .. callsign)
end
return callsign
end

View File

@@ -35,7 +35,7 @@ IDENTIFIABLE = {
local _CategoryName = {
[Unit.Category.AIRPLANE] = "Airplane",
[Unit.Category.HELICOPTER] = "Helicoper",
[Unit.Category.HELICOPTER] = "Helicopter",
[Unit.Category.GROUND_UNIT] = "Ground Identifiable",
[Unit.Category.SHIP] = "Ship",
[Unit.Category.STRUCTURE] = "Structure",
@@ -56,8 +56,7 @@ end
-- If the Identifiable is not alive, nil is returned.
-- If the Identifiable is alive, true is returned.
-- @param #IDENTIFIABLE self
-- @return #boolean true if Identifiable is alive.
-- @return #nil if the Identifiable is not existing or is not alive.
-- @return #boolean true if Identifiable is alive or `#nil` if the Identifiable is not existing or is not alive.
function IDENTIFIABLE:IsAlive()
self:F3( self.IdentifiableName )
@@ -77,11 +76,8 @@ end
--- Returns DCS Identifiable object name.
-- The function provides access to non-activated objects too.
-- @param #IDENTIFIABLE self
-- @return #string The name of the DCS Identifiable.
-- @return #nil The DCS Identifiable is not existing or alive.
-- @return #string The name of the DCS Identifiable or `#nil`.
function IDENTIFIABLE:GetName()
self:F2( self.IdentifiableName )
local IdentifiableName = self.IdentifiableName
return IdentifiableName
end
@@ -148,8 +144,7 @@ end
--- Returns coalition of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return DCS#coalition.side The side of the coalition.
-- @return #nil The DCS Identifiable is not existing or alive.
-- @return DCS#coalition.side The side of the coalition or `#nil` The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:GetCoalition()
self:F2( self.IdentifiableName )
@@ -190,8 +185,7 @@ end
--- Returns country of the Identifiable.
-- @param #IDENTIFIABLE self
-- @return DCS#country.id The country identifier.
-- @return #nil The DCS Identifiable is not existing or alive.
-- @return DCS#country.id The country identifier or `#nil` The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:GetCountry()
self:F2( self.IdentifiableName )
@@ -222,8 +216,7 @@ end
--- Returns Identifiable descriptor. Descriptor type depends on Identifiable category.
-- @param #IDENTIFIABLE self
-- @return DCS#Object.Desc The Identifiable descriptor.
-- @return #nil The DCS Identifiable is not existing or alive.
-- @return DCS#Object.Desc The Identifiable descriptor or `#nil` The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:GetDesc()
self:F2( self.IdentifiableName )
@@ -242,8 +235,7 @@ end
--- Check if the Object has the attribute.
-- @param #IDENTIFIABLE self
-- @param #string AttributeName The attribute name.
-- @return #boolean true if the attribute exists.
-- @return #nil The DCS Identifiable is not existing or alive.
-- @return #boolean true if the attribute exists or `#nil` The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:HasAttribute( AttributeName )
self:F2( self.IdentifiableName )
@@ -266,8 +258,10 @@ function IDENTIFIABLE:GetCallsign()
return ''
end
--- Gets the threat level.
-- @param #IDENTIFIABLE self
-- @return #number Threat level.
-- @return #string Type.
function IDENTIFIABLE:GetThreatLevel()
return 0, "Scenery"
end

View File

@@ -13,7 +13,7 @@
--
-- ### Author: **funkyfranky**
-- @module Wrapper.Marker
-- @image Wrapper_Marker.png
-- @image MOOSE_Core.JPG
--- Marker class.
-- @type MARKER
@@ -150,7 +150,7 @@ _MARKERID = 0
--- Marker class version.
-- @field #string version
MARKER.version = "0.1.0"
MARKER.version="0.1.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -175,7 +175,9 @@ function MARKER:New( Coordinate, Text )
-- Inherit everything from FSM class.
local self = BASE:Inherit( self, FSM:New() ) -- #MARKER
self.coordinate = Coordinate
local self=BASE:Inherit(self, FSM:New()) -- #MARKER
self.coordinate=UTILS.DeepCopy(Coordinate)
self.text = Text
@@ -315,6 +317,16 @@ function MARKER:ReadOnly()
return self
end
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadWrite()
self.readonly=false
return self
end
--- Set message that is displayed on screen if the marker is added.
-- @param #MARKER self
-- @param #string Text Message displayed when the marker is added.
@@ -580,7 +592,7 @@ end
--- Set text that is displayed in the marker panel. Note this does not show the marker.
-- @param #MARKER self
-- @param #string Text Marker text. Default is an empty sting "".
-- @param #string Text Marker text. Default is an empty string "".
-- @return #MARKER self
function MARKER:SetText( Text )
self.text = Text and tostring( Text ) or ""
@@ -637,7 +649,9 @@ function MARKER:OnEventMarkRemoved( EventData )
local MarkID = EventData.MarkID
self:T3( self.lid .. string.format( "Captured event MarkAdded for Mark ID=%s", tostring( MarkID ) ) )
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkRemoved for Mark ID=%s", tostring(MarkID)))
if MarkID == self.mid then
@@ -664,16 +678,22 @@ function MARKER:OnEventMarkChange( EventData )
if MarkID == self.mid then
self:Changed( EventData )
local MarkID=EventData.MarkID
self:TextChanged( tostring( EventData.MarkText ) )
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID)))
if MarkID==self.mid then
self.text=tostring(EventData.MarkText)
self:Changed(EventData)
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -381,10 +381,10 @@ function POSITIONABLE:GetCoordinate()
-- Get the current position.
local PositionableVec3 = self:GetVec3()
local coord = COORDINATE:NewFromVec3( PositionableVec3 )
local coord=COORDINATE:NewFromVec3(PositionableVec3)
local heading = self:GetHeading()
coord.Heading = heading
-- Return a new coordinate object.
-- Return a new coordiante object.
return coord
end
@@ -537,7 +537,7 @@ function POSITIONABLE:GetBoundingRadius( MinDist )
return nil
end
--- Returns the altitude of the POSITIONABLE.
--- Returns the altitude above sea level of the POSITIONABLE.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return DCS#Distance The altitude of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
@@ -703,11 +703,11 @@ function POSITIONABLE:IsSubmarine()
if DCSUnit then
local UnitDescriptor = DCSUnit:getDesc()
if UnitDescriptor.attributes["Submarines"] == true then
return true
else
return false
end
if UnitDescriptor.attributes["Submarines"] == true then
return true
else
return false
end
end
self:E( { "Cannot check IsSubmarine", Positionable = self, Alive = self:IsAlive() } )
@@ -779,11 +779,11 @@ function POSITIONABLE:GetRelativeVelocity( Positionable )
return UTILS.VecNorm( vtot )
end
--- Returns the POSITIONABLE height in meters.
--- Returns the POSITIONABLE height above sea level in meters.
-- @param Wrapper.Positionable#POSITIONABLE self
-- @return DCS#Vec3 The height of the POSITIONABLE in meters.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetHeight()
-- @return DCS#Vec3 Height of the positionable in meters (or nil, if the object does not exist).
function POSITIONABLE:GetHeight() --R2.1
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
@@ -1232,6 +1232,33 @@ function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name )
return nil
end
--- Send a message to a @{Wrapper.Unit}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param DCS#Duration Duration The duration of the message.
-- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name )
self:F2( { Message, Duration } )
local DCSObject = self:GetDCSObject()
if DCSObject then
if DCSObject:isExist() then
if MessageUnit:IsAlive() then
self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit )
else
BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } )
end
else
BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } )
end
end
return nil
end
--- Send a message of a message type to a @{Wrapper.Group}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@@ -1298,6 +1325,30 @@ function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name
return nil
end
--- Send a message to a @{Core.Set#SET_UNIT}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
-- @param #string Message The message text
-- @param DCS#Duration Duration The duration of the message.
-- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message.
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name )
self:F2( { Message, Duration } )
local DCSObject = self:GetDCSObject()
if DCSObject then
if DCSObject:isExist() then
MessageSetUnit:ForEachUnit(
function( MessageGroup )
self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup )
end
)
end
end
return nil
end
--- Send a message to the players in the @{Wrapper.Group}.
-- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message.
-- @param #POSITIONABLE self
@@ -1480,17 +1531,15 @@ do -- Cargo
return ItemCount
end
-- --- Get Cargo Bay Free Volume in m3.
-- -- @param #POSITIONABLE self
-- -- @return #number CargoBayFreeVolume
-- function POSITIONABLE:GetCargoBayFreeVolume()
-- local CargoVolume = 0
-- for CargoName, Cargo in pairs( self.__.Cargo ) do
-- CargoVolume = CargoVolume + Cargo:GetVolume()
-- end
-- return self.__.CargoBayVolumeLimit - CargoVolume
-- end
--
--- Get the number of infantry soldiers that can be embarked into an aircraft (airplane or helicopter).
-- Returns `nil` for ground or ship units.
-- @param #POSITIONABLE self
-- @return #number Descent number of soldiers that fit into the unit. Returns `#nil` for ground and ship units.
function POSITIONABLE:GetTroopCapacity()
local DCSunit=self:GetDCSObject() --DCS#Unit
local capacity=DCSunit:getDescentCapacity()
return capacity
end
--- Get Cargo Bay Free Weight in kg.
-- @param #POSITIONABLE self
@@ -1509,55 +1558,97 @@ do -- Cargo
return self.__.CargoBayWeightLimit - CargoWeight
end
-- --- Get Cargo Bay Volume Limit in m3.
-- -- @param #POSITIONABLE self
-- -- @param #number VolumeLimit
-- function POSITIONABLE:SetCargoBayVolumeLimit( VolumeLimit )
-- self.__.CargoBayVolumeLimit = VolumeLimit
-- end
--- Set Cargo Bay Weight Limit in kg.
-- @param #POSITIONABLE self
-- @param #number WeightLimit
-- @param #number WeightLimit (Optional) Weight limit in kg. If not given, the value is taken from the descriptors or hard coded.
function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit )
if WeightLimit then
if WeightLimit then
---
-- User defined value
---
self.__.CargoBayWeightLimit = WeightLimit
elseif self.__.CargoBayWeightLimit ~= nil then
-- Value already set ==> Do nothing!
else
-- If WeightLimit is not provided, we will calculate it depending on the type of unit.
---
-- Weightlimit is not provided, we will calculate it depending on the type of unit.
---
-- Descriptors that contain the type name and for aircraft also weights.
local Desc = self:GetDesc()
self:F({Desc=Desc})
-- Unit type name.
local TypeName=Desc.typeName or "Unknown Type"
-- When an airplane or helicopter, we calculate the WeightLimit based on the descriptor.
if self:IsAir() then
local Desc = self:GetDesc()
self:F( { Desc = Desc } )
-- Max takeoff weight if DCS descriptors have unrealstic values.
local Weights = {
["C-17A"] = 35000, -- 77519 cannot be used, because it loads way too many APCs and infantry.
["C-130"] = 22000 -- The real value cannot be used, because it loads way too many APCs and infantry.
-- C-17A
-- Wiki says: max=265,352, empty=128,140, payload=77,516 (134 troops, 1 M1 Abrams tank, 2 M2 Bradley or 3 Stryker)
-- DCS says: max=265,350, empty=125,645, fuel=132,405 ==> Cargo Bay=7300 kg with a full fuel load (lot of fuel!) and 73300 with half a fuel load.
--["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry.
-- C-130:
-- DCS says: max=79,380, empty=36,400, fuel=10,415 kg ==> Cargo Bay=32,565 kg with fuel load.
-- Wiki says: max=70,307, empty=34,382, payload=19,000 kg (92 passengers, 2-3 Humvees or 2 M113s), max takeoff weight 70,037 kg.
-- Here we say two M113s should be transported. Each one weights 11,253 kg according to DCS. So the cargo weight should be 23,000 kg with a full load of fuel.
-- This results in a max takeoff weight of 69,815 kg (23,000+10,415+36,400), which is very close to the Wiki value of 70,037 kg.
["C-130"] = 70000,
}
self.__.CargoBayWeightLimit = Weights[Desc.typeName] or (Desc.massMax - (Desc.massEmpty + Desc.fuelMassMax))
-- Max (takeoff) weight (empty+fuel+cargo weight).
local massMax= Desc.massMax or 0
-- Adjust value if set above.
local maxTakeoff=Weights[TypeName]
if maxTakeoff then
massMax=maxTakeoff
end
-- Empty weight.
local massEmpty=Desc.massEmpty or 0
-- Fuel. The descriptor provides the max fuel mass in kg. This needs to be multiplied by the relative fuel amount to calculate the actual fuel mass on board.
local massFuelMax=Desc.fuelMassMax or 0
local relFuel=math.min(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks.
local massFuel=massFuelMax*relFuel
-- Number of soldiers according to DCS function
--local troopcapacity=self:GetTroopCapacity() or 0
-- Calculate max cargo weight, which is the max (takeoff) weight minus the empty weight minus the actual fuel weight.
local CargoWeight=massMax-(massEmpty+massFuel)
-- Debug info.
self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg", TypeName, CargoWeight, massMax, massEmpty, massFuelMax, relFuel, massFuel))
--self:T(string.format("Descent Troop Capacity=%d ==> %d kg (for 95 kg soldier)", troopcapacity, troopcapacity*95))
-- Set value.
self.__.CargoBayWeightLimit = CargoWeight
elseif self:IsShip() then
local Desc = self:GetDesc()
self:F( { Desc = Desc } )
-- Hard coded cargo weights in kg.
local Weights = {
["Type_071"] = 245000,
["LHA_Tarawa"] = 500000,
["Ropucha-class"] = 150000,
["Dry-cargo ship-1"] = 70000,
["Dry-cargo ship-2"] = 70000,
["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia).
["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops.
["LST_Mk2"] = 2100000 -- Can carry 2100 tons according to wiki source!
["Type_071"] = 245000,
["LHA_Tarawa"] = 500000,
["Ropucha-class"] = 150000,
["Dry-cargo ship-1"] = 70000,
["Dry-cargo ship-2"] = 70000,
["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia).
["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops.
["LST_Mk2"] = 2100000, -- Can carry 2100 tons according to wiki source!
["speedboat"] = 500, -- 500 kg ~ 5 persons
["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns.
}
self.__.CargoBayWeightLimit = (Weights[Desc.typeName] or 50000)
self.__.CargoBayWeightLimit = ( Weights[TypeName] or 50000 )
else
local Desc = self:GetDesc()
-- Hard coded number of soldiers.
local Weights = {
["AAV7"] = 25,
["Bedford_MWD"] = 8, -- new by kappa
@@ -1593,7 +1684,7 @@ do -- Cargo
["KrAZ6322"] = 12,
["M 818"] = 12,
["Tigr_233036"] = 6,
["TPZ"] = 10,
["TPZ"] = 10, -- Fuchs
["UAZ-469"] = 4, -- new by kappa
["Ural-375"] = 12,
["Ural-4320-31"] = 14,
@@ -1607,12 +1698,28 @@ do -- Cargo
["HL_DSHK"] = 6,
}
local CargoBayWeightLimit = (Weights[Desc.typeName] or 0) * 95
-- Assuming that each passenger weighs 95 kg on average.
local CargoBayWeightLimit = ( Weights[TypeName] or 0 ) * 95
self.__.CargoBayWeightLimit = CargoBayWeightLimit
end
end
self:F( { CargoBayWeightLimit = self.__.CargoBayWeightLimit } )
self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit})
end
--- Get Cargo Bay Weight Limit in kg.
-- @param #POSITIONABLE self
-- @return #number Max cargo weight in kg.
function POSITIONABLE:GetCargoBayWeightLimit()
if self.__.CargoBayWeightLimit==nil then
self:SetCargoBayWeightLimit()
end
return self.__.CargoBayWeightLimit
end
end --- Cargo
--- Signal a flare at the position of the POSITIONABLE.

View File

@@ -57,3 +57,40 @@ end
function SCENERY:GetThreatLevel()
return 0, "Scenery"
end
--- Find a SCENERY object by it's name/id.
--@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()
end
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
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())
end
end

View File

@@ -24,6 +24,7 @@
--- @type UNIT
-- @field #string ClassName Name of the class.
-- @field #string UnitName Name of the unit.
-- @field #string GroupName Name of the group the unit belongs to.
-- @extends Wrapper.Controllable#CONTROLLABLE
--- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the _@{DATABASE} object.
@@ -86,11 +87,11 @@
-- * Use the @{#UNIT.IsLOS}() method to check if the given unit is within line of sight.
--
--
-- @field #UNIT UNIT
-- @field #UNIT
UNIT = {
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
}
@@ -178,8 +179,28 @@ function UNIT:GetDCSObject()
return nil
end
--- Returns the unit altitude above sea level in meters.
-- @param Wrapper.Unit#UNIT self
-- @param #boolean FromGround Measure from the ground or from sea level (ASL). Provide **true** for measuring from the ground (AGL). **false** or **nil** if you measure from sea level.
-- @return #number The height of the group or nil if is not existing or alive.
function UNIT:GetAltitude(FromGround)
local DCSUnit = Unit.getByName( self.UnitName )
if DCSUnit then
local altitude = 0
local point = DCSUnit:getPoint() --DCS#Vec3
altitude = point.y
if FromGround then
local land = land.getHeight( { x = point.x, y = point.z } ) or 0
altitude = altitude - land
end
return altitude
end
return nil
end
--- Respawn the @{Wrapper.Unit} using a (tweaked) template of the parent Group.
--
@@ -677,6 +698,7 @@ end
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
-- @return #number Number of artillery shells left (with explosive mass, included in shells; shells can also be machine gun ammo)
function UNIT:GetAmmunition()
-- Init counter.
@@ -685,6 +707,7 @@ function UNIT:GetAmmunition()
local nrockets=0
local nmissiles=0
local nbombs=0
local narti=0
local unit=self
@@ -721,7 +744,11 @@ function UNIT:GetAmmunition()
-- Add up all shells.
nshells=nshells+Nammo
if ammotable[w].desc.warhead and ammotable[w].desc.warhead.explosiveMass and ammotable[w].desc.warhead.explosiveMass > 0 then
narti=narti+Nammo
end
elseif Category==Weapon.Category.ROCKET then
-- Add up all rockets.
@@ -758,7 +785,7 @@ function UNIT:GetAmmunition()
-- Total amount of ammunition.
nammo=nshells+nrockets+nmissiles+nbombs
return nammo, nshells, nrockets, nbombs, nmissiles
return nammo, nshells, nrockets, nbombs, nmissiles, narti
end
--- Returns the unit sensors.
@@ -809,7 +836,9 @@ function UNIT:HasSEAD()
local HasSEAD = false
if UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND1_FOR_ARM"] == true or
UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true then
UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] and UnitSEADAttributes["RADAR_BAND2_FOR_ARM"] == true or
UnitSEADAttributes["Optical Tracker"] and UnitSEADAttributes["Optical Tracker"] == true
then
HasSEAD = true
end
return HasSEAD
@@ -1046,7 +1075,7 @@ function UNIT:GetThreatLevel()
if Descriptor then
local Attributes = Descriptor.attributes
if self:IsGround() then
local ThreatLevels = {

View File

@@ -6,6 +6,7 @@ Utilities/Profiler.lua
Utilities/Templates.lua
Utilities/STTS.lua
Utilities/FiFo.lua
Utilities/Socket.lua
Core/Base.lua
Core/Beacon.lua
@@ -29,6 +30,10 @@ Core/SpawnStatic.lua
Core/Timer.lua
Core/Goal.lua
Core/Spot.lua
Core/MarkerOps_Base.lua
Core/Astar.lua
Core/Condition.lua
Core/TextAndSound.lua
Wrapper/Object.lua
Wrapper/Identifiable.lua