Compare commits

...

224 Commits

Author SHA1 Message Date
Frank
8bf56b8b1a Update Airboss.lua
- Fixed AI handling (got lost in some merge with develop branch and was using FLIGHTGROUPS)
2023-07-26 16:36:44 +02:00
Applevangelist
f31741f934 #AI_ESCORT
* Improve documentation
2023-07-25 12:01:18 +02:00
Applevangelist
46258492bd #AIRBASE, #ATIS
* Additions
2023-07-23 12:37:57 +02:00
Applevangelist
6481f66e27 #MANTIS
* Added IDs for Current Hill Assets, keyword "CHM" for group names
2023-07-16 11:27:34 +02:00
Thomas
5954b8692f Update Spot.lua
Fix
2023-07-15 09:36:24 +02:00
Applevangelist
f6fdecf892 Merge remote-tracking branch 'origin/master' 2023-07-13 16:15:11 +02:00
Applevangelist
53fb77b50d #SPAWN 2023-07-13 16:15:06 +02:00
Applevangelist
04a9dc3a8c #SPAWN
* Added method to init spawn position
2023-07-13 16:13:00 +02:00
Applevangelist
10b9a32f29 #SPOT
* Fix for switching laser off and on again not follwing unit any more
2023-07-12 17:54:17 +02:00
Applevangelist
40fa929eb0 * Added omissed DetectedItem.Name found by @Nocke 2023-07-07 15:14:33 +02:00
Applevangelist
8c8ef19f01 #CONTROLLABLE
* Added TaskGroundEscort
2023-07-03 16:44:46 +02:00
Applevangelist
1eaa3d309d #SCORING
* typo
2023-07-01 13:15:59 +02:00
Applevangelist
a52cd5612a Merge remote-tracking branch 'origin/master' 2023-07-01 13:12:29 +02:00
Applevangelist
e82ed762be Merge remote-tracking branch 'origin/master' 2023-07-01 13:11:46 +02:00
Applevangelist
0bb16ec827 #AIRBASE
* Fix for nil error in finding parking for a group

#MANTIS
* Added SAM type "SHORAD" as designator

#SCORING
* fix for non local problem

#UTILS
* fix for OneLineSerialize
2023-07-01 13:11:21 +02:00
Thomas
a978420a67 Point - BRAANATO (#1969)
corrected Track to be direction of travel of bogey (self in this case)
2023-06-26 13:25:23 +02:00
Applevangelist
f59326bf10 #ATIS
* Fix for airbases which have no runways, e.g. Naqoura Syria
2023-06-24 14:03:43 +02:00
Thomas
d937ab2679 CTLD (#1967)
* Added option for troops subcategories in menu
2023-06-22 13:46:11 +02:00
Applevangelist
7164e211f2 #AIRBASE
- Nicosia unused atm
2023-06-21 10:42:35 +02:00
Applevangelist
dae78d7ac1 #Positionable, Unit - remove pcall 2023-06-16 11:06:46 +02:00
Applevangelist
254468c723 #NET
* Improvements
2023-06-15 14:51:36 +02:00
Applevangelist
71be4d99d6 #POSITIONABLE:GetRelativeCoordinate( x, y, z ) 2023-06-14 17:40:33 +02:00
Frank
f86fc845e7 Update Timer.lua
- Timer stop is using protected call now because it can crash the whole script.
2023-06-13 21:53:19 +02:00
Frank
cc907b9c14 Update Unit.lua
- UNIT:IsAlive() fixed so it is consistent with GROUP:IsAlive()
2023-06-13 19:02:55 +02:00
Applevangelist
241b2beee1 * Makes calls for GetAmmo() and GetVec3() safer with pcall()
* Small docu fix Ben-Gurion
2023-06-13 08:39:56 +02:00
Frank
12f260e857 Update Unit.lua
- Added `UNIT:IsExist` function.
2023-06-12 23:01:19 +02:00
Thomas
096d145cf9 Update Airbase.lua (#1957)
Deleted raj al … airbases #1955
2023-06-12 06:22:51 +02:00
Thomas
b3883301a2 Update Airbase.lua
Corrected enumerator for Ben-Gurion (Ben_Gurion)
2023-06-11 12:33:01 +02:00
Applevangelist
3a7521c492 #UTILS
* Sinai TIme
2023-06-10 19:08:46 +02:00
Applevangelist
caa5a96235 fix 2023-06-10 19:06:22 +02:00
Applevangelist
cee1c592ba #UTILS
* Sinai TIme
2023-06-10 19:04:40 +02:00
Frank
f1fc9f0b27 Update Utils.lua
- Added parameters time zone and magvar for Sinai map
2023-06-10 10:08:22 +02:00
Applevangelist
e9adcb0dd5 #AIRBASE
* Sinai airfield enumerator
2023-06-09 15:13:45 +02:00
Applevangelist
afe2b675e5 #RAT
* Fixes from dev
2023-06-08 13:59:59 +02:00
Applevangelist
2c14ee74b0 #SCORING
* Can leave text empty on some instances
2023-06-08 13:58:26 +02:00
Frank
f351d2a37e Merge pull request #1951 from FlightControl-Master/FF/MasterDevel
Update RAT.lua
2023-06-08 09:45:49 +02:00
Frank
4cb0e70184 Update RAT.lua
- Fixed RATMANAGER spamming due to not accounting for planned/scheduled groups
2023-06-08 09:43:11 +02:00
Applevangelist
41bb2551d3 #CTLD
* Ensure new menus can be build if player changes helos
2023-06-03 12:34:18 +02:00
Applevangelist
55ed394782 #UNIT #CTLD
* Stabilize that sometimes a unit coordinate cannot be found
2023-06-02 08:44:14 +02:00
Applevangelist
dd7a883a33 Fixes 2023-06-01 10:02:51 +02:00
Applevangelist
f7acbc3928 #CSAR
* Corrected flare distance to kms
2023-05-30 07:38:19 +02:00
Applevangelist
2318578126 #SPAWN
* Logic fix for the last parameter of `NewFromTemplate()`
2023-05-28 15:27:34 +02:00
Applevangelist
3e6f25f17c #UTILS
* Added UTILS.PrintTableToLog()
2023-05-26 08:28:08 +02:00
Applevangelist
0cd6a59ce4 #CONTROLLER
* Fix for Link4
2023-05-25 08:57:15 +02:00
Applevangelist
91a445961b #RESCUEHELO
* Small fix to get the coordinate of an ejected/crashed unit
2023-05-25 08:23:31 +02:00
Frank
749e9b7e08 Update Event.lua
Fixed bug that statics are handled incorrectly if they are the target object of an event (*e.g.* hit event)
2023-05-24 11:16:01 +02:00
Applevangelist
905d29b279 #SPAWN
* Tweaked NewFromTemplate, and gave a better example
2023-05-22 17:57:03 +02:00
Applevangelist
5f97299cb2 Small Fix 2023-05-20 12:12:18 +02:00
Applevangelist
60b75a4c8f #POINT
* Added FindClosestStatic()
2023-05-19 17:11:54 +02:00
Applevangelist
0868286f27 #WAREHOUSE
* Fixed one omission for SHIP transport types
2023-05-17 10:26:29 +02:00
Applevangelist
ffcc46cb2d #CTLD
* Changed VHF modulation to AM
* Added option to have a different sound file for UHF, in case you want to silent FC3 radios
* Thanks to Streakeagle
2023-05-17 09:34:54 +02:00
Frank
21bcd64c9d Update Controllable.lua
Corrected parameters for enroute tasks according to hoggit for
- CONTROLLABLE:EnRouteTaskFAC
- CONTROLLABLE:EnRouteTaskFAC_EngageGroup
2023-05-13 23:35:58 +02:00
Applevangelist
943b68f38f #CTLD
* placement of dropped crates to better align to unit length
2023-05-11 15:31:24 +02:00
Applevangelist
256b93087f #AI_A2X...
* Fixes
2023-05-09 09:48:21 +02:00
Applevangelist
7ed0d7eec3 #AI_A2X...
* Fixes
2023-05-09 09:44:41 +02:00
Applevangelist
9b8154246f #COORDINATE
* Added `IsInSteepArea()`and `IsInFlatArea()`
2023-05-05 10:31:23 +02:00
Frank
c360759e49 Merge pull request #1939 from FlightControl-Master/FF/MasterDevel
Update Socket.lua
2023-04-25 21:42:53 +02:00
Frank
d8b80aab1a Update Socket.lua
- Added `SOCKET:SendTextToSpeech()` function
2023-04-25 21:20:16 +02:00
Applevangelist
b96ebc1872 #SCENERY
* Added update of Life0 value if `GetLife()`is called
#SET_SCENERY
* Added Documentation
2023-04-25 09:12:31 +02:00
Applevangelist
6896dc155a #SCENERY
* Added update of Life0 value if `GetLife()`is called
#SET_SCENERY
* Added Documentation
2023-04-25 09:12:25 +02:00
Applevangelist
1060d63808 #SET_SCENERY
* Added functions to count Life0, Life and RelativeLife points of SET_SCENERY
2023-04-24 16:44:38 +02:00
Thomas
88b6540f5b AIRBASE - add Normandy AFBs (#1937)
Add Normandy AFBs
2023-04-24 11:09:03 +02:00
Frank
302e785f32 Merge pull request #1936 from FlightControl-Master/FF/MasterDevel
Remove Junk
2023-04-22 14:05:09 +02:00
Frank
1507cc0b42 Remove Junk
**ZONE**
- Added `ZONE_RADIUS:RemoveJunk()` function
- Added `ZONE_POLYGON_BASE:RemoveJunk()` function (not working)

**POSITIONABLE**
- Added `Explosion`
2023-04-22 14:02:18 +02:00
Applevangelist
240307ef94 #CTLD
* Docu changes
2023-04-20 08:23:03 +02:00
Applevangelist
90c74bd82c #SET
- Minus one log entry
2023-04-20 08:06:43 +02:00
Thomas
9414096de7 Added active ZONE filtering (#1935)
for groups, units, clients entering/leaving zones to be used with `FilterStart()`
2023-04-19 09:51:53 +02:00
Applevangelist
07c3be9d6a Fixes for getPlayername() errors 2023-04-18 10:24:56 +02:00
Applevangelist
9869dbd95a #CTLD
* Include current group structure in load/save functions
2023-04-16 16:10:46 +02:00
Frank
49b702106a Update Weapon.lua
- Decreased min possible time step to 0.00001 seconds
2023-04-16 12:29:57 +02:00
Applevangelist
8eb09beb96 Merge remote-tracking branch 'origin/master' 2023-04-15 16:19:46 +02:00
Applevangelist
b402a99a25 #CTLD
* Small fix allowing minus signs in template names for repairs
2023-04-15 16:19:35 +02:00
Applevangelist
ad8938cd74 #CTLD
* Small fix allowing minus signs in template names for repairs
2023-04-15 16:17:01 +02:00
Applevangelist
ee409c45a0 #CTLD
* Allow save/loadback with precise(r) coordinates for vehicles and troops.
2023-04-13 15:51:21 +02:00
Applevangelist
9d5da3388d smaller docu item 2023-04-13 15:28:41 +02:00
Applevangelist
9e138aa149 #DATABASE
* Small fix for CLIENT:FindByName()
2023-04-04 10:33:04 +02:00
Applevangelist
cf94c4d043 #NET
* Fix for ucid can be nil
2023-04-03 12:15:29 +02:00
Applevangelist
63b3807cf6 Added Init Methods to set Unit Positions on Spawn 2023-03-30 12:22:04 +02:00
Applevangelist
f3b6b27521 Small fix for SET_UNIT if used in capture zone coalition with a polygon zone 2023-03-30 09:24:08 +02:00
Applevangelist
ff656182e8 SET_STATIC - Added GetClosestStatic() 2023-03-28 11:03:00 +02:00
Applevangelist
a85ef6e769 Merge remote-tracking branch 'origin/master' 2023-03-23 09:21:46 +01:00
Applevangelist
0bb8c56ea9 Typo fix 2023-03-23 09:21:42 +01:00
Applevangelist
5f108aec23 Typo fix 2023-03-23 09:21:20 +01:00
Applevangelist
bf6683f993 Merge remote-tracking branch 'origin/master' 2023-03-23 08:47:07 +01:00
Applevangelist
b7a5e8dd85 Add'l check in UNIT:GetLife() 2023-03-23 08:47:04 +01:00
Applevangelist
57294aeaf5 Add'l check in UNIT:GetLife() 2023-03-23 08:45:54 +01:00
Applevangelist
20bfb8b08f #AIRBASE
* Added/changed South Atlantic Map:
  ["Hipico_Flying_Club"] = "Hipico Flying Club",
  ["Aeropuerto_de_Gobernador_Gregores"] = "Aeropuerto de Gobernador Gregores",
  ["Aerodromo_O_Higgins"] = "Aerodromo O'Higgins",
  ["Cullen_Airport"] = "Cullen Airport",
  ["Gull_Point"] = "Gull Point",
2023-03-16 08:44:16 +01:00
UglySkyfire
5585a8992c Drop crates from herc as CTLD_Cargo (#1929)
Adds option to drop crates from a herc via parachute drop as CTLD_Cargo that needs proper unpacking - can be picked up be a helo.
2023-03-15 20:12:30 +01:00
Applevangelist
d92a20050c #CTLD
* Added/completed add/get/set functions for stock
2023-03-10 10:09:08 +01:00
Applevangelist
c9a91d0683 #GROUP
* Fix for GetMaxHeight()
2023-03-09 08:49:07 +01:00
Applevangelist
ae6716ac01 #GROUP
* Callsign translation refactor
2023-03-05 11:03:26 +01:00
Applevangelist
34285b26ae #SPAWN
* clarified docu for SpawnScheduled()
* Added parameter to SpawnScheduled() for a delayed spawn
2023-02-28 08:00:06 +01:00
Thomas
5341e5faee Update Enums.lua 2023-02-26 19:03:33 +01:00
Applevangelist
a6224b484e dcs 2023-02-23 10:33:11 +01:00
Applevangelist
e95c1ad3aa #Fixes from dev 2023-02-23 10:30:59 +01:00
Applevangelist
9cfed56847 #CTLD logic correction for loading 2023-02-21 11:38:40 +01:00
Applevangelist
6c1abddb1e #NET added a couple of functions 2023-02-19 17:16:00 +01:00
Applevangelist
7dc239f506 Added CLIENT:FindByPlayerName(Name)
NET - slot blocker comments in log removed
2023-02-19 12:31:32 +01:00
Applevangelist
3e8413c6b7 #NET - further ado 2023-02-18 15:03:53 +01:00
Applevangelist
ff86bfb91d #NET Event blocker fix 2023-02-17 16:41:38 +01:00
Applevangelist
66494b7b5a NET 2023-02-17 16:23:02 +01:00
Applevangelist
973127aa8c #NET Fixes 2023-02-17 15:42:06 +01:00
Applevangelist
83866c3dd3 #NET Fixes 2023-02-17 15:02:11 +01:00
Applevangelist
4797abc287 #NET Bugfix 2023-02-17 14:32:48 +01:00
Applevangelist
50f73f1be2 #NET extended block to UCID 2023-02-17 13:22:10 +01:00
Applevangelist
2ebbc8f466 #NET - added docu 2023-02-17 08:54:15 +01:00
Frank
1a96b30a45 Merge pull request #1922 from FlightControl-Master/FF/MasterDevel
Airspeed
2023-02-16 17:28:13 +01:00
Frank
3b1b57a33e Update Positionable.lua
- Added GetGroundSpeed
2023-02-16 17:20:53 +01:00
Frank
f85c0320ec Airspeed
**POSITIONABLE**
- Added function `GetAirspeedIndicated` to return IAS
- Added function `GetAirspeedTrue` to return TAS

**UTILS**
- Added function `UTILS.IasToTas` to convert IAS to TAS
- Added function `TasToIas` to convert TAS to IAS.

**POINT**
- Added function `COORDINATE:GetWindVec3`
2023-02-16 17:09:12 +01:00
Applevangelist
bae7edb914 #NET
* Added event management for clients and block/unblock functions
2023-02-16 11:41:45 +01:00
Applevangelist
35322399e9 #SPOT - minor fixes 2023-02-15 12:23:31 +01:00
Applevangelist
5d4c0f3c87 Bugfix 2023-02-15 10:31:52 +01:00
Frank
92c5c32e8c Merge pull request #1921 from FlightControl-Master/FF/MasterDevel
Update Range.lua
2023-02-12 13:03:08 +01:00
Frank
c8780b2d5f Update Range.lua
- Coordinate of bomb target is now always updated (not only for moving targets)
- If the target is not alive any more, the last known position is used for its coordinate
2023-02-12 13:00:19 +01:00
Frank
f11dbfa367 Merge branch 'FF/MasterDevel' 2023-02-12 11:49:13 +01:00
Frank
54fb9deb3e Polygon and line drawings 2023-02-12 11:38:46 +01:00
Thomas
d3c34ef04d Update MarkerOps_Base.lua (#1919) 2023-02-11 22:11:21 +01:00
Applevangelist
3d5470eb22 #NET
* Initial release
2023-02-10 11:00:14 +01:00
Applevangelist
2a3d69d0d5 #DETECTION
* Docu fixes
2023-02-09 16:11:45 +01:00
Frank
9862c32378 Update settings.json 2023-02-08 16:30:44 +01:00
Frank
9f02232589 Update settings.json 2023-02-08 16:29:25 +01:00
Frank
ffc86bf046 Unwanted changes 2023-02-08 16:28:04 +01:00
Frank
e4c8ca34ed Update Pathline.lua 2023-02-08 16:11:47 +01:00
Frank
f02b7036a9 PATHLINE 2023-02-08 12:37:18 +01:00
Frank
6adef47340 PATHLINE
Added new class PATHLINE
2023-02-07 22:47:55 +01:00
Thomas
5b044cc036 Update CSAR.lua (#1917) 2023-02-07 07:28:00 +01:00
Mr.Alien
03ba7524b2 Comment out code for scoring hits (#1911) 2023-02-06 08:15:45 +01:00
Frank
772921c6b8 WEAPON
- Added new class WEAPON
2023-02-04 23:31:55 +01:00
Mr.Alien
d4d305d53b Fix confusing hit and kill messages with incorrect math (#1913) 2023-02-04 23:02:36 +01:00
Mr.Alien
c58918e002 Add missing part of code in previous commit (#1910) 2023-02-04 22:36:16 +01:00
Mr.Alien
dec7854476 Fix issue with getting thread level after a kill (#1908) 2023-02-04 22:01:55 +01:00
Mr.Alien
8ca102f584 Scoring credits a kill to player who hit the same unit before it spawned (#1905) 2023-02-04 21:46:34 +01:00
Applevangelist
4748c66d9c #CTLD
* Added method `CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)`
2023-02-04 17:21:16 +01:00
Frank
bf486b9f44 Merge pull request #1901 from MrAlien753/patch-1
fix nil error message in dcs.log
2023-02-03 22:34:01 +01:00
MrAlien753
6ff433ed7a fix nil error message in dcs.log
When instant killing a target in a capture zone nil error message is printed in the dcs.log. This fixes this issue.
2023-02-03 22:14:14 +01:00
Applevangelist
f9aa392c6d #CTLD_HERCULES
* Fixed an issue spawning 90 paratroopers instead of 30
2023-02-02 09:35:53 +01:00
Applevangelist
48721859fa #CTLD
* Added Subcategories for static cargo objects
2023-01-31 11:27:12 +01:00
Applevangelist
ee59d999f5 #POINT
* Added COORDINATE:SetAtLandheight()
2023-01-30 18:00:16 +01:00
Applevangelist
2f4f98cfe0 #CTLD - nobuildmenu option 2023-01-29 18:36:44 +01:00
Frank
ac1f202c19 Update Airboss.lua
- Added CJS Superhornet Mod
2023-01-29 12:53:50 +01:00
Frank
3f97ba3bd7 Database
- Polygon drawings are registered as polygon zones
2023-01-28 18:42:29 +01:00
Frank
819912cfd3 Database
- Polygon drawings are adde as polygon zones to DB
2023-01-28 15:39:42 +01:00
Frank
50ee1ae922 Merge branch 'master' into FF/MasterDevel 2023-01-28 11:09:08 +01:00
grandpaSam
c0442fca68 Changed documentation for Marker.lua and MarkerOps_Base.lua (#1891) 2023-01-28 09:02:15 +01:00
Thomas
c20927b6d7 Update Marker.lua 2023-01-27 18:34:53 +01:00
Jason du Plessis
0d5b6d4c3e #CSAR - Add Persistence (#1889)
* Adds a modified version of ops.CTLD's Persistence to ops.CSAR
2023-01-26 21:11:15 +01:00
Applevangelist
bdc08a530d #AB 2023-01-25 17:54:13 +01:00
Applevangelist
6ab1632b4b #RANGE
* Set Google Key if given

#AIRBASE
* Added 3 Falklands AB to the enumerator
2023-01-25 17:53:29 +01:00
Thomas
cec865b9fb UTILS - exclude 243 MHz and 121.5 MHz 2023-01-25 13:37:56 +01:00
Thomas
d763e924a9 Update Utils.lua (#1886) 2023-01-24 20:16:46 +01:00
Applevangelist
57a30621e1 #CTLD added documentation 2023-01-24 15:26:32 +01:00
Applevangelist
f344084791 #1885
#PSEUDOATC - Menu shows Waypoint name if it has been set
2023-01-24 10:06:05 +01:00
Applevangelist
be68314c3a #CONTROLLABLE
* Added CommandActivateACLS()
* Added CommandDeactivateACLS()
2023-01-23 17:11:18 +01:00
Applevangelist
53cff8229b #CTLD
* Added option movecratesbeforebuild
* Added option surfacetypes for build-in reloads
* Inject function can optionally take surfacetypes
* Inject function can use the center of the zone instead of a random position

#EVENT
* Handle landing event if the place is a SCENERY object (helopad on a map, but not an airbase)

#ZONE
* docu corrections
2023-01-22 13:10:27 +01:00
Frank
1d52e27668 Update Controllable.lua
- Added enroute task SEAD
2023-01-19 19:10:56 +01:00
Applevangelist
6ec867196c #UTILS - make LoadSetOfGroups save(r) for groups spawned with SpawnScheduled 2023-01-19 14:59:54 +01:00
Applevangelist
80798f278c #Controllable - docu changes 2023-01-17 12:09:13 +01:00
Applevangelist
4e61bbb92e Merge remote-tracking branch 'origin/master' 2023-01-17 09:26:16 +01:00
Applevangelist
fabab9bfbb #ATIS docu fix 2023-01-17 09:25:01 +01:00
Applevangelist
4ae0089e4f #CSAR
* Docu corrections
2023-01-15 17:40:03 +01:00
Applevangelist
d82bce79c9 #CSAR
* Docu corrections
2023-01-15 17:38:54 +01:00
Applevangelist
55fcaf1c05 #CTLD - Adde Shark III typename 2023-01-12 13:20:12 +01:00
Applevangelist
e83df502ed #AIRBASE - docu fixes 2023-01-10 13:08:05 +01:00
Applevangelist
6501e89fa2 #PSEUDOATC
* Fix for debug messages
2023-01-10 07:47:56 +01:00
Applevangelist
91801d441f #GROUP
* Improve functionality of GetUnit(x)
* Added GetFirstUnit()
2023-01-09 17:07:21 +01:00
Frank
dd2a4ee7ff COORDINATE
- Added `GetMagneticDeclination` function
2023-01-08 19:32:38 +01:00
Applevangelist
8cedd88ce2 #SCENERY - explain destroy doesn't work 2023-01-05 10:58:40 +01:00
Applevangelist
c3fde2b698 Merge remote-tracking branch 'origin/master' 2023-01-05 10:48:55 +01:00
Applevangelist
59e8aed445 #CSAR fix for beacons 2023-01-05 10:48:50 +01:00
Applevangelist
e47b8f377c #CTLD/CSAR
* Small fix for
2023-01-05 10:45:37 +01:00
Applevangelist
793c0d988e Changes from dev 2023-01-03 10:22:10 +01:00
Applevangelist
b0eef34146 #CONTROLLABLE
* Docu fix

#WAREHOUSE
* Changes from dev

#ZONE
* Additions to ZONE_POLYGON
2023-01-03 10:15:16 +01:00
Applevangelist
3fbfb8b528 #UNIT
* Fix #1865
2023-01-02 17:27:01 +01:00
Thomas
cdd240abb7 PseudoATC - Option to display playername (#1870)
Use `myatc:SetReportPlayername()` to switch this on #1864
2023-01-02 14:28:35 +01:00
Applevangelist
5d802f0e16 #TIMER
* Added `StartIf()`
2023-01-01 12:34:02 +01:00
Applevangelist
8661d07e1e #ATIS
* Make SRS say TACAN and FARP and not spell the single characters
2022-12-29 16:33:13 +01:00
Thomas
41eec658e0 UTILS - Update Load/Save Groups and Statics (#1857)
Addresses #1855 and #1856 

Added options to save groups in a structrued manner, allowing to despawn specific unit-types on a reload. Optionally, cinematic effects like smoke and fires can be put in place of despawned units. For statics, an option to replace them with dead statics has been added, and also cinematic effects.
2022-12-28 15:40:40 +01:00
Thomas
9facf07955 ATIS - added basic FARP support (#1854)
ATIS - added basic FARP support, only works with SRS
2022-12-25 14:18:53 +01:00
Applevangelist
cd4844d26c #EVENT
* Small fix for hit event on coordinates
2022-12-24 12:03:04 +01:00
Applevangelist
9619b11c58 #CTLD
* add sound folder path option
2022-12-23 13:42:14 +01:00
Thomas
535060153a CTLD #1851 Add variable for a sound path (#1852)
CTLD #1851 Add variable for a sound path - defaults to  `self.RadioPath = "l10n/DEFAULT/"`
2022-12-23 13:39:00 +01:00
Applevangelist
f26ff52712 #UNIT
* Docu fix
2022-12-21 14:09:47 +01:00
Applevangelist
4a299ea53f #CTLD
* Added option to inject troops into helos
2022-12-21 14:05:58 +01:00
Applevangelist
6f0ba337c4 #SET_SCENERY
* Added GetAliveSet()
2022-12-21 12:55:01 +01:00
Applevangelist
8df6e2dd57 #UNIT
* Improve GetGroup() as after Dec/22 patch geGroup() might be nil
# SET_UNIT
* Improved Docu
2022-12-19 16:12:09 +01:00
Applevangelist
5010cdff71 #CTLD
* Fix to also save crates which have not been moved
2022-12-19 14:04:30 +01:00
Applevangelist
fea1839c06 #AIRBASE
* Added Rio Chico Airfield
2022-12-16 18:43:50 +01:00
Applevangelist
80e3b157ca #UTILS
* UTILS.LoadSetOfStatics(Path,Filename) ignore statics which do not exist
2022-12-15 18:28:06 +01:00
Applevangelist
d5028d86df #SET_CLIENT
* small addition
2022-12-15 11:49:28 +01:00
Applevangelist
1fac6b7c93 #SET_CLIENT
* Added `FilterCallsigns()` and `FilterPlayernames()`
2022-12-14 14:48:20 +01:00
Applevangelist
089467c15a #AI\_A2A\_GCICAP
* Fix demo mission link
2022-12-14 09:41:35 +01:00
Applevangelist
77e7f767d8 #AIRBASE
* docu correction
2022-12-12 16:23:47 +01:00
Applevangelist
324739aeb9 #SET
* Improve GetRandom() a bit
2022-12-11 15:49:50 +01:00
Applevangelist
84d301c676 #CTLD
* Added disallow building in loadzones: my_ctld.nobuildinloadzones = true
2022-12-09 12:37:39 +01:00
Applevangelist
dcfce8b619 #CTLD - enforce modulation on beacons 2022-12-07 18:50:26 +01:00
Frank
813d4edc97 CONDITION v0.3.0
- Added methods to remove condition functions
- Added option to define output if no condition functions at all were given
-
2022-12-07 18:35:12 +01:00
Applevangelist
9ea4a5dbd4 #CTLD less log noise 2022-12-06 12:49:27 +01:00
Applevangelist
508e36d327 #CTLD Fix for Beacon Zone disappearing too fast 2022-12-03 14:37:01 +01:00
Applevangelist
37b1e7366c *smaller fixes 2022-12-02 16:39:09 +01:00
Thomas
b29b9f1b2c Update README.md 2022-12-01 21:32:19 +01:00
Applevangelist
7865be43bb * Minor fixes 2022-12-01 13:27:58 +01:00
Frank
f17f688a20 Condition and Message 2022-11-30 18:37:14 +01:00
Applevangelist
df2a6a6902 #ATIS
* Added `ATIS:GetSRSText()`
2022-11-29 17:53:41 +01:00
Applevangelist
df0c0ec21e #CTLD
* Small fix for BEACON Zones
2022-11-29 15:39:58 +01:00
Thomas
53d71d9766 Update RAT.lua
Fix #1848
2022-11-28 17:42:09 +01:00
Applevangelist
eb5a72fc27 * less noise 2022-11-27 17:35:13 +01:00
Applevangelist
cec045045e * Less noise 2022-11-27 17:35:13 +01:00
Applevangelist
33f30101d9 Merge remote-tracking branch 'origin/master' 2022-11-23 09:55:50 +01:00
Applevangelist
1e139a6005 #SPAWN
* Fix callsign dupplication of numbers introduced with 2.8
2022-11-23 09:55:47 +01:00
Applevangelist
eacfbad729 #SPAWN
* Fix for unit callsign number duplication since 2.8 release (ED saving callsign.name now as "Texaco11" instead of "Texaco" for the F10 Map overview
2022-11-23 09:48:06 +01:00
Frank
6834a2e083 Spawning FARPS
**SPAWNSTATIC**
- Added event birth when static FARPS are spawned

**EVENT**
- Unknown airbases as inititiator are registered
2022-11-20 20:55:34 +01:00
Frank
2538d583ad Update Suppression.lua
- Fixed routing of group (passing waypoint function was triggered too early due to changed DCS behaviour)
2022-11-19 19:36:55 +01:00
Frank
c9ccc28251 Merge branch 'master' into FF/MasterDevel 2022-11-19 11:44:46 +01:00
Applevangelist
0f1ad9d811 # TaskRecoveryTanker 2022-11-18 11:28:52 +01:00
Applevangelist
93c986f00a #GROUP
* Added additional push of tanker task to GROUP:SetAsRecoveryTanker() for a working setup
2022-11-18 11:23:56 +01:00
Applevangelist
239e2ef86d #GROUP/CONTROLLABLE
* Added RecoveryTanker Task
2022-11-18 09:58:48 +01:00
Applevangelist
5a7a23552d #AIRBASE
* Added enumerators for
-- * AIRBASE.SouthAtlantic.Puerto_Santa_Cruz
-- * AIRBASE.SouthAtlantic.Comandante_Luis_Piedrabuena
-- * AIRBASE.SouthAtlantic.Aerodromo_De_Tolhuin
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
-- * AIRBASE.SouthAtlantic.Rio_Turbio
2022-11-17 17:13:33 +01:00
Applevangelist
4fb98cf72b #CSAR
* Make rescued pilot's weight configureable
2022-11-17 13:54:43 +01:00
Applevangelist
a125497fe7 #SPAWN
* Ensure we have a numbered table for InitRandomizeTemplate/Zone so math.random actually works
* Also, pre-shuffle tables
2022-11-16 11:12:47 +01:00
Applevangelist
ae2196585e #MESSAGE
* added to country
* added a couple of countries to country.id. enumerator
2022-11-16 09:39:54 +01:00
Applevangelist
fbad50973f # Bug fixes 2022-11-14 18:15:55 +01:00
Applevangelist
782cfd1fd0 #ATIS
* Added honor Stop() functionality
2022-11-14 17:36:34 +01:00
Applevangelist
d4a06089c9 #CTLD
* Change call order to move troops, vehicle on `onafter..` internally
* added pseudo-function for "OnBefore..."
2022-11-13 13:37:25 +01:00
Frank
2157f8e8cd Airboss
Take animation for determining the wire
2022-09-26 23:34:00 +02:00
54 changed files with 9118 additions and 2617 deletions

View File

@@ -1687,6 +1687,20 @@ do -- AI_A2A_DISPATCHER
return DefenderSquadron
end
--- Get a resource count from a specific squadron
-- @param #AI_A2A_DISPATCHER self
-- @param #string Squadron Name of the squadron.
-- @return #number Number of airframes available or nil if the squadron does not exist
function AI_A2A_DISPATCHER:QuerySquadron(Squadron)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount))
return Squadron.ResourceCount
end
self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
return nil
end
--- [DEPRECATED - Might create problems launching planes] Set the Squadron visible before startup of the dispatcher.
-- All planes will be spawned as uncontrolled on the parking spot.
@@ -3940,11 +3954,7 @@ do
--
-- # Demo Missions
--
-- ### [AI\_A2A\_GCICAP for Caucasus](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-200%20-%20AI_A2A%20-%20GCICAP%20Demonstration)
-- ### [AI\_A2A\_GCICAP for NTTR](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-210%20-%20NTTR%20AI_A2A_GCICAP%20Demonstration)
-- ### [AI\_A2A\_GCICAP for Normandy](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-220%20-%20NORMANDY%20AI_A2A_GCICAP%20Demonstration)
--
-- ### [AI\_A2A\_GCICAP for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching)
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-A2A%20-%20AI%20A2A%20Dispatching)
--
-- ===
--

View File

@@ -1,4 +1,4 @@
--- **AI** - Create an automated A2G defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAS operations.
--- **AI** - Create an automated A2G defense system with reconnaissance units, coordinating SEAD, BAI and CAS operations.
--
-- ===
--
@@ -951,7 +951,7 @@ do -- AI_A2G_DISPATCHER
AI_A2G_DISPATCHER.DefenseQueue = {}
--- Defense approach types.
-- @type #AI_A2G_DISPATCHER.DefenseApproach
-- @type AI_A2G_DISPATCHER.DefenseApproach
AI_A2G_DISPATCHER.DefenseApproach = {
Random = 1,
Distance = 2,
@@ -1806,6 +1806,19 @@ do -- AI_A2G_DISPATCHER
return DefenderSquadron
end
--- Get a resource count from a specific squadron
-- @param #AI_A2G_DISPATCHER self
-- @param #string Squadron Name of the squadron.
-- @return #number Number of airframes available or nil if the squadron does not exist
function AI_A2G_DISPATCHER:QuerySquadron(Squadron)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
self:T2(string.format("%s = %s",Squadron.Name,Squadron.ResourceCount))
return Squadron.ResourceCount
end
self:F({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
return nil
end
--- Set the Squadron visible before startup of the dispatcher.
-- All planes will be spawned as uncontrolled on the parking spot.
@@ -1839,7 +1852,7 @@ do -- AI_A2G_DISPATCHER
--- Check if the Squadron is visible before startup of the dispatcher.
-- @param #AI_A2G_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #bool true if visible.
-- @return #boolean true if visible.
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.

View File

@@ -947,7 +947,7 @@ do -- AI_AIR_DISPATCHER
AI_AIR_DISPATCHER.DefenseQueue = {}
--- Defense approach types
-- @type #AI_AIR_DISPATCHER.DefenseApproach
-- @type AI_AIR_DISPATCHER.DefenseApproach
AI_AIR_DISPATCHER.DefenseApproach = {
Random = 1,
Distance = 2,
@@ -1852,7 +1852,7 @@ do -- AI_AIR_DISPATCHER
--- Check if the Squadron is visible before startup of the dispatcher.
-- @param #AI_AIR_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #bool true if visible.
-- @return #boolean true if visible.
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.

View File

@@ -147,8 +147,8 @@
-- @image Escorting.JPG
--- @type AI_ESCORT
---
-- @type AI_ESCORT
-- @extends AI.AI_Formation#AI_FORMATION
@@ -168,11 +168,14 @@
--
-- -- First find the GROUP object and the CLIENT object.
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
-- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort".
--
-- -- Now use these 2 objects to construct the new EscortPlanes object.
-- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
--
-- EscortPlanes:MenusAirplanes() -- create menus for airplanes
-- EscortPlanes:__Start(2)
--
--
-- @field #AI_ESCORT
AI_ESCORT = {
ClassName = "AI_ESCORT",
@@ -189,7 +192,7 @@ AI_ESCORT = {
TaskPoints = {}
}
--- @field Functional.Detection#DETECTION_AREAS
-- @field Functional.Detection#DETECTION_AREAS
AI_ESCORT.Detection = nil
--- MENUPARAM type
@@ -211,10 +214,14 @@ AI_ESCORT.Detection = nil
--
-- -- First find the GROUP object and the CLIENT object.
-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor.
-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client.
-- local EscortGroup = SET_GROUP:New():FilterPrefixes("Escort"):FilterOnce() -- The the group name of the escorts contains "Escort".
--
-- -- Now use these 2 objects to construct the new EscortPlanes object.
-- EscortPlanes = AI_ESCORT:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." )
-- EscortPlanes:MenusAirplanes() -- create menus for airplanes
-- EscortPlanes:__Start(2)
--
--
function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing )
local self = BASE:Inherit( self, AI_FORMATION:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT
@@ -227,10 +234,17 @@ function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing )
self.EscortGroupSet = EscortGroupSet
self.EscortGroupSet:SetSomeIteratorLimit( 8 )
self.EscortBriefing = EscortBriefing
self.Menu = {}
self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {}
self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {}
self.Menu.Flare = self.Menu.Flare or {}
self.Menu.Smoke = self.Menu.Smoke or {}
self.Menu.Targets = self.Menu.Targets or {}
self.Menu.ROE = self.Menu.ROE or {}
self.Menu.ROT = self.Menu.ROT or {}
-- if not EscortBriefing then
-- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " ..
@@ -250,7 +264,7 @@ function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing )
EscortGroupSet:ForEachGroup(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
-- Set EscortGroup known at EscortUnit.
if not self.PlayerUnit._EscortGroups then
@@ -325,14 +339,14 @@ function AI_ESCORT:_InitEscortRoute( EscortGroup )
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Core.Set#SET_GROUP EscortGroupSet
function AI_ESCORT:onafterStart( EscortGroupSet )
self:F()
EscortGroupSet:ForEachGroup(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
EscortGroup:WayPointInitialize()
@@ -370,7 +384,7 @@ function AI_ESCORT:onafterStart( EscortGroupSet )
self:_InitFlightMenus()
self.EscortGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
self:_InitEscortMenus( EscortGroup )
@@ -378,7 +392,7 @@ function AI_ESCORT:onafterStart( EscortGroupSet )
self:SetFlightModeFormation( EscortGroup )
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Core.Event#EVENTDATA EventData
function EscortGroup:OnEventDeadOrCrash( EventData )
self:F( { "EventDead", EventData } )
@@ -394,14 +408,14 @@ function AI_ESCORT:onafterStart( EscortGroupSet )
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Core.Set#SET_GROUP EscortGroupSet
function AI_ESCORT:onafterStop( EscortGroupSet )
self:F()
EscortGroupSet:ForEachGroup(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
EscortGroup:WayPointInitialize()
@@ -443,9 +457,9 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @param #number ZLevels The amount of levels on the Z-axis.
-- @return #AI_ESCORT
@@ -493,9 +507,9 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @param #number ZLevels The amount of levels on the Z-axis.
-- @return #AI_ESCORT
@@ -550,7 +564,7 @@ function AI_ESCORT:SetFlightMenuFormation( Formation )
local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation,
function ( self, Formation, ... )
self.EscortGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup, self, Formation, Arguments )
if EscortGroup:IsAir() then
self:E({FormationID=FormationID})
@@ -580,7 +594,7 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart )
@@ -594,7 +608,7 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace )
@@ -609,8 +623,8 @@ end
-- This menu will appear under **Formation**.
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationLeftLine( XStart, YStart, ZStart, ZSpace )
@@ -625,8 +639,8 @@ end
-- This menu will appear under **Formation**.
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationRightLine( XStart, YStart, ZStart, ZSpace )
@@ -642,8 +656,8 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationLeftWing( XStart, XSpace, YStart, ZStart, ZSpace )
@@ -659,8 +673,8 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationRightWing( XStart, XSpace, YStart, ZStart, ZSpace )
@@ -676,9 +690,9 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationCenterWing( XStart, XSpace, YStart, YSpace, ZStart, ZSpace )
@@ -694,9 +708,9 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @return #AI_ESCORT
function AI_ESCORT:MenuFormationVic( XStart, XSpace, YStart, YSpace, ZStart, ZSpace )
@@ -712,9 +726,9 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group.
-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZStart The start position on the Z-axis in meters for the first group.
-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group.
-- @param #number ZLevels The amount of levels on the Z-axis.
-- @return #AI_ESCORT
@@ -764,7 +778,7 @@ end
function AI_ESCORT:SetFlightMenuHoldAtEscortPosition()
for _, MenuHoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition ) do
for _, MenuHoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {} ) do
local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu )
local FlightMenuHoldPosition = MENU_GROUP_COMMAND
@@ -785,7 +799,7 @@ end
function AI_ESCORT:SetEscortMenuHoldAtEscortPosition( EscortGroup )
for _, HoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition ) do
for _, HoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu )
@@ -853,7 +867,7 @@ end
function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition()
for _, MenuHoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition ) do
for _, MenuHoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do
local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu )
local FlightMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND
@@ -874,7 +888,7 @@ end
function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition( EscortGroup )
for _, HoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition ) do
for _, HoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
@@ -999,7 +1013,7 @@ end
function AI_ESCORT:SetFlightMenuFlare()
for _, MenuFlare in pairs( self.Menu.Flare) do
for _, MenuFlare in pairs( self.Menu.Flare or {}) do
local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu )
local FlightMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, FlightMenuReportNavigation )
@@ -1014,7 +1028,7 @@ end
function AI_ESCORT:SetEscortMenuFlare( EscortGroup )
for _, MenuFlare in pairs( self.Menu.Flare) do
for _, MenuFlare in pairs( self.Menu.Flare or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
@@ -1059,7 +1073,7 @@ end
function AI_ESCORT:SetFlightMenuSmoke()
for _, MenuSmoke in pairs( self.Menu.Smoke) do
for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do
local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu )
local FlightMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, FlightMenuReportNavigation )
@@ -1076,7 +1090,7 @@ end
function AI_ESCORT:SetEscortMenuSmoke( EscortGroup )
for _, MenuSmoke in pairs( self.Menu.Smoke) do
for _, MenuSmoke in pairs( self.Menu.Smoke or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
@@ -1169,7 +1183,7 @@ function AI_ESCORT:SetFlightMenuTargets()
local FlightMenuAttackNearbyAir = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest airborne targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Air ):SetTag( "Attack" )
local FlightMenuAttackNearbyGround = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Attack nearest ground targets", self.FlightMenuAttack, AI_ESCORT._FlightAttackNearestTarget, self, self.__Enum.ReportType.Ground ):SetTag( "Attack" )
for _, MenuTargets in pairs( self.Menu.Targets) do
for _, MenuTargets in pairs( self.Menu.Targets or {}) do
MenuTargets.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, MenuTargets.Interval, MenuTargets.Interval )
end
@@ -1179,7 +1193,7 @@ end
function AI_ESCORT:SetEscortMenuTargets( EscortGroup )
for _, MenuTargets in pairs( self.Menu.Targets) do
for _, MenuTargets in pairs( self.Menu.Targets or {} or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
--local EscortMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu )
@@ -1231,7 +1245,7 @@ function AI_ESCORT:MenuAssistedAttack()
self:F()
self.EscortGroupSet:ForSomeGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if not EscortGroup:IsAir() then
-- Request assistance from other escorts.
@@ -1246,7 +1260,7 @@ end
function AI_ESCORT:SetFlightMenuROE()
for _, MenuROE in pairs( self.Menu.ROE) do
for _, MenuROE in pairs( self.Menu.ROE or {}) do
local FlightMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", self.FlightMenu )
local FlightMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", FlightMenuROE, AI_ESCORT._FlightROEHoldFire, self, "Holding weapons!" )
@@ -1261,7 +1275,7 @@ end
function AI_ESCORT:SetEscortMenuROE( EscortGroup )
for _, MenuROE in pairs( self.Menu.ROE) do
for _, MenuROE in pairs( self.Menu.ROE or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
@@ -1302,7 +1316,7 @@ end
function AI_ESCORT:SetFlightMenuROT()
for _, MenuROT in pairs( self.Menu.ROT) do
for _, MenuROT in pairs( self.Menu.ROT or {}) do
local FlightMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", self.FlightMenu )
local FlightMenuROTNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", FlightMenuROT, AI_ESCORT._FlightROTNoReaction, self, "Fighting until death!" )
@@ -1317,7 +1331,7 @@ end
function AI_ESCORT:SetEscortMenuROT( EscortGroup )
for _, MenuROT in pairs( self.Menu.ROT) do
for _, MenuROT in pairs( self.Menu.ROT or {}) do
if EscortGroup:IsAir() then
local EscortGroupName = EscortGroup:GetName()
@@ -1375,7 +1389,7 @@ function AI_ESCORT:SetEscortMenuResumeMission( EscortGroup )
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Wrapper.Group#GROUP OrbitGroup
-- @param Wrapper.Group#GROUP EscortGroup
-- @param #number OrbitHeight
@@ -1419,7 +1433,7 @@ function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSec
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Wrapper.Group#GROUP OrbitGroup
-- @param #number OrbitHeight
-- @param #number OrbitSeconds
@@ -1428,7 +1442,7 @@ function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds )
local EscortUnit = self.PlayerUnit
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup, OrbitGroup )
if EscortGroup:IsAir() then
if OrbitGroup == nil then
@@ -1456,7 +1470,7 @@ end
function AI_ESCORT:_FlightJoinUp()
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_JoinUp( EscortGroup )
@@ -1471,7 +1485,7 @@ end
-- @param #AI_ESCORT self
-- @param #number XStart The start position on the X-axis in meters for the first group.
-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group.
-- @param #nubmer YStart The start position on the Y-axis in meters for the first group.
-- @param #number YStart The start position on the Y-axis in meters for the first group.
-- @return #AI_ESCORT
function AI_ESCORT:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart )
@@ -1483,7 +1497,7 @@ end
function AI_ESCORT:_FlightFormationTrail( XStart, XSpace, YStart )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart )
@@ -1510,7 +1524,7 @@ end
function AI_ESCORT:_FlightFormationStack( XStart, XSpace, YStart, YSpace )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace )
@@ -1533,7 +1547,7 @@ end
function AI_ESCORT:_FlightFlare( Color, Message )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_Flare( EscortGroup, Color, Message )
@@ -1556,7 +1570,7 @@ end
function AI_ESCORT:_FlightSmoke( Color, Message )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_Smoke( EscortGroup, Color, Message )
@@ -1587,7 +1601,7 @@ end
function AI_ESCORT:_FlightSwitchReportNearbyTargets( ReportTargets )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup )
if EscortGroup:IsAir() then
self:_EscortSwitchReportNearbyTargets( EscortGroup, ReportTargets )
@@ -1679,7 +1693,7 @@ function AI_ESCORT:_ScanTargets( ScanDuration )
end
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
-- @param #AI_ESCORT self
function AI_ESCORT.___Resume( EscortGroup, self )
@@ -1701,7 +1715,7 @@ function AI_ESCORT.___Resume( EscortGroup, self )
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Wrapper.Group#GROUP EscortGroup
-- @param #number WayPoint
function AI_ESCORT:_ResumeMission( EscortGroup, WayPoint )
@@ -1723,7 +1737,7 @@ function AI_ESCORT:_ResumeMission( EscortGroup, WayPoint )
end
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item.
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem )
@@ -1743,7 +1757,7 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem )
local AttackUnitTasks = {}
DetectedSet:ForEachUnit(
--- @param Wrapper.Unit#UNIT DetectedUnit
-- @param Wrapper.Unit#UNIT DetectedUnit
function( DetectedUnit, Tasks )
if DetectedUnit:IsAlive() then
AttackUnitTasks[#AttackUnitTasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit )
@@ -1767,7 +1781,7 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem )
local Tasks = {}
DetectedSet:ForEachUnit(
--- @param Wrapper.Unit#UNIT DetectedUnit
-- @param Wrapper.Unit#UNIT DetectedUnit
function( DetectedUnit, Tasks )
if DetectedUnit:IsAlive() then
Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 )
@@ -1795,7 +1809,7 @@ end
function AI_ESCORT:_FlightAttackTarget( DetectedItem )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Core.Group#GROUP EscortGroup
-- @param Core.Group#GROUP EscortGroup
function( EscortGroup, DetectedItem )
if EscortGroup:IsAir() then
self:_AttackTarget( EscortGroup, DetectedItem )
@@ -1842,7 +1856,7 @@ end
---
--- @param #AI_ESCORT self
-- @param #AI_ESCORT self
-- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item.
-- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem
function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem )
@@ -1854,7 +1868,7 @@ function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem )
local Tasks = {}
DetectedSet:ForEachUnit(
--- @param Wrapper.Unit#UNIT DetectedUnit
-- @param Wrapper.Unit#UNIT DetectedUnit
function( DetectedUnit, Tasks )
if DetectedUnit:IsAlive() then
Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 )
@@ -1881,7 +1895,7 @@ end
function AI_ESCORT:_FlightROEHoldFire( EscortROEMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROE( EscortGroup, EscortGroup.OptionROEHoldFire, EscortROEMessage )
end
@@ -1890,7 +1904,7 @@ end
function AI_ESCORT:_FlightROEOpenFire( EscortROEMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROE( EscortGroup, EscortGroup.OptionROEOpenFire, EscortROEMessage )
end
@@ -1899,7 +1913,7 @@ end
function AI_ESCORT:_FlightROEReturnFire( EscortROEMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROE( EscortGroup, EscortGroup.OptionROEReturnFire, EscortROEMessage )
end
@@ -1908,7 +1922,7 @@ end
function AI_ESCORT:_FlightROEWeaponFree( EscortROEMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROE( EscortGroup, EscortGroup.OptionROEWeaponFree, EscortROEMessage )
end
@@ -1924,7 +1938,7 @@ end
function AI_ESCORT:_FlightROTNoReaction( EscortROTMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROT( EscortGroup, EscortGroup.OptionROTNoReaction, EscortROTMessage )
end
@@ -1933,7 +1947,7 @@ end
function AI_ESCORT:_FlightROTPassiveDefense( EscortROTMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROT( EscortGroup, EscortGroup.OptionROTPassiveDefense, EscortROTMessage )
end
@@ -1942,7 +1956,7 @@ end
function AI_ESCORT:_FlightROTEvadeFire( EscortROTMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROT( EscortGroup, EscortGroup.OptionROTEvadeFire, EscortROTMessage )
end
@@ -1951,7 +1965,7 @@ end
function AI_ESCORT:_FlightROTVertical( EscortROTMessage )
self.EscortGroupSet:ForEachGroupAlive(
--- @param Wrapper.Group#GROUP EscortGroup
-- @param Wrapper.Group#GROUP EscortGroup
function( EscortGroup )
self:_ROT( EscortGroup, EscortGroup.OptionROTVertical, EscortROTMessage )
end
@@ -2178,5 +2192,3 @@ function AI_ESCORT:_FlightReportTargetsScheduler()
return false
end

View File

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

View File

@@ -88,6 +88,8 @@ DATABASE = {
WAREHOUSES = {},
FLIGHTGROUPS = {},
FLIGHTCONTROLS = {},
OPSZONES = {},
PATHLINES = {},
}
local _DATABASECoalition =
@@ -244,7 +246,7 @@ function DATABASE:FindAirbase( AirbaseName )
end
do -- Zones
do -- Zones and Pathlines
--- Finds a @{Core.Zone} based on the zone name.
-- @param #DATABASE self
@@ -267,7 +269,6 @@ do -- Zones
end
end
--- Deletes a @{Core.Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
@@ -277,6 +278,39 @@ do -- Zones
end
--- Adds a @{Core.Pathline} based on its name in the DATABASE.
-- @param #DATABASE self
-- @param #string PathlineName The name of the pathline
-- @param Core.Pathline#PATHLINE Pathline The pathline.
function DATABASE:AddPathline( PathlineName, Pathline )
if not self.PATHLINES[PathlineName] then
self.PATHLINES[PathlineName]=Pathline
end
end
--- Finds a @{Core.Pathline} by its name.
-- @param #DATABASE self
-- @param #string PathlineName The name of the Pathline.
-- @return Core.Pathline#PATHLINE The found PATHLINE.
function DATABASE:FindPathline( PathlineName )
local pathline = self.PATHLINES[PathlineName]
return pathline
end
--- Deletes a @{Core.Pathline} from the DATABASE based on its name.
-- @param #DATABASE self
-- @param #string PathlineName The name of the PATHLINE.
function DATABASE:DeletePathline( PathlineName )
self.PATHLINES[PathlineName]=nil
return self
end
--- Private method that registers new ZONE_BASE derived objects within the DATABASE Object.
-- @param #DATABASE self
-- @return #DATABASE self
@@ -370,10 +404,147 @@ do -- Zones
-- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon )
end
end
end
-- Drawings as zones
if env.mission.drawings and env.mission.drawings.layers then
-- Loop over layers.
for layerID, layerData in pairs(env.mission.drawings.layers or {}) do
-- Loop over objects in layers.
for objectID, objectData in pairs(layerData.objects or {}) do
-- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
if objectData.polygonMode and (objectData.polygonMode=="free") and objectData.points and #objectData.points>=4 then
---
-- Drawing: Polygon free
---
-- Name of the zone.
local ZoneName=objectData.name or "Unknown free Polygon Drawing"
-- Reference point. All other points need to be translated by this.
local vec2={x=objectData.mapX, y=objectData.mapY}
-- Debug stuff.
--local vec3={x=objectData.mapX, y=0, z=objectData.mapY}
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
--trigger.action.markToAll(id, "mapXY", vec3)
-- Copy points array.
local points=UTILS.DeepCopy(objectData.points)
-- Translate points.
for i,_point in pairs(points) do
local point=_point --DCS#Vec2
points[i]=UTILS.Vec2Add(point, vec2)
end
-- Remove last point.
table.remove(points, #points)
-- Debug output
self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)", ZoneName, #points))
-- Create new polygon zone.
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points)
-- Set color.
Zone:SetColor({1, 0, 0}, 0.15)
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
-- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice)
elseif objectData.polygonMode and objectData.polygonMode=="rect" then
---
-- Drawing: Polygon rect
---
-- Name of the zone.
local ZoneName=objectData.name or "Unknown rect Polygon Drawing"
-- Reference point (center of the rectangle).
local vec2={x=objectData.mapX, y=objectData.mapY}
-- For a rectangular polygon drawing, we have the width (y) and height (x).
local w=objectData.width
local h=objectData.height
-- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it).
local points={}
points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left
points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right
points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right
points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left
--local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY")
-- Debug output
self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)", ZoneName, #points))
-- Create new polygon zone.
local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points)
-- Set color.
Zone:SetColor({1, 0, 0}, 0.15)
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then
---
-- Drawing: Line (segments, segment or free)
---
-- Name of the zone.
local Name=objectData.name or "Unknown Line Drawing"
-- Reference point. All other points need to be translated by this.
local vec2={x=objectData.mapX, y=objectData.mapY}
-- Copy points array.
local points=UTILS.DeepCopy(objectData.points)
-- Translate points.
for i,_point in pairs(points) do
local point=_point --DCS#Vec2
points[i]=UTILS.Vec2Add(point, vec2)
end
-- Debug output
self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points))
-- Create new polygon zone.
local Pathline=PATHLINE:NewFromVec2Array(Name, points)
-- Set color.
--Zone:SetColor({1, 0, 0}, 0.15)
-- Add zone.
self:AddPathline(Name,Pathline)
end
end
end
end
end
end -- zone
@@ -410,6 +581,46 @@ do -- Zone_Goal
end
end -- Zone_Goal
do -- OpsZone
--- Finds a @{Ops.OpsZone#OPSZONE} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Ops.OpsZone#OPSZONE The found OPSZONE.
function DATABASE:FindOpsZone( ZoneName )
local ZoneFound = self.OPSZONES[ZoneName]
return ZoneFound
end
--- Adds a @{Ops.OpsZone#OPSZONE} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param Ops.OpsZone#OPSZONE OpsZone The zone.
function DATABASE:AddOpsZone( OpsZone )
if OpsZone then
local ZoneName=OpsZone:GetName()
if not self.OPSZONES[ZoneName] then
self.OPSZONES[ZoneName] = OpsZone
end
end
end
--- Deletes a @{Ops.OpsZone#OPSZONE} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteOpsZone( ZoneName )
self.OPSZONES[ZoneName] = nil
end
end -- OpsZone
do -- cargo
--- Adds a Cargo based on the Cargo Name in the DATABASE.
@@ -913,7 +1124,7 @@ end
-- @param #string AirbaseName Name of the airbase.
-- @return #number Category.
function DATABASE:GetCategoryFromAirbase( AirbaseName )
return self.AIRBASES[AirbaseName]:GetCategory()
return self.AIRBASES[AirbaseName]:GetAirbaseCategory()
end
@@ -991,7 +1202,7 @@ function DATABASE:_RegisterClients()
for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do
self:I(string.format("Register Client: %s", tostring(ClientName)))
local client=self:AddClient( ClientName )
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y)
end
return self
@@ -1027,11 +1238,26 @@ function DATABASE:_RegisterAirbases()
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
self:_RegisterAirbase(DCSAirbase)
end
return self
end
--- Register a DCS airbase.
-- @param #DATABASE self
-- @param DCS#Airbase airbase Airbase.
-- @return #DATABASE self
function DATABASE:_RegisterAirbase(airbase)
if airbase then
-- Get the airbase name.
local DCSAirbaseName = DCSAirbase:getName()
local DCSAirbaseName = airbase:getName()
-- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now.
local airbaseID=DCSAirbase:getID()
local airbaseID=airbase:getID()
-- Add and register airbase.
local airbase=self:AddAirbase( DCSAirbaseName )
@@ -1065,20 +1291,23 @@ function DATABASE:_EventOnBirth( Event )
if Event.IniDCSUnit then
if Event.IniObjectCategory == 3 then
if Event.IniObjectCategory == Object.Category.STATIC then
-- Add static object to DB.
self:AddStatic( Event.IniDCSUnitName )
else
if Event.IniObjectCategory == 1 then
if Event.IniObjectCategory == Object.Category.UNIT then
-- Add unit and group to DB.
self:AddUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName )
-- Add airbase if it was spawned later in the mission.
-- A unit can also be an airbase (e.g. ships).
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
-- Add airbase if it was spawned later in the mission.
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
@@ -1086,7 +1315,7 @@ function DATABASE:_EventOnBirth( Event )
end
end
if Event.IniObjectCategory == 1 then
if Event.IniObjectCategory == Object.Category.UNIT then
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
@@ -1411,10 +1640,10 @@ end
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called object in the database. The function needs to accept a CLIENT parameter.
-- @return #DATABASE self
function DATABASE:ForEachClient( IteratorFunction, ... )
function DATABASE:ForEachClient( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.CLIENTS )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.CLIENTS )
return self
end
@@ -1423,10 +1652,10 @@ end
-- @param #DATABASE self
-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a CLIENT parameter.
-- @return #DATABASE self
function DATABASE:ForEachCargo( IteratorFunction, ... )
function DATABASE:ForEachCargo( IteratorFunction, FinalizeFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self.CARGOS )
self:ForEach( IteratorFunction, FinalizeFunction, arg, self.CARGOS )
return self
end
@@ -1652,7 +1881,7 @@ function DATABASE:_RegisterTemplates()
if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
else

View File

@@ -305,8 +305,8 @@ EVENTS = {
-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object.
-- @field #string PlaceName The name of the airbase.
--
-- @field #table weapon The weapon used during the event.
-- @field #table Weapon
-- @field DCS#Weapon weapon The weapon used during the event.
-- @field DCS#Weapon Weapon The weapon used during the event.
-- @field #string WeaponName Name of the weapon.
-- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon.
--
@@ -1130,7 +1130,7 @@ function EVENT:onEvent( Event )
Event.IniUnitName = Event.IniDCSUnitName
Event.IniDCSGroup = Event.IniDCSUnit:getGroup()
Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName )
if not Event.IniUnit then
-- Unit can be a CLIENT. Most likely this will be the case ...
Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true )
@@ -1165,8 +1165,7 @@ function EVENT:onEvent( Event )
if Event.IniObjectCategory == Object.Category.SCENERY then
---
-- Scenery
---
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
@@ -1186,6 +1185,12 @@ function EVENT:onEvent( Event )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
-- If the airbase does not exist in the DB, we add it (e.g. when FARPS are spawned).
if not Event.IniUnit then
_DATABASE:_RegisterAirbase(Event.initiator)
Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName)
end
end
end
@@ -1275,9 +1280,11 @@ function EVENT:onEvent( Event )
--local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :(
-- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground.
--Event.Place=UNIT:Find(Event.place)
else
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
else
if Event.place:isExist() and Event.place:getCategory() ~= Object.Category.SCENERY then
Event.Place=AIRBASE:Find(Event.place)
Event.PlaceName=Event.Place:GetName()
end
end
end

View File

@@ -5,13 +5,19 @@
-- * Create an easy way to tap into markers added to the F10 map by users.
-- * Recognize own tag and list of keywords.
-- * Matched keywords are handed down to functions.
-- ##Listen for your tag
-- myMarker = MARKEROPS_BASE:New("tag", {}, false)
-- function myMarker:OnAfterMarkChanged(From, Event, To, Text, Keywords, Coord, idx)
--
-- end
-- Make sure to use the "MarkChanged" event as "MarkAdded" comes in right after the user places a blank marker and your callback will never be called.
--
-- ===
--
-- ### Author: **Applevangelist**
--
-- Date: 5 May 2021
-- Last Update: Sep 2022
-- Last Update: Feb 2023
--
-- ===
---
@@ -44,7 +50,7 @@ MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.1.0",
version = "0.1.1",
debug = false,
Casesensitive = true,
}
@@ -118,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive)
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
-- @param #number idx DCS Marker ID
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
-- @param #MARKEROPS_BASE self
@@ -166,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord)
self:MarkChanged(Eventtext,matchtable,coord,Event.idx)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then

View File

@@ -98,7 +98,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen
self.MessageType = nil
-- When no MessageCategory is given, we don't show it as a title...
-- When no MessageCategory is given, we don't show it as a title...
if MessageCategory and MessageCategory ~= "" then
if MessageCategory:sub( -1 ) ~= "\n" then
self.MessageCategory = MessageCategory .. ": "
@@ -204,19 +204,20 @@ 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.
-- @param #MESSAGE self
-- @param Wrapper.Group#GROUP Group to which the message is displayed.
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object.
function MESSAGE:ToGroup( Group, Settings )
self:F( Group.GroupName )
@@ -241,6 +242,7 @@ end
--- Sends a MESSAGE to a Unit.
-- @param #MESSAGE self
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object.
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
@@ -262,6 +264,41 @@ function MESSAGE:ToUnit( Unit, Settings )
return self
end
--- Sends a MESSAGE to a Country.
-- @param #MESSAGE self
-- @param #number Country to which the message is displayed, e.g. country.id.GERMANY. For all country numbers see here: [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_country)
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object.
function MESSAGE:ToCountry( Country, Settings )
self:F(Country )
if Country then
if self.MessageType then
local Settings = Settings 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.outTextForCountry( Country, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
end
return self
end
--- Sends a MESSAGE to a Country.
-- @param #MESSAGE self
-- @param #number Country to which the message is displayed, , e.g. country.id.GERMANY. For all country numbers see here: [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_enum_country)
-- @param #boolean Condition Sends the message only if the condition is true.
-- @param Core.Settings#Settings Settings (Optional) Settings for message display.
-- @return #MESSAGE Message object.
function MESSAGE:ToCountryIf( Country, Condition, Settings )
self:F(Country )
if Country and Condition == true then
self:ToCountry( Country, Settings )
end
return self
end
--- Sends a MESSAGE to the Blue coalition.
-- @param #MESSAGE self
-- @return #MESSAGE
@@ -386,6 +423,7 @@ end
--- Sends a MESSAGE to all players if the given Condition is true.
-- @param #MESSAGE self
-- @param #boolean Condition
-- @return #MESSAGE
function MESSAGE:ToAllIf( Condition )
@@ -395,3 +433,24 @@ function MESSAGE:ToAllIf( Condition )
return self
end
--- Sends a MESSAGE to DCS log file.
-- @param #MESSAGE self
-- @return #MESSAGE self
function MESSAGE:ToLog()
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
return self
end
--- Sends a MESSAGE to DCS log file if the given Condition is true.
-- @param #MESSAGE self
-- @return #MESSAGE self
function MESSAGE:ToLogIf( Condition )
if Condition and Condition == true then
env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" ))
end
return self
end

View File

@@ -0,0 +1,370 @@
--- **Core** - Path from A to B.
--
-- **Main Features:**
--
-- * Path from A to B
-- * Arbitrary number of points
-- * Automatically from lines drawtool
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Pathline
-- @image CORE_Pathline.png
--- PATHLINE class.
-- @type PATHLINE
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the path line.
-- @field #table points List of 3D points defining the path.
-- @extends Core.Base#BASE
--- *The shortest distance between two points is a straight line.* -- Archimedes
--
-- ===
--
-- # The PATHLINE Concept
--
-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height
-- and the water depth (if over sea).
--
-- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database.
-- They can be accessed with the @{#PATHLINE.FindByName) function.
--
-- # Constructor
--
-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3}
--
-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively.
--
-- # Line Drawings
--
-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines.
-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one
-- you specify in the draw panel.
--
-- # Mark on F10 map
--
-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and
-- water depth.
--
-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`).
--
-- @field #PATHLINE
PATHLINE = {
ClassName = "PATHLINE",
lid = nil,
points = {},
}
--- Point of line.
-- @type PATHLINE.Point
-- @field DCS#Vec3 vec3 3D position.
-- @field DCS#Vec2 vec2 2D position.
-- @field #number surfaceType Surface type.
-- @field #number landHeight Land height in meters.
-- @field #number depth Water depth in meters.
-- @field #number markerID Marker ID.
--- PATHLINE class version.
-- @field #string version
PATHLINE.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new PATHLINE object. Points need to be added later.
-- @param #PATHLINE self
-- @param #string Name Name of the path.
-- @return #PATHLINE self
function PATHLINE:New(Name)
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, BASE:New()) --#PATHLINE
self.name=Name or "Unknown Path"
self.lid=string.format("PATHLINE %s | ", Name)
return self
end
--- Create a new PATHLINE object from a given list of 2D points.
-- @param #PATHLINE self
-- @param #string Name Name of the pathline.
-- @param #table Vec2Array List of DCS#Vec2 points.
-- @return #PATHLINE self
function PATHLINE:NewFromVec2Array(Name, Vec2Array)
local self=PATHLINE:New(Name)
for i=1,#Vec2Array do
self:AddPointFromVec2(Vec2Array[i])
end
return self
end
--- Create a new PATHLINE object from a given list of 3D points.
-- @param #PATHLINE self
-- @param #string Name Name of the pathline.
-- @param #table Vec3Array List of DCS#Vec3 points.
-- @return #PATHLINE self
function PATHLINE:NewFromVec3Array(Name, Vec3Array)
local self=PATHLINE:New(Name)
for i=1,#Vec3Array do
self:AddPointFromVec3(Vec3Array[i])
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find a pathline in the database.
-- @param #PATHLINE self
-- @param #string Name The name of the pathline.
-- @return #PATHLINE self
function PATHLINE:FindByName(Name)
local pathline = _DATABASE:FindPathline(Name)
return pathline
end
--- Add a point to the path from a given 2D position. The third dimension is determined from the land height.
-- @param #PATHLINE self
-- @param DCS#Vec2 Vec2 The 2D vector (x,y) to add.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec2(Vec2)
if Vec2 then
local point=self:_CreatePoint(Vec2)
table.insert(self.points, point)
end
return self
end
--- Add a point to the path from a given 3D position.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec3 The 3D vector (x,y) to add.
-- @return #PATHLINE self
function PATHLINE:AddPointFromVec3(Vec3)
if Vec3 then
local point=self:_CreatePoint(Vec3)
table.insert(self.points, point)
end
return self
end
--- Get name of pathline.
-- @param #PATHLINE self
-- @return #string Name of the pathline.
function PATHLINE:GetName()
return self.name
end
--- Get number of points.
-- @param #PATHLINE self
-- @return #number Number of points.
function PATHLINE:GetNumberOfPoints()
local N=#self.points
return N
end
--- Get points of pathline. Not that points are tables, that contain more information as just the 2D or 3D position but also the surface type etc.
-- @param #PATHLINE self
-- @return <#PATHLINE.Point> List of points.
function PATHLINE:GetPoints()
return self.points
end
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec3> List of DCS#Vec3 points.
function PATHLINE:GetPoints3D()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
table.insert(vecs, point.vec3)
end
return vecs
end
--- Get 2D points of pathline.
-- @param #PATHLINE self
-- @return <DCS#Vec2> List of DCS#Vec2 points.
function PATHLINE:GetPoints2D()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
table.insert(vecs, point.vec2)
end
return vecs
end
--- Get COORDINATES of pathline. Note that COORDINATE objects are created when calling this function. That does involve deep copy calls and can have an impact on performance if done too often.
-- @param #PATHLINE self
-- @return <Core.Point#COORDINATE> List of COORDINATES points.
function PATHLINE:GetCoordinats()
local vecs={}
for _,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
local coord=COORDINATE:NewFromVec3(point.vec3)
end
return vecs
end
--- Get the n-th point of the pathline.
-- @param #PATHLINE self
-- @param #number n The index of the point. Default is the first point.
-- @return #PATHLINE.Point Point.
function PATHLINE:GetPointFromIndex(n)
local N=self:GetNumberOfPoints()
n=n or 1
local point=nil --#PATHLINE.Point
if n>=1 and n<=N then
point=self.point[n]
else
self:E(self.lid..string.format("ERROR: No point in pathline for N=%s", tostring(n)))
end
return point
end
--- Get the 3D position of the n-th point.
-- @param #PATHLINE self
-- @param #number n The n-th point.
-- @return DCS#VEC3 Position in 3D.
function PATHLINE:GetPoint3DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec3
end
return nil
end
--- Get the 2D position of the n-th point.
-- @param #PATHLINE self
-- @param #number n The n-th point.
-- @return DCS#VEC2 Position in 3D.
function PATHLINE:GetPoint2DFromIndex(n)
local point=self:GetPointFromIndex(n)
if point then
return point.vec2
end
return nil
end
--- Mark points on F10 map.
-- @param #PATHLINE self
-- @param #boolean Switch If `true` or nil, set marks. If `false`, remove marks.
-- @return <DCS#Vec3> List of DCS#Vec3 points.
function PATHLINE:MarkPoints(Switch)
for i,_point in pairs(self.points) do
local point=_point --#PATHLINE.Point
if Switch==false then
if point.markerID then
UTILS.RemoveMark(point.markerID, Delay)
end
else
if point.markerID then
UTILS.RemoveMark(point.markerID)
end
point.markerID=UTILS.GetMarkID()
local text=string.format("Pathline %s: Point #%d\nSurface Type=%d\nHeight=%.1f m\nDepth=%.1f m", self.name, i, point.surfaceType, point.landHeight, point.depth)
trigger.action.markToAll(point.markerID, text, point.vec3, "")
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get 3D points of pathline.
-- @param #PATHLINE self
-- @param DCS#Vec3 Vec Position vector. Can also be a DCS#Vec2 in which case the altitude at landheight is taken.
-- @return #PATHLINE.Point
function PATHLINE:_CreatePoint(Vec)
local point={} --#PATHLINE.Point
if Vec.z then
-- Given vec is 3D
point.vec3=UTILS.DeepCopy(Vec)
point.vec2={x=Vec.x, y=Vec.z}
else
-- Given vec is 2D
point.vec2=UTILS.DeepCopy(Vec)
point.vec3={x=Vec.x, y=land.getHeight(Vec), z=Vec.y}
end
-- Get surface type.
point.surfaceType=land.getSurfaceType(point.vec2)
-- Get land height and depth.
point.landHeight, point.depth=land.getSurfaceHeightWithSeabed(point.vec2)
point.markerID=nil
return point
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -406,6 +406,42 @@ do -- COORDINATE
return self
end
--- Returns the magnetic declination at the given coordinate.
-- NOTE that this needs `require` to be available so you need to desanitize the `MissionScripting.lua` file in your DCS/Scrips folder.
-- If `require` is not available, a constant value for the whole map.
-- @param #COORDINATE self
-- @param #number Month (Optional) The month at which the declination is calculated. Default is the mission month.
-- @param #number Year (Optional) The year at which the declination is calculated. Default is the mission year.
-- @return #number Magnetic declination in degrees.
function COORDINATE:GetMagneticDeclination(Month, Year)
local decl=UTILS.GetMagneticDeclination()
if require then
local magvar = require('magvar')
if magvar then
local date, year, month, day=UTILS.GetDCSMissionDate()
magvar.init(Month or month, Year or year)
local lat, lon=self:GetLLDDM()
decl=magvar.get_mag_decl(lat, lon)
if decl then
decl=math.deg(decl)
end
end
else
self:T("The require package is not available. Using constant value for magnetic declination")
end
return decl
end
--- Returns the coordinate from the latitude and longitude given in decimal degrees.
-- @param #COORDINATE self
@@ -567,6 +603,46 @@ do -- COORDINATE
return set
end
--- Scan/find STATICS within a certain radius around the coordinate using the world.searchObjects() DCS API function.
-- @param #COORDINATE self
-- @param #number radius (Optional) Scan radius in meters. Default 100 m.
-- @return Core.Set#SET_UNIT Set of units.
function COORDINATE:ScanStatics(radius)
local _,_,_,_,statics=self:ScanObjects(radius, false, true, false)
local set=SET_STATIC:New()
for _,stat in pairs(statics) do
set:AddStatic(STATIC:Find(stat))
end
return set
end
--- Find the closest static to the COORDINATE within a certain radius.
-- @param #COORDINATE self
-- @param #number radius Scan radius in meters. Default 100 m.
-- @return Wrapper.Static#STATIC The closest static or #nil if no unit is inside the given radius.
function COORDINATE:FindClosestStatic(radius)
local units=self:ScanStatics(radius)
local umin=nil --Wrapper.Unit#UNIT
local dmin=math.huge
for _,_unit in pairs(units.Set) do
local unit=_unit --Wrapper.Static#STATIC
local coordinate=unit:GetCoordinate()
local d=self:Get2DDistance(coordinate)
if d<dmin then
dmin=d
umin=unit
end
end
return umin
end
--- Find the closest unit to the COORDINATE within a certain radius.
-- @param #COORDINATE self
@@ -1018,28 +1094,55 @@ do -- COORDINATE
return heading
end
--- Returns the 3D wind direction vector. Note that vector points into the direction the wind in blowing to.
-- @param #COORDINATE self
-- @param #number height (Optional) parameter specifying the height ASL in meters. The minimum height will be always be the land height since the wind is zero below the ground.
-- @param #boolean turbulence (Optional) If `true`, include turbulence.
-- @return DCS#Vec3 Wind 3D vector. Components in m/s.
function COORDINATE:GetWindVec3(height, turbulence)
-- We at 0.1 meters to be sure to be above ground since wind is zero below ground level.
local landheight=self:GetLandHeight()+0.1
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
-- Get wind velocity vector.
local wind = nil --DCS#Vec3
if turbulence then
wind = atmosphere.getWindWithTurbulence(point)
else
wind = atmosphere.getWind(point)
end
return wind
end
--- Returns the wind direction (from) and strength.
-- @param #COORDINATE self
-- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground.
-- @return Direction the wind is blowing from in degrees.
-- @return Wind strength in m/s.
function COORDINATE:GetWind(height)
local landheight=self:GetLandHeight()+0.1 -- we at 0.1 meters to be sure to be above ground since wind is zero below ground level.
local point={x=self.x, y=math.max(height or self.y, landheight), z=self.z}
-- get wind velocity vector
local wind = atmosphere.getWind(point)
local direction = math.deg(math.atan2(wind.z, wind.x))
if direction < 0 then
direction = 360 + direction
end
-- Convert to direction to from direction
-- @param #number height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground.
-- @param #boolean turbulence If `true`, include turbulence. If `false` or `nil`, wind without turbulence.
-- @return #number Direction the wind is blowing from in degrees.
-- @return #number Wind strength in m/s.
function COORDINATE:GetWind(height, turbulence)
-- Get wind velocity vector
local wind = self:GetWindVec3(height, turbulence)
-- Calculate the direction of the vector.
local direction=UTILS.VecHdg(wind)
-- Invert "to" direction to "from" direction.
if direction > 180 then
direction = direction-180
else
direction = direction+180
end
local strength=math.sqrt((wind.x)^2+(wind.z)^2)
-- Return wind direction and strength km/h.
-- Wind strength in m/s.
local strength=UTILS.VecNorm(wind) -- math.sqrt((wind.x)^2+(wind.z)^2)
-- Return wind direction and strength.
return direction, strength
end
@@ -1097,12 +1200,15 @@ do -- COORDINATE
--- Return the 3D distance in meters between the target COORDINATE and the COORDINATE.
-- @param #COORDINATE self
-- @param #COORDINATE TargetCoordinate The target COORDINATE.
-- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3.
-- @return DCS#Distance Distance The distance in meters.
function COORDINATE:Get3DDistance( TargetCoordinate )
local TargetVec3 = TargetCoordinate:GetVec3()
--local TargetVec3 = TargetCoordinate:GetVec3()
local TargetVec3 = {x=TargetCoordinate.x, y=TargetCoordinate.y, z=TargetCoordinate.z}
local SourceVec3 = self:GetVec3()
return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
--local dist=( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5
local dist=UTILS.VecDist3D(TargetVec3, SourceVec3)
return dist
end
@@ -1285,7 +1391,15 @@ do -- COORDINATE
self.y=alt
return self
end
--- Set altitude to be at land height (i.e. on the ground!)
-- @param #COORDINATE self
function COORDINATE:SetAtLandheight()
local alt=self:GetLandHeight()
self.y=alt
return self
end
--- Build an air type route point.
-- @param #COORDINATE self
-- @param #COORDINATE.WaypointAltType AltType The altitude type.
@@ -1911,7 +2025,6 @@ do -- COORDINATE
-- @param #COORDINATE self
-- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object.
function COORDINATE:StopBigSmokeAndFire( name )
self:F2( { name = name } )
name = name or self.firename
trigger.action.effectSmokeStop( name )
end
@@ -2831,8 +2944,13 @@ do -- COORDINATE
if alt < 1 then
alttext = "very low"
end
local track = UTILS.BearingToCardinal(bearing) or "North"
-- corrected Track to be direction of travel of bogey (self in this case)
local track = "Maneuver"
if self.Heading then
track = UTILS.BearingToCardinal(self.Heading) or "North"
end
if rangeNM > 3 then
if SSML then -- google says "oh" instead of zero, be aware
@@ -3210,7 +3328,52 @@ do -- COORDINATE
return self:GetTemperatureText( nil, Settings )
end
--- Function to check if a coordinate is in a steep (>8% elevation) area of the map
-- @param #COORDINATE self
-- @param #number Radius (Optional) Radius to check around the coordinate, defaults to 50m (100m diameter)
-- @param #number Minelevation (Optional) Elevation from which on a area is defined as steep, defaults to 8% (8m height gain across 100 meters)
-- @return #boolen IsSteep If true, area is steep
-- @return #number MaxElevation Elevation in meters measured over 100m
function COORDINATE:IsInSteepArea(Radius,Minelevation)
local steep = false
local elev = Minelevation or 8
local bdelta = 0
local h0 = self:GetLandHeight()
local radius = Radius or 50
local diam = radius * 2
for i=0,150,30 do
local polar = math.fmod(i+180,360)
local c1 = self:Translate(radius,i,false,false)
local c2 = self:Translate(radius,polar,false,false)
local h1 = c1:GetLandHeight()
local h2 = c2:GetLandHeight()
local d1 = math.abs(h1-h2)
local d2 = math.abs(h0-h1)
local d3 = math.abs(h0-h2)
local dm = d1 > d2 and d1 or d2
local dm1 = dm > d3 and dm or d3
bdelta = dm1 > bdelta and dm1 or bdelta
self:T(string.format("d1=%d, d2=%d, d3=%d, max delta=%d",d1,d2,d3,bdelta))
end
local steepness = bdelta / (radius / 100)
if steepness >= elev then steep = true end
return steep, math.floor(steepness)
end
--- Function to check if a coordinate is in a flat (<8% elevation) area of the map
-- @param #COORDINATE self
-- @param #number Radius (Optional) Radius to check around the coordinate, defaults to 50m (100m diameter)
-- @param #number Minelevation (Optional) Elevation from which on a area is defined as steep, defaults to 8% (8m height gain across 100 meters)
-- @return #boolen IsFlat If true, area is flat
-- @return #number MaxElevation Elevation in meters measured over 100m
function COORDINATE:IsInFlatArea(Radius,Minelevation)
local steep, elev = self:IsInSteepArea(Radius,Minelevation)
local flat = not steep
return flat, elev
end
end
do -- POINT_VEC3

File diff suppressed because it is too large Load Diff

View File

@@ -335,7 +335,7 @@ do -- SETTINGS
--- Sets the SETTINGS MGRS accuracy.
-- @param #SETTINGS self
-- @param #number MGRS_Accuracy
-- @param #number MGRS_Accuracy 0 to 5
-- @return #SETTINGS
function SETTINGS:SetMGRS_Accuracy( MGRS_Accuracy )
self.MGRS_Accuracy = MGRS_Accuracy

View File

@@ -162,7 +162,17 @@
-- ### Array formation
--
-- * @{#SPAWN.InitArray}(): Make groups visible before they are actually activated, and order these groups like a battalion in an array.
--
--
-- ### Group initial position - if wanted different from template position, for use with e.g. @{#SPAWN.SpawnScheduled}().
--
-- * @{#SPAWN.InitPositionCoordinate}(): Set initial position of group via a COORDINATE.
-- * @{#SPAWN.InitPositionVec2}(): Set initial position of group via a VEC2.
--
-- ### Set the positions of a group's units to absolute positions, or relative positions to unit No. 1
--
-- * @{#SPAWN.InitSetUnitRelativePositions}(): Spawn the UNITs of this group with individual relative positions to unit #1 and individual headings.
-- * @{#SPAWN.InitSetUnitAbsolutePositions}(): Spawn the UNITs of this group with individual absolute positions and individual headings.
--
-- ### Position randomization
--
-- * @{#SPAWN.InitRandomizePosition}(): Randomizes the position of @{Wrapper.Group}s that are spawned within a **radius band**, given an Outer and Inner radius, from the point that the spawn happens.
@@ -268,7 +278,7 @@ SPAWN = {
-- @type SPAWN.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff
--- @field #SPAWN.Takeoff Takeoff
-- @field #SPAWN.Takeoff Takeoff
SPAWN.Takeoff = {
Air = 1,
Runway = 2,
@@ -276,7 +286,7 @@ SPAWN.Takeoff = {
Cold = 4,
}
--- @type SPAWN.SpawnZoneTable
-- @type SPAWN.SpawnZoneTable
-- @list <Core.Zone#ZONE_BASE> SpawnZone
--- Creates the main object to spawn a @{Wrapper.Group} defined in the DCS ME.
@@ -335,7 +345,7 @@ end
-- @param #SPAWN self
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template.
-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime.
-- @return #SPAWN
-- @return #SPAWN self
-- @usage
-- -- NATO helicopters engaging in the battle field.
-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
@@ -385,32 +395,129 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
return self
end
--- Creates a new SPAWN instance to create new groups based on the provided template.
--- Creates a new SPAWN instance to create new groups based on the provided template. This will also register the template for future use.
-- @param #SPAWN self
-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure!
-- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn.
-- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime.
-- @return #SPAWN
-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure - see [Hoggit Wiki](https://wiki.hoggitworld.com/view/DCS_func_addGroup)!
-- @param #string SpawnTemplatePrefix [Mandatory] is the name of the template and the prefix of the GROUP on spawn. The name in the template **will** be overwritten!
-- @param #string SpawnAliasPrefix [Optional] is the prefix that will be given to the GROUP on spawn.
-- @param #boolean NoMooseNamingPostfix [Optional] If true, skip the Moose naming additions (like groupname#001-01) - **but** you need to ensure yourself no duplicate group names exist!
-- @return #SPAWN self
-- @usage
-- -- Create a new SPAWN object based on a Group Template defined from scratch.
-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
-- @usage
--
-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier.
-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" )
--
function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix )
local self = BASE:Inherit( self, BASE:New() )
self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } )
if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then
BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" )
-- -- Spawn a P51 Mustang from scratch
-- local ttemp =
-- {
-- ["modulation"] = 0,
-- ["tasks"] =
-- {
-- }, -- end of ["tasks"]
-- ["task"] = "Reconnaissance",
-- ["uncontrolled"] = false,
-- ["route"] =
-- {
-- ["points"] =
-- {
-- [1] =
-- {
-- ["alt"] = 2000,
-- ["action"] = "Turning Point",
-- ["alt_type"] = "BARO",
-- ["speed"] = 125,
-- ["task"] =
-- {
-- ["id"] = "ComboTask",
-- ["params"] =
-- {
-- ["tasks"] =
-- {
-- }, -- end of ["tasks"]
-- }, -- end of ["params"]
-- }, -- end of ["task"]
-- ["type"] = "Turning Point",
-- ["ETA"] = 0,
-- ["ETA_locked"] = true,
-- ["y"] = 666285.71428571,
-- ["x"] = -312000,
-- ["formation_template"] = "",
-- ["speed_locked"] = true,
-- }, -- end of [1]
-- }, -- end of ["points"]
-- }, -- end of ["route"]
-- ["groupId"] = 1,
-- ["hidden"] = false,
-- ["units"] =
-- {
-- [1] =
-- {
-- ["alt"] = 2000,
-- ["alt_type"] = "BARO",
-- ["livery_id"] = "USAF 364th FS",
-- ["skill"] = "High",
-- ["speed"] = 125,
-- ["type"] = "TF-51D",
-- ["unitId"] = 1,
-- ["psi"] = 0,
-- ["y"] = 666285.71428571,
-- ["x"] = -312000,
-- ["name"] = "P51-1-1",
-- ["payload"] =
-- {
-- ["pylons"] =
-- {
-- }, -- end of ["pylons"]
-- ["fuel"] = 340.68,
-- ["flare"] = 0,
-- ["chaff"] = 0,
-- ["gun"] = 100,
-- }, -- end of ["payload"]
-- ["heading"] = 0,
-- ["callsign"] =
-- {
-- [1] = 1,
-- [2] = 1,
-- ["name"] = "Enfield11",
-- [3] = 1,
-- }, -- end of ["callsign"]
-- ["onboard_num"] = "010",
-- }, -- end of [1]
-- }, -- end of ["units"]
-- ["y"] = 666285.71428571,
-- ["x"] = -312000,
-- ["name"] = "P51",
-- ["communication"] = true,
-- ["start_time"] = 0,
-- ["frequency"] = 124,
-- }
--
--
-- local mustang = SPAWN:NewFromTemplate(ttemp,"P51D")
-- -- you MUST set the next three:
-- mustang:InitCountry(country.id.FRANCE)
-- mustang:InitCategory(Group.Category.AIRPLANE)
-- mustang:InitCoalition(coalition.side.BLUE)
-- mustang:OnSpawnGroup(
-- function(grp)
-- MESSAGE:New("Group Spawned: "..grp:GetName(),15,"SPAWN"):ToAll()
-- end
-- )
-- mustang:Spawn()
--
function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix, NoMooseNamingPostfix )
local self = BASE:Inherit( self, BASE:New() )
self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } )
--if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then
--BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" )
--return nil
--end
if SpawnTemplatePrefix == nil or SpawnTemplatePrefix == "" then
BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnTemplatePrefix is not set" )
return nil
end
if SpawnTemplate then
self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnAliasPrefix = SpawnAliasPrefix
self.SpawnAliasPrefix = SpawnAliasPrefix or SpawnTemplatePrefix
self.SpawnTemplate.name = SpawnTemplatePrefix
self.SpawnIndex = 0
self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
@@ -435,7 +542,11 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr
self.SpawnInitModex = nil
self.SpawnInitAirbase = nil
self.TweakedTemplate = true -- Check if the user is using self made template.
self.MooseNameing = true
if NoMooseNamingPostfix == true then
self.MooseNameing = false
end
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else
error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
@@ -772,10 +883,8 @@ end
-- @usage
--
-- -- NATO helicopters engaging in the battle field.
-- -- The KA-50 has waypoints Start point ( =0 or SP ), 1, 2, 3, 4, End point (= 5 or DP).
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new spawn of the helicopter.
-- -- The randomization of waypoint 2 and 3 will take place within a radius of 2000 meters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeRoute( 2, 2, 2000 )
-- -- UNIT positions of this group will be randomized around the base unit #1 in a circle of 50 to 500 meters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitRandomizeUnits( true, 500, 50 )
--
function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
self:F( { self.SpawnTemplatePrefix, RandomizeUnits, OuterRadius, InnerRadius } )
@@ -791,6 +900,46 @@ function SPAWN:InitRandomizeUnits( RandomizeUnits, OuterRadius, InnerRadius )
return self
end
--- Spawn the UNITs of this group with individual relative positions to unit #1 and individual headings.
-- @param #SPAWN self
-- @param #table Positions Table of positions, needs to one entry per unit in the group(!). The table contains one table each for each unit, with x,y, and optionally z
-- relative positions, and optionally an individual heading.
-- @return #SPAWN
-- @usage
--
-- -- NATO helicopter group of three units engaging in the battle field.
-- local Positions = { [1] = {x = 0, y = 0, heading = 0}, [2] = {x = 50, y = 50, heading = 90}, [3] = {x = -50, y = 50, heading = 180} }
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitRelativePositions(Positions)
--
function SPAWN:InitSetUnitRelativePositions(Positions)
self:F({self.SpawnTemplatePrefix, Positions})
self.SpawnUnitsWithRelativePositions = true
self.UnitsRelativePositions = Positions
return self
end
--- Spawn the UNITs of this group with individual absolute positions and individual headings.
-- @param #SPAWN self
-- @param #table Positions Table of positions, needs to one entry per unit in the group(!). The table contains one table each for each unit, with x,y, and optionally z
-- absolute positions, and optionally an individual heading.
-- @return #SPAWN
-- @usage
--
-- -- NATO helicopter group of three units engaging in the battle field.
-- local Positions = { [1] = {x = 0, y = 0, heading = 0}, [2] = {x = 50, y = 50, heading = 90}, [3] = {x = -50, y = 50, heading = 180} }
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):InitSetUnitAbsolutePositions(Positions)
--
function SPAWN:InitSetUnitAbsolutePositions(Positions)
self:F({self.SpawnTemplatePrefix, Positions})
self.SpawnUnitsWithAbsolutePositions = true
self.UnitsAbsolutePositions = Positions
return self
end
--- This method is rather complicated to understand. But I'll try to explain.
-- This method becomes useful when you need to spawn groups with random templates of groups defined within the mission editor,
-- but they will all follow the same Template route and have the same prefix name.
@@ -813,8 +962,13 @@ end
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplate( Spawn_US_Platoon ):InitRandomizeRoute( 3, 3, 2000 )
function SPAWN:InitRandomizeTemplate( SpawnTemplatePrefixTable )
self:F( { self.SpawnTemplatePrefix, SpawnTemplatePrefixTable } )
self.SpawnTemplatePrefixTable = SpawnTemplatePrefixTable
local temptable = {}
for _,_temp in pairs(SpawnTemplatePrefixTable) do
temptable[#temptable+1] = _temp
end
self.SpawnTemplatePrefixTable = UTILS.ShuffleTable(temptable)
self.SpawnRandomizeTemplate = true
for SpawnGroupID = 1, self.SpawnMaxGroups do
@@ -848,16 +1002,12 @@ end
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):InitLimit( 12, 150 ):SpawnScheduled( 200, 0.4 ):InitRandomizeTemplateSet( Spawn_US_PlatoonSet ):InitRandomizeRoute( 3, 3, 2000 )
--
function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet ) -- R2.3
function SPAWN:InitRandomizeTemplateSet( SpawnTemplateSet )
self:F( { self.SpawnTemplatePrefix } )
self.SpawnTemplatePrefixTable = SpawnTemplateSet:GetSetNames()
self.SpawnRandomizeTemplate = true
for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_RandomizeTemplate( SpawnGroupID )
end
local setnames = SpawnTemplateSet:GetSetNames()
self:InitRandomizeTemplate(setnames)
return self
end
@@ -907,7 +1057,7 @@ end
--- This method provides the functionality to randomize the spawning of the Groups at a given list of zones of different types.
-- @param #SPAWN self
-- @param #table SpawnZoneTable A table with @{Core.Zone} objects. If this table is given, then each spawn will be executed within the given list of @{Core.Zone}s objects.
-- @return #SPAWN
-- @return #SPAWN self
-- @usage
--
-- -- Create a zone table of the 2 zones.
@@ -921,8 +1071,13 @@ end
--
function SPAWN:InitRandomizeZones( SpawnZoneTable )
self:F( { self.SpawnTemplatePrefix, SpawnZoneTable } )
self.SpawnZoneTable = SpawnZoneTable
local temptable = {}
for _,_temp in pairs(SpawnZoneTable) do
temptable[#temptable+1] = _temp
end
self.SpawnZoneTable = UTILS.ShuffleTable(temptable)
self.SpawnRandomizeZones = true
for SpawnGroupID = 1, self.SpawnMaxGroups do
@@ -932,6 +1087,31 @@ function SPAWN:InitRandomizeZones( SpawnZoneTable )
return self
end
--- This method sets a spawn position for the group that is different from the location of the template.
-- @param #SPAWN self
-- @param Core.Point#COORDINATE Coordinate The position to spawn from
-- @return #SPAWN self
function SPAWN:InitPositionCoordinate(Coordinate)
self:T( { self.SpawnTemplatePrefix, Coordinate:GetVec2()} )
self:InitPositionVec2(Coordinate:GetVec2())
return self
end
--- This method sets a spawn position for the group that is different from the location of the template.
-- @param #SPAWN self
-- @param DCS#Vec2 Vec2 The position to spawn from
-- @return #SPAWN self
function SPAWN:InitPositionVec2(Vec2)
self:T( { self.SpawnTemplatePrefix, Vec2} )
self.SpawnInitPosition = Vec2
self.SpawnFromNewPosition = true
self:I("MaxGroups:"..self.SpawnMaxGroups)
for SpawnGroupID = 1, self.SpawnMaxGroups do
self:_SetInitialPosition( SpawnGroupID )
end
return self
end
--- For planes and helicopters, when these groups go home and land on their home airbases and FARPs, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the runtime environment.
-- This method is used to re-spawn automatically (so no extra call is needed anymore) the same group after it has landed.
-- This will enable a spawned group to be re-spawned after it lands, until it is destroyed...
@@ -1140,7 +1320,8 @@ do -- Delay methods
return self
end
--- Turns the Delay On for the @{Wrapper.Group} when spawning.
--- Turns the Delay On for the @{Wrapper.Group} when spawning with @{SpawnScheduled}(). In effect then the 1st group will only be spawned
-- after the number of seconds given in SpawnScheduled as arguments, and not immediately.
-- @param #SPAWN self
-- @return #SPAWN The SPAWN object
function SPAWN:InitDelayOn()
@@ -1230,7 +1411,12 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } )
if self:_GetSpawnIndex( SpawnIndex ) then
if self.SpawnFromNewPosition then
self:_SetInitialPosition( SpawnIndex )
end
if self.SpawnGroups[self.SpawnIndex].Visible then
self.SpawnGroups[self.SpawnIndex].Group:Activate()
else
@@ -1336,7 +1522,38 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
SpawnTemplate.units[UnitID].psi = -SpawnTemplate.units[UnitID].heading
end
end
-- Individual relative unit positions + heading
if self.SpawnUnitsWithRelativePositions and self.UnitsRelativePositions then
local BaseX = SpawnTemplate.units[1].x or 0
local BaseY = SpawnTemplate.units[1].y or 0
local BaseZ = SpawnTemplate.units[1].z or 0
for UnitID = 1, #SpawnTemplate.units do
if self.UnitsRelativePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading = math.rad(self.UnitsRelativePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x = BaseX + (self.UnitsRelativePositions[UnitID].x or 0)
SpawnTemplate.units[UnitID].y = BaseY + (self.UnitsRelativePositions[UnitID].y or 0)
if self.UnitsRelativePositions[UnitID].z then
SpawnTemplate.units[UnitID].z = BaseZ + (self.UnitsRelativePositions[UnitID].z or 0)
end
end
end
-- Individual asbolute unit positions + heading
if self.SpawnUnitsWithAbsolutePositions and self.UnitsAbsolutePositions then
for UnitID = 1, #SpawnTemplate.units do
if self.UnitsAbsolutePositions[UnitID].heading then
SpawnTemplate.units[UnitID].heading = math.rad(self.UnitsAbsolutePositions[UnitID].heading or 0)
end
SpawnTemplate.units[UnitID].x = self.UnitsAbsolutePositions[UnitID].x or 0
SpawnTemplate.units[UnitID].y = self.UnitsAbsolutePositions[UnitID].y or 0
if self.UnitsAbsolutePositions[UnitID].z then
SpawnTemplate.units[UnitID].z = self.UnitsAbsolutePositions[UnitID].z or 0
end
end
end
-- Set livery.
if self.SpawnInitLivery then
for UnitID = 1, #SpawnTemplate.units do
@@ -1437,6 +1654,8 @@ end
-- @param #number SpawnTime The time interval defined in seconds between each new spawn of new groups.
-- @param #number SpawnTimeVariation The variation to be applied on the defined time interval between each new spawn.
-- The variation is a number between 0 and 1, representing the % of variation to be applied on the time interval.
-- @param #boolean WithDelay Do not spawn the **first** group immediately, but delay the spawn as per the calculation below.
-- Effectively the same as @{InitDelayOn}().
-- @return #SPAWN self
-- @usage
-- -- NATO helicopters engaging in the battle field.
@@ -1447,17 +1666,20 @@ end
-- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
-- -- Between these two values, a random amount of seconds will be chosen for each new spawn of the helicopters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):SpawnScheduled( 600, 0.5 )
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation, WithDelay )
self:F( { SpawnTime, SpawnTimeVariation } )
local SpawnTime = SpawnTime or 60
local SpawnTimeVariation = SpawnTimeVariation or 0.5
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
local InitialDelay = 0
if self.DelayOnOff == true then
if WithDelay or self.DelayOnOff == true then
InitialDelay = math.random( SpawnTime - SpawnTime * SpawnTimeVariation, SpawnTime + SpawnTime * SpawnTimeVariation )
end
self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, InitialDelay, SpawnTime, SpawnTimeVariation )
end
return self
end
@@ -2945,7 +3167,11 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix )
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
local SpawnTemplate = nil
if _DATABASE.Templates.Groups[SpawnTemplatePrefix] == nil then
error( 'No Template exists for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
end
local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template
self:F( { Template = Template } )
@@ -2979,6 +3205,11 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
if self.TweakedTemplate ~= nil and self.TweakedTemplate == true then
BASE:I( "WARNING: You are using a tweaked template." )
SpawnTemplate = self.SpawnTemplate
if self.MooseNameing == true then
SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
else
SpawnTemplate.name = self:SpawnGroupName()
end
else
SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
@@ -3032,6 +3263,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
if type( Callsign ) ~= "number" then -- blue callsign
Callsign[2] = ((SpawnIndex - 1) % 10) + 1
local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string
CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers
local CallsignLen = CallsignName:len()
SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3]
else
@@ -3108,6 +3340,57 @@ function SPAWN:_RandomizeTemplate( SpawnIndex )
return self
end
--- Private method that sets the DCS#Vec2 where the Group will be spawned.
-- @param #SPAWN self
-- @param #number SpawnIndex
-- @return #SPAWN self
function SPAWN:_SetInitialPosition( SpawnIndex )
self:T( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnRandomizeZones } )
if self.SpawnFromNewPosition then
self:T( "Preparing Spawn at Vec2 ", self.SpawnInitPosition )
local SpawnVec2 = self.SpawnInitPosition
self:T( { SpawnVec2 = SpawnVec2 } )
local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate
SpawnTemplate.route = SpawnTemplate.route or {}
SpawnTemplate.route.points = SpawnTemplate.route.points or {}
SpawnTemplate.route.points[1] = SpawnTemplate.route.points[1] or {}
SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0
self:T( { Route = SpawnTemplate.route } )
for UnitID = 1, #SpawnTemplate.units do
local UnitTemplate = SpawnTemplate.units[UnitID]
self:T( 'Before Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y )
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
local TX = SpawnVec2.x + (SX - BX)
local TY = SpawnVec2.y + (SY - BY)
UnitTemplate.x = TX
UnitTemplate.y = TY
-- TODO: Manage altitude based on landheight...
-- SpawnTemplate.units[UnitID].alt = SpawnVec2:
self:T( 'After Translation SpawnTemplate.units[' .. UnitID .. '].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units[' .. UnitID .. '].y = ' .. UnitTemplate.y )
end
SpawnTemplate.route.points[1].x = SpawnVec2.x
SpawnTemplate.route.points[1].y = SpawnVec2.y
SpawnTemplate.x = SpawnVec2.x
SpawnTemplate.y = SpawnVec2.y
end
return self
end
--- Private method that randomizes the @{Core.Zone}s where the Group will be spawned.
-- @param #SPAWN self
-- @param #number SpawnIndex
@@ -3227,7 +3510,7 @@ end
-- TODO Need to delete this... _DATABASE does this now ...
--- @param #SPAWN self
-- @param #SPAWN self
-- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnBirth( EventData )
self:F( self.SpawnTemplatePrefix )
@@ -3247,7 +3530,7 @@ function SPAWN:_OnBirth( EventData )
end
--- @param #SPAWN self
-- @param #SPAWN self
-- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnDeadOrCrash( EventData )
self:F( self.SpawnTemplatePrefix )

View File

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

View File

@@ -5,7 +5,7 @@
-- SPOT implements the DCS Spot class functionality, but adds additional luxury to be able to:
--
-- * Spot for a defined duration.
-- * Updates of laer spot position every 0.2 seconds for moving targets.
-- * Updates of laser spot position every 0.2 seconds for moving targets.
-- * Wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff.
@@ -13,18 +13,8 @@
-- ===
--
-- # Demo Missions
--
-- ### [SPOT Demo Missions source code]()
--
-- ### [SPOT Demo Missions, only for beta testers]()
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
--
-- # YouTube Channel
--
-- ### [SPOT YouTube Channel]()
-- ### [Demo Missions on GitHub](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
--
-- ===
--
@@ -43,21 +33,22 @@
do
--- @type SPOT
---
-- @type SPOT
-- @extends Core.Fsm#FSM
--- Implements the target spotting or marking functionality, but adds additional luxury to be able to:
--
-- * Mark targets for a defined duration.
-- * Updates of laer spot position every 0.2 seconds for moving targets.
-- * Updates of laser spot position every 0.25 seconds for moving targets.
-- * Wiggle the spot at the target.
-- * Provide a @{Wrapper.Unit} as a target, instead of a point.
-- * Implement a status machine, LaseOn, LaseOff.
--
-- ## 1. SPOT constructor
--
-- * @{#SPOT.New}(..\Presentations\SPOT\Dia2.JPG): Creates a new SPOT object.
-- * @{#SPOT.New}(): Creates a new SPOT object.
--
-- ## 2. SPOT is a FSM
--
@@ -217,6 +208,8 @@ do
self.Recce = Recce
self.RecceName = self.Recce:GetName()
self.LaseScheduler = SCHEDULER:New( self )
@@ -236,13 +229,17 @@ do
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
function SPOT:onafterLaseOn( From, Event, To, Target, LaserCode, Duration )
self:F( { "LaseOn", Target, LaserCode, Duration } )
self:T({From, Event, To})
self:T2( { "LaseOn", Target, LaserCode, Duration } )
local function StopLase( self )
self:LaseOff()
end
self.Target = Target
self.TargetName = Target:GetName()
self.LaserCode = LaserCode
self.Lasing = true
@@ -261,6 +258,8 @@ do
self:HandleEvent( EVENTS.Dead )
self:__Lasing( -1 )
return self
end
@@ -273,7 +272,7 @@ do
-- @param #number LaserCode Laser code.
-- @param #number Duration Duration of lasing in seconds.
function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration)
self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
self:T2( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } )
local function StopLase( self )
self:LaseOff()
@@ -295,55 +294,72 @@ do
end
self:__Lasing(-1)
return self
end
--- @param #SPOT self
---
-- @param #SPOT self
-- @param Core.Event#EVENTDATA EventData
function SPOT:OnEventDead(EventData)
self:F( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
self:T2( { Dead = EventData.IniDCSUnitName, Target = self.Target } )
if self.Target then
if EventData.IniDCSUnitName == self.Target:GetName() then
self:F( {"Target dead ", self.Target:GetName() } )
if EventData.IniDCSUnitName == self.TargetName then
self:F( {"Target dead ", self.TargetName } )
self:Destroyed()
self:LaseOff()
end
end
if self.Recce then
if EventData.IniDCSUnitName == self.RecceName then
self:F( {"Recce dead ", self.RecceName } )
self:LaseOff()
end
end
return self
end
--- @param #SPOT self
---
-- @param #SPOT self
-- @param From
-- @param Event
-- @param To
function SPOT:onafterLasing( From, Event, To )
if self.Target and self.Target:IsAlive() then
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
self:__Lasing( -0.2 )
elseif self.TargetCoord then
self:T({From, Event, To})
-- Wiggle the IR spot a bit.
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
if self.Lasing then
if self.Target and self.Target:IsAlive() then
self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() )
self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() )
self:__Lasing(0.2)
elseif self.TargetCoord then
self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3)
self:__Lasing(-0.25)
else
self:F( { "Target is not alive", self.Target:IsAlive() } )
-- Wiggle the IR spot a bit.
local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3
local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3
self.SpotIR:setPoint(irvec3)
self.SpotLaser:setPoint(lsvec3)
self:__Lasing(0.2)
else
self:F( { "Target is not alive", self.Target:IsAlive() } )
end
end
return self
end
--- @param #SPOT self
---
-- @param #SPOT self
-- @param From
-- @param Event
-- @param To
-- @return #SPOT
function SPOT:onafterLaseOff( From, Event, To )
self:F( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self:T({From, Event, To})
self:T2( {"Stopped lasing for ", self.Target and self.Target:GetName() or "coord", SpotIR = self.SportIR, SpotLaser = self.SpotLaser } )
self.Lasing = false

View File

@@ -107,7 +107,7 @@ _TIMERID=0
--- TIMER class version.
-- @field #string version
TIMER.version="0.1.2"
TIMER.version="0.2.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -155,7 +155,7 @@ function TIMER:New(Function, ...)
return self
end
--- Create a new TIMER object.
--- Start TIMER object.
-- @param #TIMER self
-- @param #number Tstart Relative start time in seconds.
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
@@ -192,6 +192,20 @@ function TIMER:Start(Tstart, dT, Duration)
return self
end
--- Start TIMER object if a condition is met. Useful for e.g. debugging.
-- @param #TIMER self
-- @param #boolean Condition Must be true for the TIMER to start
-- @param #number Tstart Relative start time in seconds.
-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once.
-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function.
-- @return #TIMER self
function TIMER:StartIf(Condition,Tstart, dT, Duration)
if Condition then
self:Start(Tstart, dT, Duration)
end
return self
end
--- Stop the timer by removing the timer function.
-- @param #TIMER self
-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped.
@@ -208,7 +222,20 @@ function TIMER:Stop(Delay)
-- Remove timer function.
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
timer.removeFunction(self.tid)
-- We use a pcall here because if the DCS timer does not exist any more, it crashes the whole script!
local status=pcall(
function ()
timer.removeFunction(self.tid)
end
)
-- Debug messages.
if status then
self:T2(self.lid..string.format("Stopped timer!"))
else
self:E(self.lid..string.format("WARNING: Could not remove timer function! isrunning=%s", tostring(self.isrunning)))
end
-- Not running any more.
self.isrunning=false

View File

@@ -555,7 +555,7 @@ function ZONE_BASE:GetZoneMaybe()
end
end
-- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
--- 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.
@@ -569,7 +569,7 @@ function ZONE_BASE:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
-- Returns the zone Properties table.
--- 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()
@@ -927,7 +927,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius()
self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
--self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()})
local SphereSearch = {
id = world.VolumeType.SPHERE,
@@ -1001,6 +1001,24 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
end
--- Remove junk inside the zone using the `world.removeJunk` function.
-- @param #ZONE_RADIUS self
-- @return #number Number of deleted objects.
function ZONE_RADIUS:RemoveJunk()
local radius=self.Radius
local vec3=self:GetVec3()
local volS = {
id = world.VolumeType.SPHERE,
params = {point = vec3, radius = radius}
}
local n=world.removeJunk(volS)
return n
end
--- Count the number of different coalitions inside the zone.
-- @param #ZONE_RADIUS self
-- @return #table Table of DCS units and DCS statics inside the zone.
@@ -1387,7 +1405,7 @@ end
-- @param #ZONE_RADIUS self
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 100 times to find the right type!
-- @return Core.Point#COORDINATE The random coordinate.
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
@@ -2084,6 +2102,92 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
return self
end
--- Get the smallest radius encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @return #number Radius of the zone in meters.
function ZONE_POLYGON_BASE:GetRadius()
local center=self:GetVec2()
local radius=0
for _,_vec2 in pairs(self._.Polygon) do
local vec2=_vec2 --DCS#Vec2
local r=UTILS.VecDist2D(center, vec2)
if r>radius then
radius=r
end
end
return radius
end
--- Get the smallest circular zone encompassing all points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_RADIUS The circular zone.
function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone)
local center=self:GetVec2()
local radius=self:GetRadius()
local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone)
return zone
end
--- Get the smallest rectangular zone encompassing all points points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone.
-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered.
-- @return #ZONE_POLYGON The rectangular zone.
function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone)
local vec1, vec3=self:GetBoundingVec2()
local vec2={x=vec1.x, y=vec3.y}
local vec4={x=vec3.x, y=vec1.y}
local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4})
return zone
end
--- Remove junk inside the zone. Due to DCS limitations, this works only for rectangular zones. So we get the smallest rectangular zone encompassing all points points of the polygon zone.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Height Height of the box in meters. Default 1000.
-- @return #number Number of removed objects.
function ZONE_POLYGON_BASE:RemoveJunk(Height)
Height=Height or 1000
local vec2SW, vec2NE=self:GetBoundingVec2()
local vec3SW={x=vec2SW.x, y=-Height, z=vec2SW.y} --DCS#Vec3
local vec3NE={x=vec2NE.x, y= Height, z=vec2NE.y} --DCS#Vec3
--local coord1=COORDINATE:NewFromVec3(vec3SW):MarkToAll("SW")
--local coord1=COORDINATE:NewFromVec3(vec3NE):MarkToAll("NE")
local volume = {
id = world.VolumeType.BOX,
params = {
min=vec3SW,
max=vec3SW
}
}
local n=world.removeJunk(volume)
return n
end
--- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color.
@@ -2286,6 +2390,32 @@ function ZONE_POLYGON_BASE:GetBoundingSquare()
return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
end
--- Get the bounding 2D vectors of the polygon.
-- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 Coordinates of western-southern-lower vertex of the box.
-- @return DCS#Vec2 Coordinates of eastern-northern-upper vertex of the box.
function ZONE_POLYGON_BASE:GetBoundingVec2()
local x1 = self._.Polygon[1].x
local y1 = self._.Polygon[1].y
local x2 = self._.Polygon[1].x
local y2 = self._.Polygon[1].y
for i = 2, #self._.Polygon do
self:T2( { self._.Polygon[i], x1, y1, x2, y2 } )
x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1
x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2
y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1
y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2
end
local vec1={x=x1, y=y1}
local vec2={x=x2, y=y2}
return vec1, vec2
end
--- Draw a frontier on the F10 map with small filled circles.
-- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.
@@ -2841,7 +2971,7 @@ do -- ZONE_ELASTIC
--- 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.
-- @param Core.Set#SET_GROUP GroupSet Set of groups.
-- @return #ZONE_ELASTIC self
function ZONE_ELASTIC:AddSetGroup(GroupSet)

View File

@@ -193,21 +193,29 @@ do -- land
--- [Type of surface enumerator](https://wiki.hoggitworld.com/view/DCS_singleton_land)
-- @type land.SurfaceType
-- @field LAND
-- @field SHALLOW_WATER
-- @field WATER
-- @field ROAD
-- @field RUNWAY
-- @field LAND Land=1
-- @field SHALLOW_WATER Shallow water=2
-- @field WATER Water=3
-- @field ROAD Road=4
-- @field RUNWAY Runway=5
--- Returns altitude MSL of the point.
--- Returns the distance from sea level (y-axis) of a given vec2 point.
-- @function [parent=#land] getHeight
-- @param #Vec2 point point on the ground.
-- @return #Distance
-- @param #Vec2 point Point on the ground.
-- @return #number Height in meters.
--- Returns the surface height and depth of a point. Useful for checking if the path is deep enough to support a given ship.
-- Both values are positive. When checked over water at sea level the first value is always zero.
-- When checked over water at altitude, for example the reservoir of the Inguri Dam, the first value is the corresponding altitude the water level is at.
-- @function [parent=#land] getSurfaceHeightWithSeabed
-- @param #Vec2 point Position where to check.
-- @return #number Height in meters.
-- @return #number Depth in meters.
--- returns surface type at the given point.
--- Returns surface type at the given point.
-- @function [parent=#land] getSurfaceType
-- @param #Vec2 point Point on the land.
-- @return #land.SurfaceType
-- @return #number Enumerator value from `land.SurfaceType` (LAND=1, SHALLOW_WATER=2, WATER=3, ROAD=4, RUNWAY=5)
--- [DCS Singleton land](https://wiki.hoggitworld.com/view/DCS_singleton_land)
land = {} --#land
@@ -308,6 +316,11 @@ do -- country
-- @field Argentinia
-- @field Cyprus
-- @field Slovenia
-- @field BOLIVIA
-- @field GHANA
-- @field NIGERIA
-- @field PERU
-- @field ECUADOR
country = {} --#country
@@ -336,9 +349,23 @@ do -- coalition
-- @field RED
-- @field BLUE
--- @function [parent=#coalition] getCountryCoalition
-- @param #number countryId
-- @return #number coalitionId
--- Get country coalition.
-- @function [parent=#coalition] getCountryCoalition
-- @param #number countryId Country ID.
-- @return #number coalitionId Coalition ID.
--- Dynamically spawns a group. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
-- @function [parent=#coalition] addGroup
-- @param #number countryId Id of the country.
-- @param #number groupCategory Group category. Set -1 for spawning FARPS.
-- @param #table groupData Group data table.
-- @return DCS#Group The spawned Group object.
--- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup)
-- @function [parent=#coalition] addStaticObject
-- @param #number countryId Id of the country.
-- @param #table groupData Group data table.
-- @return DCS#Static The spawned static object.
coalition = {} -- #coalition
@@ -408,8 +435,8 @@ do -- Types
--- Vec3 type is a 3D-vector.
-- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain.
-- @type Vec3
-- @field #Distance x is directed to the north
-- @field #Distance z is directed to the east
-- @field #Distance x is directed to the North
-- @field #Distance z is directed to the East
-- @field #Distance y is directed up
--- Vec2 is a 2D-vector for the ground plane as a reference plane.
@@ -660,10 +687,11 @@ do -- Weapon
--- Weapon.Category enum that stores weapon categories.
-- @type Weapon.Category
-- @field SHELL
-- @field MISSILE
-- @field ROCKET
-- @field BOMB
-- @field #number SHELL Shell.
-- @field #number MISSILE Missile
-- @field #number ROCKET Rocket.
-- @field #number BOMB Bomb.
-- @field #number TORPEDO Torpedo.
--- Weapon.GuidanceType enum that stores guidance methods. Available only for guided weapon (Weapon.Category.MISSILE and some Weapon.Category.BOMB).
@@ -982,8 +1010,8 @@ do -- Unit
--- Enum that stores aircraft refueling system types.
-- @type Unit.RefuelingSystem
-- @field BOOM_AND_RECEPTACLE
-- @field PROBE_AND_DROGUE
-- @field BOOM_AND_RECEPTACLE Tanker with a boom.
-- @field PROBE_AND_DROGUE Tanker with a probe.
--- Enum that stores sensor types.
-- @type Unit.SensorType
@@ -1289,6 +1317,42 @@ do -- Group
end -- Group
do -- StaticObject
--- Represents a static object.
-- @type StaticObject
-- @extends DCS#Object
--- Returns the static object.
-- @function [parent=#StaticObject] getByName
-- @param #string name Name of the static object.
-- @return #StaticObject
StaticObject = {} --#StaticObject
end
do --Event
--- Event structure. Note that present fields depend on type of event.
-- @type Event
-- @field #number id Event ID.
-- @field #number time Mission time in seconds.
-- @field DCS#Unit initiator Unit initiating the event.
-- @field DCS#Unit target Target unit.
-- @field DCS#Airbase place Airbase.
-- @field number subPlace Subplace. Unknown and often just 0.
-- @field #string weapon_name Weapoin name.
-- @field #number idx Mark ID.
-- @field #number coalition Coalition ID.
-- @field #number groupID Group ID, *e.g.* of group that added mark point.
-- @field #string text Text, *e.g.* of mark point.
-- @field DCS#Vec3 pos Position vector, *e.g.* of mark point.
-- @field #string comment Comment, *e.g.* LSO score.
Event={} --#Event
end
do -- AI

View File

@@ -103,6 +103,7 @@
-- @field #number coalition The coalition of the arty group.
-- @field #boolean respawnafterdeath Respawn arty group after all units are dead.
-- @field #number respawndelay Respawn delay in seconds.
-- @field #number dtTrack Time interval in seconds for weapon tracking.
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can
@@ -693,7 +694,7 @@ ARTY.db={
--- Arty script version.
-- @field #string version
ARTY.version="1.2.0"
ARTY.version="1.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -801,6 +802,9 @@ function ARTY:New(group, alias)
else
self.ismobile=false
end
-- Set track time interval.
self.dtTrack=0.2
-- Set speed to 0.7 of maximum.
self.Speed=self.SpeedMax * 0.7
@@ -1497,6 +1501,15 @@ function ARTY:SetStatusInterval(interval)
return self
end
--- Set time interval for weapon tracking.
-- @param #ARTY self
-- @param #number interval Time interval in seconds. Default 0.2 seconds.
-- @return self
function ARTY:SetTrackInterval(interval)
self.dtTrack=interval or 0.2
return self
end
--- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed.
-- @param #ARTY self
-- @param #number waittime Time in seconds. Default 300 seconds.
@@ -2129,6 +2142,95 @@ end
-- Event Handling
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called during tracking of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncTrack(weapon, self, target)
-- Coordinate and distance to target.
local _coord=weapon.coordinate
local _dist=_coord:Get2DDistance(target.coord)
local _destroyweapon=false
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=target.coord:GetRandomCoordinateInRadius(target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
weapon:Destroy()
-- No more tracking.
weapon.tracking=false
end
end
--- Function called after impact of weapon.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #ARTY self ARTY object.
-- @param #ARTY.Target target Target of the weapon.
function ARTY._FuncImpact(weapon, self, target)
-- Debug info.
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Get impact coordinate.
local _impactcoord=weapon:GetImpactCoordinate()
-- Create a "nuclear" explosion and blast at the impact point.
if target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
--SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
self:ScheduleOnce(1.0, ARTY._NuclearBlast, self, _impactcoord)
end
end
--- Eventhandler for shot event.
-- @param #ARTY self
-- @param Core.Event#EVENTDATA EventData
@@ -2162,128 +2264,32 @@ function ARTY:OnEventShot(EventData)
self:T(self.lid..text)
MESSAGE:New(text, 5):Clear():ToAllIf(self.report or self.Debug)
-- Last known position of the weapon fired.
local _lastpos={x=0, y=0, z=0}
--- Track the position of the weapon if it is supposed to model a tac nuke, illumination or smoke shell.
-- @param #table _weapon
local function _TrackWeapon(_data)
-- When the pcall status returns false the weapon has hit.
local _weaponalive,_currpos = pcall(
function()
return _data.weapon:getPoint()
end)
-- Debug
self:T3(self.lid..string.format("ARTY %s: Weapon still in air: %s", self.groupname, tostring(_weaponalive)))
-- Destroy weapon before impact.
local _destroyweapon=false
if _weaponalive then
-- Update last position.
_lastpos={x=_currpos.x, y=_currpos.y, z=_currpos.z}
-- Coordinate and distance to target.
local _coord=COORDINATE:NewFromVec3(_lastpos)
local _dist=_coord:Get2DDistance(_data.target.coord)
-- Debug
self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist))
if _data.target.weapontype==ARTY.WeaponType.IlluminationShells then
-- Check if within distace.
if _dist<_data.target.radius then
-- Get random coordinate within certain radius of the target.
local _cr=_data.target.coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Get random altitude over target.
local _alt=_cr:GetLandHeight()+math.random(self.illuMinalt, self.illuMaxalt)
-- Adjust explosion height of coordinate.
local _ci=COORDINATE:New(_cr.x,_alt,_cr.z)
-- Create illumination flare.
_ci:IlluminationBomb(self.illuPower)
-- Destroy actual shell.
_destroyweapon=true
end
elseif _data.target.weapontype==ARTY.WeaponType.SmokeShells then
if _dist<_data.target.radius then
-- Get random coordinate within a certain radius.
local _cr=_coord:GetRandomCoordinateInRadius(_data.target.radius)
-- Fire smoke at this coordinate.
_cr:Smoke(self.smokeColor)
-- Destroy actual shell.
_destroyweapon=true
end
end
if _destroyweapon then
self:T2(self.lid..string.format("ARTY %s destroying shell, stopping timer.", self.groupname))
-- Destroy weapon and stop timer.
_data.weapon:destroy()
return nil
else
-- TODO: Make dt input parameter.
local dt=0.02
self:T3(self.lid..string.format("ARTY %s tracking weapon again in %.3f seconds", self.groupname, dt))
-- Check again in 0.05 seconds.
return timer.getTime() + dt
end
else
-- Get impact coordinate.
local _impactcoord=COORDINATE:NewFromVec3(_lastpos)
self:I(self.lid..string.format("ARTY %s weapon NOT ALIVE any more.", self.groupname))
-- Create a "nuclear" explosion and blast at the impact point.
if _data.target.weapontype==ARTY.WeaponType.TacticalNukes then
self:T(self.lid..string.format("ARTY %s triggering nuclear explosion in one second.", self.groupname))
SCHEDULER:New(nil, ARTY._NuclearBlast, {self,_impactcoord}, 1.0)
end
-- Stop timer.
return nil
end
end
-- Start track the shell if we want to model a tactical nuke.
local _tracknuke = self.currentTarget.weapontype==ARTY.WeaponType.TacticalNukes and self.Nukes>0
local _trackillu = self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0
local _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0
if _tracknuke or _trackillu or _tracksmoke then
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
local _peter={}
_peter.weapon=EventData.weapon
_peter.target=UTILS.DeepCopy(self.currentTarget)
timer.scheduleFunction(_TrackWeapon, _peter, timer.getTime() + 2.0)
-- Debug info.
self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname))
-- Create a weapon object.
local weapon=WEAPON:New(EventData.weapon)
-- Set time step for tracking.
weapon:SetTimeStepTrack(self.dtTrack)
-- Copy target. We need a copy because it might already be overwritten with the next target during flight of weapon.
local target=UTILS.DeepCopy(self.currentTarget)
-- Set callback functions.
weapon:SetFuncTrack(ARTY._FuncTrack, self, target)
weapon:SetFuncImpact(ARTY._FuncImpact, self, target)
-- Start tracking in 2 sec (arty ammo should fly a bit).
weapon:StartTrack(2)
end
-- Get current ammo.
@@ -3931,9 +3937,10 @@ function ARTY:GetAmmo(display)
return nammo, nshells, nrockets, nmissiles
end
for _,unit in pairs(units) do
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
if unit then
-- Output.
local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName())

View File

@@ -474,7 +474,7 @@ do -- DETECTION_BASE
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param #table DetectedItem The DetectedItem.
-- @param #DetectedItem DetectedItem The DetectedItem data structure.
self:AddTransition( "*", "Stop", "Stopped" )
@@ -2354,6 +2354,7 @@ do -- DETECTION_TYPES
if not DetectedItem then
DetectedItem = self:AddDetectedItem( "TYPE", DetectedTypeName )
DetectedItem.TypeName = DetectedTypeName
DetectedItem.Name = DetectedUnitName -- fix by @Nocke
end
DetectedItem.Set:AddUnit( DetectedUnit )
@@ -2478,14 +2479,14 @@ do -- DETECTION_AREAS
--- DETECTION_AREAS constructor.
-- @param #DETECTION_AREAS self
-- @param Core.Set#SET_GROUP DetectionSetGroup The @{Core.Set} of GROUPs in the Forward Air Controller role.
-- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target.
-- @param #number DetectionZoneRange The range in meters within which targets are grouped upon the first detected target. Default 5000m.
-- @return #DETECTION_AREAS
function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange )
-- Inherits from DETECTION_BASE
local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) )
self.DetectionZoneRange = DetectionZoneRange
self.DetectionZoneRange = DetectionZoneRange or 5000
self._SmokeDetectedUnits = false
self._FlareDetectedUnits = false
@@ -2988,4 +2989,3 @@ do -- DETECTION_AREAS
end
end

View File

@@ -26,6 +26,7 @@
--- FOX class.
-- @type FOX
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table menuadded Table of groups the menu was added for.
@@ -124,6 +125,7 @@
-- @field #FOX
FOX = {
ClassName = "FOX",
verbose = 0,
Debug = false,
lid = nil,
menuadded = {},
@@ -168,7 +170,7 @@ FOX = {
--- Missile data table.
-- @type FOX.MissileData
-- @field Wrapper.Unit#UNIT weapon Missile weapon unit.
-- @field DCS#Weapon weapon Missile weapon object.
-- @field #boolean active If true the missile is active.
-- @field #string missileType Type of missile.
-- @field #string missileName Name of missile.
@@ -185,6 +187,8 @@ FOX = {
-- @field #string targetName Name of the target unit or "unknown".
-- @field #string targetOrig Name of the "original" target, i.e. the one right after launched.
-- @field #FOX.PlayerData targetPlayer Player that was targeted or nil.
-- @field Core.Point#COORDINATE missileCoord Missile coordinate during tracking.
-- @field Wrapper.Weapon#WEAPON Weapon Weapon object.
--- Main radio menu on group level.
-- @field #table MenuF10 Root menu table on group level.
@@ -196,7 +200,7 @@ FOX.MenuF10Root=nil
--- FOX class version.
-- @field #string version
FOX.version="0.6.1"
FOX.version="0.8.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -500,6 +504,7 @@ function FOX:SetDisableF10Menu()
return self
end
--- Enable F10 menu for all players.
-- @param #FOX self
-- @return #FOX self
@@ -510,6 +515,15 @@ function FOX:SetEnableF10Menu()
return self
end
--- Set verbosity level.
-- @param #FOX self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #FOX self
function FOX:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
--- Set default player setting for missile destruction.
-- @param #FOX self
-- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed.
@@ -605,7 +619,9 @@ function FOX:onafterStatus(From, Event, To)
local clock=UTILS.SecondsToClock(time)
-- Status.
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
if self.verbose>=1 then
self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate))
end
-- Check missile status.
self:_CheckMissileStatus()
@@ -713,7 +729,9 @@ function FOX:_CheckMissileStatus()
if #self.missiles==0 then
text=text.." none"
end
self:I(self.lid..text)
if self.verbose>=2 then
self:I(self.lid..text)
end
-- Remove inactive missiles.
for i=#self.missiles,1,-1 do
@@ -743,7 +761,7 @@ function FOX:_IsProtected(targetunit)
if targetgroup then
local targetname=targetgroup:GetName()
for _,_group in pairs(self.protectedset:GetSetObjects()) do
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
if group then
@@ -762,6 +780,277 @@ function FOX:_IsProtected(targetunit)
return false
end
--- Function called from weapon tracking.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #FOX self FOX object.
-- @param #FOX.MissileData missile Fired missile
function FOX._FuncTrack(weapon, self, missile)
-- Missile coordinate.
local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos)
-- Missile velocity in m/s.
local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary.
self:GetMissileTarget(missile)
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
if missile.targetUnit then
-----------------------------------
-- Missile has a specific target --
-----------------------------------
if missile.targetPlayer then
-- Target is a player.
if missile.targetPlayer.destroy==true then
target=missile.targetUnit
end
else
-- Check if unit is protected.
if self:_IsProtected(missile.targetUnit) then
target=missile.targetUnit
end
end
else
------------------------------------
-- Missile has NO specific target --
------------------------------------
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
-- That would trigger the distance check right after missile launch if things to wrong.
--
-- Possible solutions:
-- * Time check: enable this check after X seconds after missile was fired. What is X?
-- * Coalition check. But would not work in training situations where blue on blue is valid!
-- * At least enable it for surface-to-air missiles.
local function _GetTarget(_unit)
local unit=_unit --Wrapper.Unit#UNIT
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if dist<=self.explosiondist then
return unit
end
end
-- Distance to closest player.
local mindist=nil
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Check that player was not the one who launched the missile.
if player.unitname~=missile.shooterName then
-- Player position.
local playerCoord=player.unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=player.unit
end
end
end
if self.protectedset then
-- Distance to closest protected unit.
mindist=nil
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
for _,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Check that player was not the one who launched the missile.
if unit:GetName()~=missile.shooterName then
-- Player position.
local playerVec3=unit:GetVec3()
-- Distance.
local dist=missileCoord:Get3DDistance(playerVec3)
-- Distance from shooter to player.
local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
end
end
-- Check if missile has a valid target.
if target then
-- Target coordinate.
local targetVec3=target:GetVec3() --target:GetCoordinate()
-- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetVec3)
-- Distance missile to shooter.
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetVec3())
end
-- Debug output.
if self.Debug then
local bearing=missileCoord:HeadingTo(targetVec3)
local eta=distance/missileVelocity
-- Debug distance check.
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta))
end
-- Distroy missile if it's getting too close.
local destroymissile=distance<=self.explosiondist
-- Check BIG missiles.
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
-- If missile is 150 m from target ==> destroy missile if in safe zone.
if destroymissile and self:_CheckCoordSafe(targetVec3) then
-- Destroy missile.
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance))
weapon:Destroy()
-- Missile is not active any more.
missile.active=false
-- Debug smoke.
if self.Debug then
missileCoord:SmokeRed()
end
-- Create event.
self:MissileDestroyed(missile)
-- Little explosion for the visual effect.
if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
missileCoord:Explosion(self.explosionpower)
end
-- Target was a player.
if missile.targetPlayer then
-- Message to target.
local text=string.format("Destroying missile. %s", self:_DeadText())
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
-- Increase dead counter.
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
-- We could disable the tracking here but then the impact function would not be called.
--weapon.tracking=false
else
-- Time step.
local dt=1.0
if distance>50000 then
-- > 50 km
dt=self.dt50 --=5.0
elseif distance>10000 then
-- 10-50 km
dt=self.dt10 --=1.0
elseif distance>5000 then
-- 5-10 km
dt=self.dt05 --0.5
elseif distance>1000 then
-- 1-5 km
dt=self.dt01 --0.1
else
-- < 1 km
dt=self.dt00 --0.01
end
-- Set time step.
weapon:SetTimeStepTrack(dt)
end
else
-- No current target.
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName))
weapon:SetTimeStepTrack(0.1)
end
end
--- Callback function on impact or destroy otherwise.
-- @param Wrapper.Weapon#WEAPON weapon Weapon object.
-- @param #FOX self FOX object.
-- @param #FOX.MissileData missile Fired missile.
function FOX._FuncImpact(weapon, self, missile)
if missile.targetPlayer then
-- Get human player.
local player=missile.targetPlayer
-- Check for player and distance < 10 km.
if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then
local text=string.format("Missile defeated. Well done, %s!", player.name)
MESSAGE:New(text, 10):ToClient(player.client)
-- Increase defeated counter.
player.defeated=player.defeated+1
end
end
-- Missile is not active any more.
missile.active=false
--Terminate the timer.
self:T(FOX.lid..string.format("Terminating missile track timer."))
weapon.tracking=false
end
--- Missle launch event.
-- @param #FOX self
-- @param #string From From state.
@@ -818,304 +1107,19 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
end
end
end
end
-- Init missile position.
local _lastBombPos = {x=0,y=0,z=0}
-- Set callback function for tracking.
missile.Weapon:SetFuncTrack(FOX._FuncTrack, self, missile)
-- Missile coordinate.
local missileCoord = nil --Core.Point#COORDINATE
-- Set callback function for impact.
missile.Weapon:SetFuncImpact(FOX._FuncImpact, self, missile)
-- Target unit of the missile.
local target=nil --Wrapper.Unit#UNIT
--- Function monitoring the position of a bomb until impact.
local function trackMissile(_ordnance)
-- When the pcall returns a failure the weapon has hit.
local _status,_bombPos = pcall(
function()
return _ordnance:getPoint()
end)
-- Check if status is not nil. If so, we have a valid point.
if _status then
----------------------------------------------
-- Still in the air. Remember this position --
----------------------------------------------
-- Missile position.
_lastBombPos = {x=_bombPos.x, y=_bombPos.y, z=_bombPos.z}
-- Missile coordinate.
missileCoord=COORDINATE:NewFromVec3(_lastBombPos)
-- Missile velocity in m/s.
local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity())
-- Update missile target if necessary.
self:GetMissileTarget(missile)
if missile.targetUnit then
-----------------------------------
-- Missile has a specific target --
-----------------------------------
if missile.targetPlayer then
-- Target is a player.
if missile.targetPlayer.destroy==true then
target=missile.targetUnit
end
else
-- Check if unit is protected.
if self:_IsProtected(missile.targetUnit) then
target=missile.targetUnit
end
end
else
------------------------------------
-- Missile has NO specific target --
------------------------------------
-- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not.
-- That would trigger the distance check right after missile launch if things to wrong.
--
-- Possible solutions:
-- * Time check: enable this check after X seconds after missile was fired. What is X?
-- * Coalition check. But would not work in training situations where blue on blue is valid!
-- * At least enable it for surface-to-air missiles.
local function _GetTarget(_unit)
local unit=_unit --Wrapper.Unit#UNIT
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if dist<=self.explosiondist then
return unit
end
end
-- Distance to closest player.
local mindist=nil
-- Loop over players.
for _,_player in pairs(self.players) do
local player=_player --#FOX.PlayerData
-- Check that player was not the one who launched the missile.
if player.unitname~=missile.shooterName then
-- Player position.
local playerCoord=player.unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=player.unit
end
end
end
if self.protectedset then
-- Distance to closest protected unit.
mindist=nil
for _,_group in pairs(self.protectedset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
for _,_unit in pairs(group:GetUnits()) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Check that player was not the one who launched the missile.
if unit:GetName()~=missile.shooterName then
-- Player position.
local playerCoord=unit:GetCoordinate()
-- Distance.
local dist=missileCoord:Get3DDistance(playerCoord)
-- Distance from shooter to player.
local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord)
-- Update mindist if necessary. Only include players in range of missile + 50% safety margin.
if (mindist==nil or dist<mindist) and (Dshooter2player<=missile.missileRange*1.5 or dist<=self.explosiondist) then
mindist=dist
target=unit
end
end
end
end
end
end
if target then
self:T(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist)))
end
end
-- Check if missile has a valid target.
if target then
-- Target coordinate.
local targetCoord=target:GetCoordinate()
-- Distance from missile to target.
local distance=missileCoord:Get3DDistance(targetCoord)
-- Distance missile to shooter.
local distShooter=nil
if missile.shooterUnit and missile.shooterUnit:IsAlive() then
distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate())
end
-- Debug output.
if self.Debug then
local bearing=targetCoord:HeadingTo(missileCoord)
local eta=distance/missileVelocity
-- Debug distance check.
self:I(self.lid..string.format("Missile %s Target %s: Distance = %.1f m, v=%.1f m/s, bearing=%03d°, ETA=%.1f sec", missile.missileType, target:GetName(), distance, missileVelocity, bearing, eta))
end
-- Distroy missile if it's getting too close.
local destroymissile=distance<=self.explosiondist
-- Check BIG missiles.
if self.explosiondist2 and distance<=self.explosiondist2 and not destroymissile then
destroymissile=missile.explosive>=self.bigmissilemass
end
-- If missile is 150 m from target ==> destroy missile if in safe zone.
if destroymissile and self:_CheckCoordSafe(targetCoord) then
-- Destroy missile.
self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m",
missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance))
_ordnance:destroy()
-- Missile is not active any more.
missile.active=false
-- Debug smoke.
if self.Debug then
missileCoord:SmokeRed()
targetCoord:SmokeGreen()
end
-- Create event.
self:MissileDestroyed(missile)
-- Little explosion for the visual effect.
if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then
missileCoord:Explosion(self.explosionpower)
end
-- Target was a player.
if missile.targetPlayer then
-- Message to target.
local text=string.format("Destroying missile. %s", self:_DeadText())
MESSAGE:New(text, 10):ToGroup(target:GetGroup())
-- Increase dead counter.
missile.targetPlayer.dead=missile.targetPlayer.dead+1
end
-- Terminate timer.
return nil
else
-- Time step.
local dt=1.0
if distance>50000 then
-- > 50 km
dt=self.dt50 --=5.0
elseif distance>10000 then
-- 10-50 km
dt=self.dt10 --=1.0
elseif distance>5000 then
-- 5-10 km
dt=self.dt05 --0.5
elseif distance>1000 then
-- 1-5 km
dt=self.dt01 --0.1
else
-- < 1 km
dt=self.dt00 --0.01
end
-- Check again in dt seconds.
return timer.getTime()+dt
end
else
-- Destroy missile.
self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName))
return timer.getTime()+0.1
-- No target ==> terminate timer.
--return nil
end
else
-------------------------------------
-- Missile does not exist any more --
-------------------------------------
if target then
-- Get human player.
local player=self:_GetPlayerFromUnit(target)
-- Check for player and distance < 10 km.
if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then
local text=string.format("Missile defeated. Well done, %s!", player.name)
MESSAGE:New(text, 10):ToClient(player.client)
-- Increase defeated counter.
player.defeated=player.defeated+1
end
end
-- Missile is not active any more.
missile.active=false
--Terminate the timer.
self:T(FOX.lid..string.format("Terminating missile track timer."))
return nil
end -- _status check
end -- end function trackBomb
-- Weapon is not yet "alife" just yet. Start timer with a little delay.
self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds."))
timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
--timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001)
missile.Weapon:StartTrack(0.0001)
end
@@ -1246,30 +1250,29 @@ end
-- @param Core.Event#EVENTDATA EventData
function FOX:OnEventShot(EventData)
self:T2({eventshot=EventData})
-- Nil checks.
if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then
return
end
if EventData.Weapon==nil then
return
end
if EventData.IniDCSUnit==nil then
return
end
-- Create a weapon object.
local weapon=WEAPON:New(EventData.weapon)
-- Weapon data.
local _weapon = EventData.WeaponName
local _weapon = weapon:GetTypeName()
local _target = EventData.Weapon:getTarget()
local _targetName = "unknown"
local _targetUnit = nil --Wrapper.Unit#UNIT
-- Weapon descriptor.
local desc=EventData.Weapon:getDesc()
local desc=weapon.desc
self:T2({desc=desc})
-- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB
local weaponcategory=desc.category
-- Missile category: 1=AAM, 2=SAM, 6=OTHER
local missilecategory=desc.missileCategory
-- Missile range.
local missilerange=nil
if missilecategory then
missilerange=desc.rangeMaxAltMax
@@ -1279,8 +1282,8 @@ function FOX:OnEventShot(EventData)
self:T2(FOX.lid.."EVENT SHOT: FOX")
self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(_weapon)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weaponcategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(weapon:GetTypeName())))
self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weapon:GetCategory())))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s", tostring(missilecategory)))
self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s", tostring(missilerange)))
@@ -1292,7 +1295,7 @@ function FOX:OnEventShot(EventData)
end
-- Track missiles of type AAM=1, SAM=2 or OTHER=6
local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
local _track = weapon:IsMissile() and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6)
-- Only track missiles
if _track then
@@ -1301,6 +1304,7 @@ function FOX:OnEventShot(EventData)
missile.active=true
missile.weapon=EventData.weapon
missile.Weapon=weapon
missile.missileType=_weapon
missile.missileRange=missilerange
missile.missileName=EventData.weapon:getName()
@@ -1313,6 +1317,7 @@ function FOX:OnEventShot(EventData)
missile.fuseDist=desc.fuseDist
missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass
missile.targetOrig=missile.targetName
missile.missileCoord=COORDINATE:New(0,0,0)
-- Set missile target name, unit and player.
self:GetMissileTarget(missile)
@@ -1631,7 +1636,7 @@ end
--- Check if a coordinate lies within a safe training zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec3.
-- @return #boolean True if safe.
function FOX:_CheckCoordSafe(coord)
@@ -1643,7 +1648,9 @@ function FOX:_CheckCoordSafe(coord)
-- Loop over all zones.
for _,_zone in pairs(self.safezones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end
@@ -1654,7 +1661,7 @@ end
--- Check if a coordinate lies within a launch zone.
-- @param #FOX self
-- @param Core.Point#COORDINATE coord Coordinate to check.
-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec2.
-- @return #boolean True if in launch zone.
function FOX:_CheckCoordLaunch(coord)
@@ -1666,7 +1673,9 @@ function FOX:_CheckCoordLaunch(coord)
-- Loop over all zones.
for _,_zone in pairs(self.launchzones) do
local zone=_zone --Core.Zone#ZONE
local inzone=zone:IsCoordinateInZone(coord)
local Vec2={x=coord.x, y=coord.z}
local inzone=zone:IsVec2InZone(Vec2)
--local inzone=zone:IsCoordinateInZone(coord)
if inzone then
return true
end

View File

@@ -22,7 +22,7 @@
-- @module Functional.Mantis
-- @image Functional.Mantis.jpg
--
-- Last Update: Oct 2022
-- Last Update: July 2023
-------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE
@@ -104,9 +104,13 @@
-- * Silkworm (though strictly speaking this is a surface to ship missile)
-- * SA-2, SA-3, SA-5, SA-6, SA-7, SA-8, SA-9, SA-10, SA-11, SA-13, SA-15, SA-19
-- * From HDS (see note on HDS below): SA-2, SA-3, SA-10B, SA-10C, SA-12, SA-17, SA-20A, SA-20B, SA-23, HQ-2
--
-- * From SMA: RBS98M, RBS70, RBS90, RBS90M, RBS103A, RBS103B, RBS103AM, RBS103BM, Lvkv9040M
-- **NOTE** If you are using the Swedish Military Assets (SMA), please note that the **group name** for RBS-SAM types also needs to contain the keyword "SMA"
--
-- * From CH: 2S38, PantsirS1, PantsirS2, PGL-625, HQ-17A, M903PAC2, M903PAC3, TorM2, TorM2K, TorM2M, NASAMS3-AMRAAMER, NASAMS3-AIM9X2, C-RAM, PGZ-09, S350-9M100, S350-9M96D
-- **NOTE** If you are using the Military Assets by Currenthill (CH), please note that the **group name** for CH-SAM types also needs to contain the keyword "CHM"
--
-- Following the example started above, an SA-6 site group name should start with "Red SAM SA-6" then, or a blue Patriot installation with e.g. "Blue SAM Patriot".
-- **NOTE** If you are using the High-Digit-Sam Mod, please note that the **group name** for the following SAM types also needs to contain the keyword "HDS":
--
@@ -369,6 +373,7 @@ MANTIS.SamData = {
["SA-20A"] = { Range=150, Blindspot=5, Height=27, Type="Long" , Radar="S-300PMU1"},
["SA-20B"] = { Range=200, Blindspot=4, Height=27, Type="Long" , Radar="S-300PMU2"},
["HQ-2"] = { Range=50, Blindspot=6, Height=35, Type="Medium", Radar="HQ_2_Guideline_LN" },
["SHORAD"] = { Range=3, Blindspot=0, Height=3, Type="Short", Radar="Igla" }
}
--- SAM data HDS
@@ -415,6 +420,35 @@ MANTIS.SamDataSMA = {
["Lvkv9040M SMA"] = { Range=4, Blindspot=0, Height=2.5, Type="Short", Radar="LvKv9040" },
}
--- SAM data CH
-- @type MANTIS.SamDataCH
-- @field #number Range Max firing range in km
-- @field #number Blindspot no-firing range (green circle)
-- @field #number Height Max firing height in km
-- @field #string Type #MANTIS.SamType of SAM, i.e. SHORT, MEDIUM or LONG (range)
-- @field #string Radar Radar typename on unit level (used as key)
MANTIS.SamDataCH = {
-- units from CH (Military Assets by Currenthill)
-- https://www.currenthill.com/
-- group name MUST contain CHM to ID launcher type correctly!
["2S38 CH"] = { Range=8, Blindspot=0.5, Height=6, Type="Short", Radar="2S38" },
["PantsirS1 CH"] = { Range=20, Blindspot=1.2, Height=15, Type="Short", Radar="PantsirS1" },
["PantsirS2 CH"] = { Range=30, Blindspot=1.2, Height=18, Type="Medium", Radar="PantsirS2" },
["PGL-625 CH"] = { Range=10, Blindspot=0.5, Height=5, Type="Short", Radar="PGL_625" },
["HQ-17A CH"] = { Range=20, Blindspot=1.5, Height=10, Type="Short", Radar="HQ17A" },
["M903PAC2 CH"] = { Range=160, Blindspot=3, Height=24.5, Type="Long", Radar="MIM104_M903_PAC2" },
["M903PAC3 CH"] = { Range=120, Blindspot=1, Height=40, Type="Long", Radar="MIM104_M903_PAC3" },
["TorM2 CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2" },
["TorM2K CH"] = { Range=12, Blindspot=1, Height=10, Type="Short", Radar="TorM2K" },
["TorM2M CH"] = { Range=16, Blindspot=1, Height=10, Type="Short", Radar="TorM2M" },
["NASAMS3-AMRAAMER CH"] = { Range=50, Blindspot=2, Height=35.7, Type="Medium", Radar="CH_NASAMS3_LN_AMRAAM_ER" },
["NASAMS3-AIM9X2 CH"] = { Range=20, Blindspot=0.2, Height=18, Type="Short", Radar="CH_NASAMS3_LN_AIM9X2" },
["C-RAM CH"] = { Range=2, Blindspot=0, Height=2, Type="Short", Radar="CH_Centurion_C_RAM" },
["PGZ-09 CH"] = { Range=4, Blindspot=0, Height=3, Type="Short", Radar="CH_PGZ09" },
["S350-9M100 CH"] = { Range=15, Blindspot=1.5, Height=8, Type="Short", Radar="CH_S350_50P6_9M100" },
["S350-9M96D CH"] = { Range=150, Blindspot=2.5, Height=30, Type="Long", Radar="CH_S350_50P6_9M96D" },
}
-----------------------------------------------------------------------
-- MANTIS System
-----------------------------------------------------------------------
@@ -578,7 +612,7 @@ do
-- TODO Version
-- @field #string version
self.version="0.8.9"
self.version="0.8.11"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions ---
@@ -1299,11 +1333,12 @@ do
-- @param #string grpname Name of the group
-- @param #boolean mod HDS mod flag
-- @param #boolean sma SMA mod flag
-- @param #boolean chm CH mod flag
-- @return #number range Max firing range
-- @return #number height Max firing height
-- @return #string type Long, medium or short range
-- @return #number blind "blind" spot
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma)
function MANTIS:_GetSAMDataFromUnits(grpname,mod,sma,chm)
self:T(self.lid.."_GetSAMRangeFromUnits")
local found = false
local range = self.checkradius
@@ -1318,8 +1353,10 @@ do
SAMData = self.SamDataHDS
elseif sma then
SAMData = self.SamDataSMA
elseif chm then
SAMData = self.SamDataCH
end
--self:I("Looking to auto-match for "..grpname)
--self:T("Looking to auto-match for "..grpname)
for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT
local type = string.lower(unit:GetTypeName())
@@ -1364,10 +1401,13 @@ do
local found = false
local HDSmod = false
local SMAMod = false
local CHMod = false
if string.find(grpname,"HDS",1,true) then
HDSmod = true
elseif string.find(grpname,"SMA",1,true) then
SMAMod = true
elseif string.find(grpname,"CHM",1,true) then
CHMod = true
end
if self.automode then
for idx,entry in pairs(self.SamData) do
@@ -1386,8 +1426,8 @@ do
end
end
-- secondary filter if not found
if (not found and self.automode) or HDSmod or SMAMod then
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod)
if (not found and self.automode) or HDSmod or SMAMod or CHMod then
range, height, type = self:_GetSAMDataFromUnits(grpname,HDSmod,SMAMod,CHMod)
elseif not found then
self:E(self.lid .. string.format("*****Could not match radar data for %s! Will default to midrange values!",grpname))
end

View File

@@ -26,9 +26,9 @@
--
-- ===
--
-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)**
-- ### Author: **funkyfranky**
--
-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536)
-- ### Contributions: FlightControl, Applevangelist
--
-- ====
-- @module Functional.PseudoATC
@@ -44,7 +44,8 @@
-- @field #number mrefresh Interval in seconds after which the F10 menu is refreshed. E.g. by the closest airports. Default is 120 sec.
-- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec.
-- @field #boolean chatty Display some messages on events like take-off and touchdown.
-- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler.
-- @field #boolean eventsmoose [Deprecated] If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler.
-- @field #boolean reportplayername If true, use playername not callsign on callouts
-- @extends Core.Base#BASE
--- Adds some rudimentary ATC functionality via the radio menu.
@@ -88,6 +89,7 @@ PSEUDOATC={
talt=3,
chatty=true,
eventsmoose=true,
reportplayername = false,
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -98,13 +100,14 @@ PSEUDOATC.id="PseudoATC | "
--- PSEUDOATC version.
-- @field #number version
PSEUDOATC.version="0.9.2"
PSEUDOATC.version="0.10.5"
-----------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-- DONE: Add takeoff event.
-- DONE: Add user functions.
-- DONE: Refactor to use Moose event handling only
-----------------------------------------------------------------------------------------------------------------------------------------
@@ -129,23 +132,14 @@ function PSEUDOATC:Start()
self:F()
-- Debug info
self:E(PSEUDOATC.id.."Starting PseudoATC")
self:I(PSEUDOATC.id.."Starting PseudoATC")
-- Handle events.
if self.eventsmoose then
self:T(PSEUDOATC.id.."Events are handled by MOOSE.")
self:HandleEvent(EVENTS.Birth, self._OnBirth)
self:HandleEvent(EVENTS.Land, self._PlayerLanded)
self:HandleEvent(EVENTS.Takeoff, self._PlayerTakeOff)
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft)
self:HandleEvent(EVENTS.Crash, self._PlayerLeft)
--self:HandleEvent(EVENTS.Ejection, self._PlayerLeft)
--self:HandleEvent(EVENTS.PilotDead, self._PlayerLeft)
else
self:T(PSEUDOATC.id.."Events are handled by DCS.")
-- Events are handled directly by DCS.
world.addEventHandler(self)
end
self:HandleEvent(EVENTS.Birth, self._OnBirth)
self:HandleEvent(EVENTS.Land, self._PlayerLanded)
self:HandleEvent(EVENTS.Takeoff, self._PlayerTakeOff)
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft)
self:HandleEvent(EVENTS.Crash, self._PlayerLeft)
end
@@ -183,6 +177,13 @@ function PSEUDOATC:SetMessageDuration(duration)
self.mdur=duration or 30
end
--- Use player name, not call sign, in callouts
-- @param #PSEUDOATC self
function PSEUDOATC:SetReportPlayername()
self.reportplayername = true
return self
end
--- Set time interval after which the F10 radio menu is refreshed.
-- @param #PSEUDOATC self
-- @param #number interval Interval in seconds. Default is every 120 sec.
@@ -190,7 +191,7 @@ function PSEUDOATC:SetMenuRefresh(interval)
self.mrefresh=interval or 120
end
--- Enable/disable event handling by MOOSE or DCS.
--- [Deprecated] Enable/disable event handling by MOOSE or DCS.
-- @param #PSEUDOATC self
-- @param #boolean switch If true, events are handled by MOOSE (default). If false, events are handled directly by DCS.
function PSEUDOATC:SetEventsMoose(switch)
@@ -207,84 +208,6 @@ end
-----------------------------------------------------------------------------------------------------------------------------------------
-- Event Handling
--- Event handler for suppressed groups.
--@param #PSEUDOATC self
--@param #table Event Event data table. Holds event.id, event.initiator and event.target etc.
function PSEUDOATC:onEvent(Event)
if Event == nil or Event.initiator == nil or Unit.getByName(Event.initiator:getName()) == nil then
return true
end
local DCSiniunit = Event.initiator
local DCSplace = Event.place
local DCSsubplace = Event.subplace
local EventData={}
local _playerunit=nil
local _playername=nil
if Event.initiator then
EventData.IniUnitName = Event.initiator:getName()
EventData.IniDCSGroup = Event.initiator:getGroup()
EventData.IniGroupName = Event.initiator:getGroup():getName()
-- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in.
_playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName)
end
if Event.place then
EventData.Place=Event.place
EventData.PlaceName=Event.place:getName()
end
if Event.subplace then
EventData.SubPlace=Event.subplace
EventData.SubPlaceName=Event.subplace:getName()
end
-- Event info.
self:T3(PSEUDOATC.id..string.format("EVENT: Event in onEvent with ID = %s", tostring(Event.id)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini unit = %s" , tostring(EventData.IniUnitName)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini group = %s" , tostring(EventData.IniGroupName)))
self:T3(PSEUDOATC.id..string.format("EVENT: Ini player = %s" , tostring(_playername)))
self:T3(PSEUDOATC.id..string.format("EVENT: Place = %s" , tostring(EventData.PlaceName)))
self:T3(PSEUDOATC.id..string.format("EVENT: SubPlace = %s" , tostring(EventData.SubPlaceName)))
-- Event birth.
if Event.id == world.event.S_EVENT_BIRTH and _playername then
self:_OnBirth(EventData)
end
-- Event takeoff.
if Event.id == world.event.S_EVENT_TAKEOFF and _playername and EventData.Place then
self:_PlayerTakeOff(EventData)
end
-- Event land.
if Event.id == world.event.S_EVENT_LAND and _playername and EventData.Place then
self:_PlayerLanded(EventData)
end
-- Event player left unit
if Event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and _playername then
self:_PlayerLeft(EventData)
end
-- Event crash ==> player left unit
if Event.id == world.event.S_EVENT_CRASH and _playername then
self:_PlayerLeft(EventData)
end
--[[
-- Event eject ==> player left unit
if Event.id == world.event.S_EVENT_EJECTION and _playername then
self:_PlayerLeft(EventData)
end
-- Event pilot dead ==> player left unit
if Event.id == world.event.S_EVENT_PILOT_DEAD and _playername then
self:_PlayerLeft(EventData)
end
]]
end
--- Function called my MOOSE event handler when a player enters a unit.
-- @param #PSEUDOATC self
@@ -294,7 +217,9 @@ function PSEUDOATC:_OnBirth(EventData)
-- Get unit and player.
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
--local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
local _unit = EventData.IniUnit
local _playername = EventData.IniPlayerName
-- Check if a player entered.
if _unit and _playername then
@@ -311,7 +236,10 @@ function PSEUDOATC:_PlayerLeft(EventData)
-- Get unit and player.
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
--local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
local _unit = EventData.IniUnit
local _playername = EventData.IniPlayerName
-- Check if a player left.
if _unit and _playername then
@@ -326,18 +254,16 @@ function PSEUDOATC:_PlayerLanded(EventData)
self:F({EventData=EventData})
-- Get unit, player and place.
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
local _unitName=EventData.IniUnitName
local _unit = EventData.IniUnit
local _playername = EventData.IniPlayerName
--local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
local _base=nil
local _baseName=nil
if EventData.place then
_base=EventData.place
_baseName=EventData.place:getName()
end
-- if EventData.subplace then
-- local _subPlace=EventData.subplace
-- local _subPlaceName=EventData.subplace:getName()
-- end
-- Call landed function.
if _unit and _playername and _base then
@@ -352,8 +278,10 @@ function PSEUDOATC:_PlayerTakeOff(EventData)
self:F({EventData=EventData})
-- Get unit, player and place.
local _unitName=EventData.IniUnitName
local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local _unitName=EventData.IniUnitName
local _unit = EventData.IniUnit
local _playername = EventData.IniPlayerName
--local _unit,_playername=self:_GetPlayerUnitAndName(_unitName)
local _base=nil
local _baseName=nil
if EventData.place then
@@ -441,14 +369,15 @@ function PSEUDOATC:PlayerLanded(unit, place)
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
local PlayerName = unit:GetPlayerName() or "Ghost"
local UnitName = unit:GetName() or "Ghostplane"
local GroupName = group:GetName() or "Ghostgroup"
if self.Debug then
-- Debug message.
local text=string.format("Player %s in unit %s of group %s landed at %s.", PlayerName, UnitName, GroupName, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
end
-- Stop altitude reporting timer if its activated.
self:AltitudeTimerStop(GID,UID)
@@ -470,21 +399,22 @@ function PSEUDOATC:PlayerTakeOff(unit, place)
-- Gather some information.
local group=unit:GetGroup()
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
local GroupName=self.group[GID].player[UID].groupname
-- Debug message.
local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
local PlayerName = unit:GetPlayerName() or "Ghost"
local UnitName = unit:GetName() or "Ghostplane"
local GroupName = group:GetName() or "Ghostgroup"
local CallSign = unit:GetCallsign() or "Ghost11"
if self.Debug then
-- Debug message.
local text=string.format("Player %s in unit %s of group %s took off at %s.", PlayerName, UnitName, GroupName, place)
self:T(PSEUDOATC.id..text)
MESSAGE:New(text, 30):ToAllIf(self.Debug)
end
-- Bye-Bye message.
if place and self.chatty then
local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign)
if self.reportplayername then
text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName)
end
MESSAGE:New(text, self.mdur):ToGroup(group)
end
@@ -501,7 +431,7 @@ function PSEUDOATC:PlayerLeft(unit)
local GID=group:GetID()
local UID=unit:GetDCSObject():getID()
if self.group[GID].player[UID] then
if self.group[GID] and self.group[GID].player and self.group[GID].player[UID] then
local PlayerName=self.group[GID].player[UID].playername
local CallSign=self.group[GID].player[UID].callsign
local UnitName=self.group[GID].player[UID].unitname
@@ -687,7 +617,9 @@ function PSEUDOATC:MenuWaypoints(GID, UID)
-- Position of Waypoint
local pos=COORDINATE:New(wp.x, wp.alt, wp.y)
local name=string.format("Waypoint %d", i-1)
if wp.name and wp.name ~= "" then
name = string.format("Waypoint %s",wp.name)
end
-- "F10/PseudoATC/Waypoints/Waypoint X"
local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints)
@@ -844,7 +776,8 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
local position=unit:GetCoordinate()
local height=get_AGL(position)
local callsign=unit:GetCallsign()
local PlayerName=self.group[GID].player[UID].playername
-- Settings.
local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS
@@ -856,7 +789,9 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear)
-- Message text.
local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs)
if self.reportplayername then
_text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs)
end
-- Append flight level.
if _clear==false then
_text=_text..string.format(" FL%03d.", position.y/30.48)
@@ -901,7 +836,7 @@ function PSEUDOATC:AltitudeTimeStart(GID, UID)
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", UID))
-- Start timer. Altitude is reported every ~3 seconds.
self.group[GID].player[UID].altimer, self.group[GID].player[UID].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, GID, UID, 0.1, true}, 1, 3)
self.group[GID].player[UID].altimer, self.group[GID].player[UID].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, GID, UID, 1, true}, 1, 3)
end
--- Stop/destroy DCS scheduler function for reporting altitude.

View File

@@ -3492,7 +3492,7 @@ function RAT:Status(message, forID)
local fuel=group:GetFuel()*100.0
local airborne=group:InAir()
local coords=group:GetCoordinate()
local alt=coords.y
local alt=coords.y or 1000
--local vel=group:GetVelocityKMH()
local departure=ratcraft.departure:GetName()
local destination=ratcraft.destination:GetName()
@@ -5490,7 +5490,7 @@ function RAT:_ATCInit(airports_map)
if not RAT.ATC.init then
local text
text="Starting RAT ATC.\nSimultanious = "..RAT.ATC.Nclearance.."\n".."Delay = "..RAT.ATC.delay
BASE:T(RAT.id..text)
BASE:T(RAT.id..text)
RAT.ATC.init=true
for _,ap in pairs(airports_map) do
local name=ap:GetName()
@@ -5671,6 +5671,9 @@ function RAT:_ATCClearForLanding(airport, flight)
-- Debug message.
local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).", airport, flight, flagvalue)
if string.find(flight,"#") then
flight = string.match(flight,"^(.+)#")
end
local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight)
BASE:T( RAT.id..text1)
MESSAGE:New(text2, 10):ToAllIf(RAT.ATC.messages)
@@ -5713,6 +5716,9 @@ function RAT:_ATCFlightLanded(name)
local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60)
local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal)
local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour)
if string.find(name,"#") then
name = string.match(name,"^(.+)#")
end
local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest)
BASE:T(RAT.id..text1)
BASE:T(RAT.id..text2)
@@ -5826,6 +5832,7 @@ RATMANAGER={
rat={},
name={},
alive={},
planned={},
min={},
nrat=0,
ntot=nil,
@@ -5874,6 +5881,7 @@ function RATMANAGER:Add(ratobject,min)
self.rat[self.nrat]=ratobject
self.alive[self.nrat]=0
self.planned[self.nrat]=0
self.name[self.nrat]=ratobject.alias
self.min[self.nrat]=min or 1
@@ -6014,11 +6022,25 @@ function RATMANAGER:_Manage()
for i=1,self.nrat do
for j=1,N[i] do
time=time+self.dTspawn
SCHEDULER:New(nil, RAT._SpawnWithRoute, {self.rat[i]}, time)
self.planned[i]=self.planned[i]+1
SCHEDULER:New(nil, RATMANAGER._Spawn, {self, i}, time)
end
end
end
--- Instantly starts the RAT manager and spawns the initial random number RAT groups for each RAT object.
-- @param #RATMANAGER self
-- @param #RATMANAGER RATMANAGER self object.
-- @param #number i Index.
function RATMANAGER:_Spawn(i)
local rat=self.rat[i] --#RAT
rat:_SpawnWithRoute()
self.planned[i]=self.planned[i]-1
end
--- Counts the number of alive RAT objects.
-- @param #RATMANAGER self
function RATMANAGER:_Count()
@@ -6047,7 +6069,7 @@ function RATMANAGER:_Count()
ntotal=ntotal+n
-- Debug output.
local text=string.format("Number of alive groups of %s = %d", self.name[i], n)
local text=string.format("Number of alive groups of %s = %d, planned=%d", self.name[i], n, self.planned[i])
self:T(RATMANAGER.id..text)
end
@@ -6077,9 +6099,10 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive)
local M={}
local P={}
for i=1,nrat do
local a=alive[i]+self.planned[i]
N[#N+1]=0
M[#M+1]=math.max(alive[i], min[i])
P[#P+1]=math.max(min[i]-alive[i],0)
M[#M+1]=math.max(a, min[i])
P[#P+1]=math.max(min[i]-a,0)
end
-- Min/max group arrays.
@@ -6096,7 +6119,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive)
-- Number of new groups to be added.
local nnew=ntot
for i=1,nrat do
nnew=nnew-alive[i]
nnew=nnew-alive[i]-self.planned[i]
end
for i=1,nrat-1 do
@@ -6128,7 +6151,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive)
end
-- Debug info
self:T3(string.format("RATMANAGER: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d", j, alive[j], min[j], mini[j], maxi[j], N[j],sN, sP))
self:T3(string.format("RATMANAGER: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d, sumN=%d, sumP=%d", j, alive[j], self.planned[i], min[j], mini[j], maxi[j], N[j],sN, sP))
end
@@ -6143,7 +6166,7 @@ function RATMANAGER:_RollDice(nrat,ntot,min,alive)
-- Debug info
local text=RATMANAGER.id.."\n"
for i=1,nrat do
text=text..string.format("%s: i=%d, alive=%d, min=%d, mini=%d, maxi=%d, add=%d\n", self.name[i], i, alive[i], min[i], mini[i], maxi[i], N[i])
text=text..string.format("%s: i=%d, alive=%d, planned=%d, min=%d, mini=%d, maxi=%d, add=%d\n", self.name[i], i, alive[i], self.planned[i], min[i], mini[i], maxi[i], N[i])
end
text=text..string.format("Total # of groups to add = %d", sum(N, done))
self:T(text)

File diff suppressed because it is too large Load Diff

View File

@@ -715,11 +715,11 @@ function SCORING:AddGoalScorePlayer( PlayerName, GoalTag, Text, Score )
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Text,
MESSAGE.Type.Information )
:ToAll()
if Text then
MESSAGE:NewType( self.DisplayMessagePrefix .. Text,
MESSAGE.Type.Information )
:ToAll()
end
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, nil )
end
end
@@ -738,7 +738,7 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
local PlayerName = PlayerUnit:GetPlayerName()
self:F( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } )
self:T2( { PlayerUnit.UnitName, PlayerName, GoalTag, Text, Score } )
-- PlayerName can be nil, if the Unit with the player crashed or due to another reason.
if PlayerName then
@@ -747,11 +747,12 @@ function SCORING:AddGoalScore( PlayerUnit, GoalTag, Text, Score )
PlayerData.Goals[GoalTag] = PlayerData.Goals[GoalTag] or { Score = 0 }
PlayerData.Goals[GoalTag].Score = PlayerData.Goals[GoalTag].Score + Score
PlayerData.Score = PlayerData.Score + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Text,
if Text then
MESSAGE:NewType( self.DisplayMessagePrefix .. Text,
MESSAGE.Type.Information )
:ToAll()
end
self:ScoreCSV( PlayerName, "", "GOAL_" .. string.upper( GoalTag ), 1, Score, PlayerUnit:GetName() )
end
end
@@ -784,11 +785,12 @@ function SCORING:_AddMissionTaskScore( Mission, PlayerUnit, Text, Score )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. Mission:GetText() .. " : " .. Text .. " Score: " .. Score,
MESSAGE.Type.Information )
:ToAll()
if Text then
MESSAGE:NewType( self.DisplayMessagePrefix .. Mission:GetText() .. " : " .. Text .. " Score: " .. Score,
MESSAGE.Type.Information )
:ToAll()
end
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:GetName() )
end
end
@@ -820,9 +822,11 @@ function SCORING:_AddMissionGoalScore( Mission, PlayerName, Text, Score )
PlayerData.Score = self.Players[PlayerName].Score + Score
PlayerData.Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:NewType( string.format( "%s%s: %s! Player %s receives %d score!", self.DisplayMessagePrefix, Mission:GetText(), Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll()
if Text then
MESSAGE:NewType( string.format( "%s%s: %s! Player %s receives %d score!", self.DisplayMessagePrefix, Mission:GetText(), Text, PlayerName, Score ), MESSAGE.Type.Information ):ToAll()
end
self:ScoreCSV( PlayerName, "", "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
@@ -847,11 +851,12 @@ function SCORING:_AddMissionScore( Mission, Text, Score )
PlayerData.Score = PlayerData.Score + Score
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in " .. Mission:GetText() .. ". " .. Score .. " mission score!",
if Text then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' has " .. Text .. " in " .. Mission:GetText() .. ". " .. Score .. " mission score!",
MESSAGE.Type.Information )
:ToAll()
end
self:ScoreCSV( PlayerName, "", "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
@@ -873,8 +878,10 @@ end
function SCORING:OnEventBirth( Event )
if Event.IniUnit then
Event.IniUnit.ThreatLevel, Event.IniUnit.ThreatType = Event.IniUnit:GetThreatLevel()
if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName()
Event.IniUnit.BirthTime = timer.getTime()
if PlayerName then
self:_AddPlayerFromUnit( Event.IniUnit )
self:SetScoringMenu( Event.IniGroup )
@@ -1005,7 +1012,18 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown"
end
else
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
end
-- Only grant hit scores if there was more than one second between the last hit.
if timer.getTime() - PlayerHit.TimeStamp > 1 then
@@ -1021,27 +1039,30 @@ function SCORING:_EventOnHit( Event )
if InitCoalition then -- A coalition object was hit.
if InitCoalition == TargetCoalition then
Player.Penalty = Player.Penalty + 10
PlayerHit.Penalty = PlayerHit.Penalty + 10
local Penalty = 10
Player.Penalty = Player.Penalty + Penalty
PlayerHit.Penalty = PlayerHit.Penalty + Penalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
"Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " ..
"Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
"Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
end
self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
Player.Score = Player.Score + 1
PlayerHit.Score = PlayerHit.Score + 1
-- Hitting a target multiple times before destoying it should not result in a higger score
-- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.ScoreHit .. " times. " ..
@@ -1104,7 +1125,18 @@ function SCORING:_EventOnHit( Event )
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0
PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0
PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT
-- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth
if PlayerHit.UNIT.ThreatType == nil then
PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel()
-- if this fails for some reason, set a good default value
if PlayerHit.ThreatType == nil then
PlayerHit.ThreatLevel = 1
PlayerHit.ThreatType = "Unknown"
end
else
PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel
PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType
end
-- Only grant hit scores if there was more than one second between the last hit.
if timer.getTime() - PlayerHit.TimeStamp > 1 then
@@ -1115,25 +1147,28 @@ 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
local Penalty = 10
Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* 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,
"Penalty: -" .. 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
PlayerHit.Score = PlayerHit.Score + 1
-- Hitting a target multiple times before destoying it should not result in a higger score
-- Multiple hits is typically a results of bombs/missles missing their target but still inflict some spash damage
-- Player.Score = Player.Score + 1
-- PlayerHit.Score = PlayerHit.Score + 1
PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
"Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty,
"Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty,
MESSAGE.Type.Update )
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
@@ -1211,7 +1246,7 @@ function SCORING:_EventOnDeadOrCrash( Event )
local Destroyed = false
-- What is the player destroying?
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 then -- Was there a hit for this unit for this player before registered???
if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered???
local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel
local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType
@@ -1240,13 +1275,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty,
"Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1268,13 +1303,13 @@ function SCORING:_EventOnDeadOrCrash( Event )
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
else
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " ..
"Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty,
"Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() )
@@ -1732,9 +1767,9 @@ function SCORING:SecondsToClock( sSeconds )
-- return nil;
return "00:00:00";
else
nHours = string.format( "%02.f", math.floor( nSeconds / 3600 ) );
nMins = string.format( "%02.f", math.floor( nSeconds / 60 - (nHours * 60) ) );
nSecs = string.format( "%02.f", math.floor( nSeconds - nHours * 3600 - nMins * 60 ) );
local nHours = string.format( "%02.f", math.floor( nSeconds / 3600 ) );
local nMins = string.format( "%02.f", math.floor( nSeconds / 60 - (nHours * 60) ) );
local nSecs = string.format( "%02.f", math.floor( nSeconds - nHours * 3600 - nMins * 60 ) );
return nHours .. ":" .. nMins .. ":" .. nSecs
end
end

View File

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

View File

@@ -302,8 +302,8 @@
--
-- Initial Spawn states is as follows:
-- GROUND: ROE, "Return Fire" Alarm, "Green"
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense"
-- NAVAL ROE, "Return Fire" Alarm,"N/A"
--
-- A request can be added by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function.
-- The parameters are
@@ -1798,7 +1798,7 @@ _WAREHOUSEDB = {
--- Warehouse class version.
-- @field #string version
WAREHOUSE.version="1.0.2"
WAREHOUSE.version="1.0.2a"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list.
@@ -2647,6 +2647,13 @@ function WAREHOUSE:SetWarehouseZone(zone)
return self
end
--- Get the warehouse zone.
-- @param #WAREHOUSE self
-- @return Core.Zone#ZONE The warehouse zone.
function WAREHOUSE:GetWarehouseZone()
return self.zone
end
--- Set auto defence on. When the warehouse is under attack, all ground assets are spawned automatically and will defend the warehouse zone.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
@@ -4554,7 +4561,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request)
self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!")
return
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER then
elseif Request.transporttype==WAREHOUSE.TransportType.SHIP or Request.transporttype==WAREHOUSE.TransportType.NAVALCARRIER
or Request.transporttype==WAREHOUSE.TransportType.ARMEDSHIP or Request.transporttype==WAREHOUSE.TransportType.WARSHIP then
-- Spawn Ship in port zone
spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone)
@@ -5810,6 +5818,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
-- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning.
local Parking={}
if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then
--TODO: Check for airstart. Should be a request property.
Parking=self:_FindParkingForAssets(self.airbase, cargoassets) or {}
end
@@ -5821,62 +5830,65 @@ function WAREHOUSE:_SpawnAssetRequest(Request)
-- Get stock item.
local asset=cargoassets[i] --#WAREHOUSE.Assetitem
if not asset.spawned then
-- Set asset status to not spawned until we capture its birth event.
asset.spawned=false
asset.iscargo=true
-- Set request ID.
asset.rid=Request.uid
-- Spawn group name.
local _alias=asset.spawngroupname
--Request add asset by id.
Request.assets[asset.uid]=asset
-- Spawn an asset group.
local _group=nil --Wrapper.Group#GROUP
if asset.category==Group.Category.GROUND then
-- Spawn ground troops.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation)
elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
-- Spawn air units.
if Parking[asset.uid] then
_group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled, Request.lateActivation)
else
_group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled, Request.lateActivation)
end
elseif asset.category==Group.Category.TRAIN then
-- Spawn train.
if self.rail then
--TODO: Rail should only get one asset because they would spawn on top!
-- Spawn naval assets.
-- Set asset status to not spawned until we capture its birth event.
asset.iscargo=true
-- Set request ID.
asset.rid=Request.uid
-- Spawn group name.
local _alias=asset.spawngroupname
--Request add asset by id.
Request.assets[asset.uid]=asset
-- Spawn an asset group.
local _group=nil --Wrapper.Group#GROUP
if asset.category==Group.Category.GROUND then
-- Spawn ground troops.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation)
elseif asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then
-- Spawn air units.
if Parking[asset.uid] then
_group=self:_SpawnAssetAircraft(_alias, asset, Request, Parking[asset.uid], UnControlled, Request.lateActivation)
else
_group=self:_SpawnAssetAircraft(_alias, asset, Request, nil, UnControlled, Request.lateActivation)
end
elseif asset.category==Group.Category.TRAIN then
-- Spawn train.
if self.rail then
--TODO: Rail should only get one asset because they would spawn on top!
-- Spawn naval assets.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.spawnzone, Request.lateActivation)
end
--self:E(self.lid.."ERROR: Spawning of TRAIN assets not possible yet!")
elseif asset.category==Group.Category.SHIP then
-- Spawn naval assets.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone, Request.lateActivation)
else
self:E(self.lid.."ERROR: Unknown asset category!")
end
--self:E(self.lid.."ERROR: Spawning of TRAIN assets not possible yet!")
elseif asset.category==Group.Category.SHIP then
-- Spawn naval assets.
_group=self:_SpawnAssetGroundNaval(_alias, asset, Request, self.portzone, Request.lateActivation)
else
self:E(self.lid.."ERROR: Unknown asset category!")
-- Trigger event.
if _group then
self:__AssetSpawned(0.01, _group, asset, Request)
end
end
-- Trigger event.
if _group then
self:__AssetSpawned(0.01, _group, asset, Request)
end
end
end
@@ -6069,7 +6081,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
end
if self.Debug then
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)
coord:MarkToAll(text)
env.info(text)
end
unit.x=coord.x
@@ -7374,6 +7388,7 @@ function WAREHOUSE:_CheckRequestNow(request)
local _transports
local _assetattribute
local _assetcategory
local _assetairstart=false
-- Check if at least one (cargo) asset is available.
if _nassets>0 then
@@ -7381,21 +7396,28 @@ function WAREHOUSE:_CheckRequestNow(request)
-- Get the attibute of the requested asset.
_assetattribute=_assets[1].attribute
_assetcategory=_assets[1].category
_assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false
-- Check available parking for air asset units.
if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then
if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then
if self:IsRunwayOperational() then
if self:IsRunwayOperational() or _assetairstart then
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
--if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
self:_InfoMessage(text, 5)
return false
if _assetairstart then
-- Airstart no need to check parking
else
-- Check parking.
local Parking=self:_FindParkingForAssets(self.airbase,_assets)
-- No parking?
if Parking==nil then
local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias)
self:_InfoMessage(text, 5)
return false
end
end
else
@@ -7969,93 +7991,123 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
-- Loop over all assets that need a parking psot.
for _,asset in pairs(assets) do
local _asset=asset --#WAREHOUSE.Assetitem
-- Get terminal type of this asset
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking.
parking[_asset.uid]={}
-- Loop over all units - each one needs a spot.
for i=1,_asset.nunits do
-- Asset name
local assetname=_asset.spawngroupname.."-"..tostring(i)
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
free=false
problem=obstacle
problem.dist=dist
break
else
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this asset unit.
table.insert(parking[_asset.uid], parkingspot)
-- Debug
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
gotit=true
break
if not _asset.spawned then
-- Get terminal type of this asset
local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory())
-- Asset specific parking.
parking[_asset.uid]={}
-- Loop over all units - each one needs a spot.
for i=1,_asset.nunits do
-- Asset name
local assetname=_asset.spawngroupname.."-"..tostring(i)
-- Loop over all parking spots.
local gotit=false
for _,_parkingspot in pairs(parkingdata) do
local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot
-- Parking valid?
local valid=true
if asset.parkingIDs then
-- If asset has assigned parking spots, we take these no matter what.
valid=self:_CheckParkingAsset(parkingspot, asset)
else
-- Debug output for occupied spots.
if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
self:I(self.lid..text)
coord:MarkToAll(string.format(text))
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
end
-- Valid terminal type depending on attribute.
local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype)
-- Valid parking list.
local validParking=self:_CheckParkingValid(parkingspot)
-- Black and white list.
local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID)
-- Debug info.
--env.info(string.format("FF validTerminal = %s", tostring(validTerminal)))
--env.info(string.format("FF validParking = %s", tostring(validParking)))
--env.info(string.format("FF validBWlist = %s", tostring(validBWlist)))
-- Check if all are true
valid=validTerminal and validParking and validBWlist
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
return nil
end
end -- loop over asset units
-- Check correct terminal type for asset. We don't want helos in shelters etc.
if valid then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local free=true
local problem=nil
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle.
local dist=_spot:Get2DDistance(obstacle.coord)
local safe=_overlap(_asset.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist))
free=false
problem=obstacle
problem.dist=dist
break
else
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist))
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this asset unit.
table.insert(parking[_asset.uid], parkingspot)
-- Debug
self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"})
gotit=true
break
else
-- Debug output for occupied spots.
if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE
local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist)
self:I(self.lid..text)
coord:MarkToAll(string.format(text))
else
self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid))
end
end
else
self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType))
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one asset :(
if not gotit then
self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid))
return nil
end
end -- loop over asset units
end -- Asset spawned check
end -- loop over asset groups
return parking

View File

@@ -715,7 +715,7 @@ do -- ZONE_CAPTURE_COALITION
local UnitHit = EventData.TgtUnit
if UnitHit.ClassName ~= "SCENERY" then
if UnitHit and UnitHit.ClassName ~= "SCENERY" then
-- Check if unit is inside the capture zone and that it is of the defending coalition.
if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then

View File

@@ -33,6 +33,7 @@ __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/Core/Pathline.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@@ -45,6 +46,8 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )

View File

@@ -1,3 +1,4 @@
---@diagnostic disable: cast-local-type
--- **Ops** - Automatic Terminal Information Service (ATIS).
--
-- ===
@@ -93,6 +94,8 @@
-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used.
-- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights
-- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players.
-- @field #string SRSText Text of the complete SRS message (if done at least once, else nil)
-- @field #boolean ATISforFARPs Will be set to true if the base given is a FARP/Helipad
-- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -268,6 +271,8 @@
-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before
-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds.
--
-- An SRS Setup-Guide can be found here: [Moose TTS Setup Guide](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf)
--
-- # Examples
--
-- ## Caucasus: Batumi
@@ -306,6 +311,19 @@
-- atis:Start()
--
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
--
-- ## FARPS
--
-- ATIS is working with FARPS, but this requires the usage of SRS. The airbase name for the `New()-method` is the UNIT name of the FARP:
--
-- atis = ATIS:New("FARP Gold",119,radio.modulation.AM)
-- atis:SetMetricUnits()
-- atis:SetTransmitOnlyWithPlayers(true)
-- atis:SetReportmBar(true)
-- atis:SetTowerFrequencies(127.50)
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US",nil,5002)
-- atis:SetAdditionalInformation("Welcome to the Jungle!")
-- atis:__Start(3)
--
-- @field #ATIS
ATIS = {
@@ -348,6 +366,7 @@ ATIS = {
relHumidity = nil,
ReportmBar = false,
TransmitOnlyWithPlayers = false,
ATISforFARPs = false,
}
--- NATO alphabet.
@@ -400,6 +419,8 @@ ATIS.RunwayM2T = {
TheChannel = -10,
Syria = 5,
MarianaIslands = 2,
Falklands = 12,
Sinai = 5,
}
--- Whether ICAO phraseology is used for ATIS broadcasts.
@@ -411,6 +432,8 @@ ATIS.RunwayM2T = {
-- @field #boolean TheChannel true.
-- @field #boolean Syria true.
-- @field #boolean MarianaIslands true.
-- @field #boolean Falklands true.
-- @field #boolean Sinai true.
ATIS.ICAOPhraseology = {
Caucasus = true,
Nevada = false,
@@ -418,7 +441,9 @@ ATIS.ICAOPhraseology = {
PersianGulf = true,
TheChannel = true,
Syria = true,
MarianaIslands = true
MarianaIslands = true,
Falklands = true,
Sinai = true,
}
--- Nav point data.
@@ -590,15 +615,16 @@ _ATIS = {}
--- ATIS class version.
-- @field #string version
ATIS.version = "0.9.10"
ATIS.version = "0.9.15"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add new Normandy airfields.
-- TODO: Zulu time --> Zulu in output.
-- TODO: Correct fog for elevation.
-- DONE: Zulu time --> Zulu in output.
-- DONE: Fix for AB not having a runway - Helopost like Naqoura
-- DONE: Add new Normandy airfields.
-- DONE: Use new AIRBASE system to set start/landing runway
-- DONE: SetILS doesn't work
-- DONE: Visibility reported twice over SRS
@@ -616,7 +642,7 @@ ATIS.version = "0.9.10"
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ATIS class object for a specific aircraft carrier unit.
--- Create a new ATIS class object for a specific airbase.
-- @param #ATIS self
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
@@ -878,6 +904,13 @@ function ATIS:SetMapMarks( switch )
return self
end
--- Return the complete SRS Text block, if at least generated once. Else nil.
-- @param #ATIS self
-- @return #string SRSText
function ATIS:GetSRSText()
return self.SRSText
end
--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°.
-- @param #ATIS self
-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended.
@@ -1039,7 +1072,7 @@ end
--
-- * 186° on the Caucaus map
-- * 192° on the Nevada map
-- * 170° on the Normany map
-- * 170° on the Normandy map
-- * 182° on the Persian Gulf map
--
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
@@ -1245,13 +1278,21 @@ end
-- @param #string Event Event.
-- @param #string To To state.
function ATIS:onafterStart( From, Event, To )
self:I("Airbase category is "..self.airbase:GetAirbaseCategory())
-- Check that this is an airdrome.
if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) )
if self.airbase:GetAirbaseCategory() == Airbase.Category.SHIP then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.", self.airbasename ) )
return
end
-- Check that if is a Helipad.
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
self:E( self.lid .. string.format( "EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON", self.airbasename ) )
self.ATISforFARPs = true
self.useSRS = true
end
-- Info.
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
@@ -1319,8 +1360,10 @@ function ATIS:onafterStatus( From, Event, To )
text = text .. string.format( ", Relay unit=%s (alive=%s)", tostring( self.relayunitname ), relayunitstatus )
end
self:T( self.lid .. text )
self:__Status( -60 )
if not self:Is("Stopped") then
self:__Status( -60 )
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1348,9 +1391,11 @@ function ATIS:onafterCheckQueue( From, Event, To )
end
end
-- Check back in 5 seconds.
self:__CheckQueue( -math.abs( self.dTQueueCheck ) )
if not self:Is("Stopped") then
-- Check back in 5 seconds.
self:__CheckQueue( -math.abs( self.dTQueueCheck ) )
end
end
--- Broadcast ATIS radio message.
@@ -1459,10 +1504,19 @@ function ATIS:onafterBroadcast( From, Event, To )
--------------
--- Runway ---
--------------
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
local runwayLanding, rwyLandingLeft
local runwayTakeoff, rwyTakeoffLeft
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
runwayLanding, rwyLandingLeft="PAD 01",false
runwayTakeoff, rwyTakeoffLeft="PAD 02",false
else
runwayLanding, rwyLandingLeft=self:GetActiveRunway()
runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
end
------------
--- Time ---
------------
@@ -1776,7 +1830,10 @@ function ATIS:onafterBroadcast( From, Event, To )
-- Airbase name
subtitle = string.format( "%s", self.airbasename )
if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
if (not self.ATISforFARPs) and self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil
and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil
and self.airbasename:find( "Field" ) == nil
then
subtitle = subtitle .. " Airport"
end
if not self.useSRS then
@@ -1851,8 +1908,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1811")
--self:I(alltext)
-- Visibility
if self.metric then
@@ -1870,8 +1925,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1830")
--self:I(alltext)
subtitle = ""
-- Weather phenomena
@@ -1973,10 +2026,8 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 1932")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
subtitle = ""
-- Temperature
if self.TDegF then
@@ -2005,9 +2056,7 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1962")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
-- Dew point
if self.TDegF then
@@ -2036,8 +2085,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1992")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
-- Altimeter QNH/QFE.
@@ -2103,69 +2150,72 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 2049")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
local _RUNACT
-- Active runway.
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
subtitle=subtitle.." Right"
end
local _RUNACT = subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
if not self.ATISforFARPs then
-- Active runway.
if runwayLanding then
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
subtitle=subtitle.." Right"
end
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
_RUNACT = subtitle
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
end
end
alltext = alltext .. ";\n" .. subtitle
end
end
-- Airfield elevation
if self.elevation then
@@ -2232,9 +2282,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- ILS
--self:I({ils=self.ils})
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
--self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft})
if ils then
subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency )
if not self.useSRS then
@@ -2249,7 +2297,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.MegaHertz, 0.2 )
end
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
end
-- Outer NDB
@@ -2385,6 +2432,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local text = string.gsub( text, "mmHg", "millimeters of Mercury" )
local text = string.gsub( text, "hPa", "hectopascals" )
local text = string.gsub( text, "m/s", "meters per second" )
local text = string.gsub( text, "TACAN", "tackan" )
local text = string.gsub( text, "FARP", "farp" )
-- Replace ";" by "."
local text = string.gsub( text, ";", " . " )
@@ -2396,7 +2445,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local duration = STTS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
--self.msrs:PlayText( text )
self.SRSText = text
end
end
@@ -2475,8 +2525,11 @@ function ATIS:GetActiveRunway(Takeoff)
else
runway=self.airbase:GetActiveRunwayLanding()
end
return runway.name, runway.isLeft
if runway then -- some ABs have NO runways, e.g. Syria Naqoura
return runway.name, runway.isLeft
else
return nil, nil
end
end
--- Get runway from user supplied magnetic heading.

View File

@@ -32,20 +32,21 @@
-- * [USS George Washington](https://en.wikipedia.org/wiki/USS_George_Washington_(CVN-73\)) (CVN-73) [Super Carrier Module]
-- * [USS Harry S. Truman](https://en.wikipedia.org/wiki/USS_Harry_S._Truman) (CVN-75) [Super Carrier Module]
-- * [USS Forrestal](https://en.wikipedia.org/wiki/USS_Forrestal_(CV-59\)) (CV-59) [Heatblur Carrier Module]
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12) [**WIP**]
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05) [**WIP**]
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1) [**WIP**]
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6) [**WIP**]
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61) [**WIP**]
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02) [**WIP**]
-- * [HMS Hermes](https://en.wikipedia.org/wiki/HMS_Hermes_(R12\)) (R12)
-- * [HMS Invincible](https://en.wikipedia.org/wiki/HMS_Invincible_(R05\)) (R05)
-- * [USS Tarawa](https://en.wikipedia.org/wiki/USS_Tarawa_(LHA-1\)) (LHA-1)
-- * [USS America](https://en.wikipedia.org/wiki/USS_America_(LHA-6\)) (LHA-6)
-- * [Juan Carlos I](https://en.wikipedia.org/wiki/Spanish_amphibious_assault_ship_Juan_Carlos_I) (L61)
-- * [HMAS Canberra](https://en.wikipedia.org/wiki/HMAS_Canberra_(L02\)) (L02)
--
-- **Supported Aircraft:**
--
-- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI)
-- * [F-14A/B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI)
-- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI)
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**]
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO)(Player & AI) [**WIP**]
-- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI)
-- * [T-45C Goshawk](https://www.vnao-cvw-7.com/t-45-goshawk) (VNAO mod) (Player & AI)
-- * [FE/A-18E/F/G Superhornet](https://forum.dcs.world/topic/316971-cjs-super-hornet-community-mod-v20-official-thread/) (CJS mod) (Player & AI)
-- * F/A-18C Hornet (AI)
-- * F-14A Tomcat (AI)
-- * E-2D Hawkeye (AI)
@@ -1278,7 +1279,10 @@ AIRBOSS = {
-- @field #string S3BTANKER Lockheed S-3B Viking tanker.
-- @field #string E2D Grumman E-2D Hawkeye AWACS.
-- @field #string C2A Grumman C-2A Greyhound from Military Aircraft Mod.
-- @field #string T45C T-45C by VNAO
-- @field #string T45C T-45C by VNAO.
-- @field #string RHINOE F/A-18E Superhornet (mod).
-- @field #string RHINOF F/A-18F Superhornet (mod).
-- @field #string GROWLER FEA-18G Superhornet (mod).
AIRBOSS.AircraftCarrier={
AV8B="AV8BNA",
HORNET="FA-18C_hornet",
@@ -1292,6 +1296,9 @@ AIRBOSS.AircraftCarrier={
S3BTANKER="S-3B Tanker",
E2D="E-2C",
C2A="C2A_Greyhound",
RHINOE="FA-18E",
RHINOF="FA-18F",
GROWLER="EA-18G",
}
--- Carrier types.
@@ -1302,7 +1309,7 @@ AIRBOSS.AircraftCarrier={
-- @field #string STENNIS USS John C. Stennis (CVN-74)
-- @field #string TRUMAN USS Harry S. Truman (CVN-75) [Super Carrier Module]
-- @field #string FORRESTAL USS Forrestal (CV-59) [Heatblur Carrier Module]
-- @field #string VINSON USS Carl Vinson (CVN-70) [Obsolete]
-- @field #string VINSON USS Carl Vinson (CVN-70) [Deprecated!]
-- @field #string HERMES HMS Hermes (R12) [V/STOL Carrier]
-- @field #string INVINCIBLE HMS Invincible (R05) [V/STOL Carrier]
-- @field #string TARAWA USS Tarawa (LHA-1) [V/STOL Carrier]
@@ -5177,7 +5184,10 @@ end
function AIRBOSS:_GetAircraftAoA( playerData )
-- Get AC type.
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local goshawk = playerData.actype == AIRBOSS.AircraftCarrier.T45C
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
@@ -5340,7 +5350,10 @@ function AIRBOSS:_GetAircraftParameters( playerData, step )
step = step or playerData.step
-- Get AC type.
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
local hornet = playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
local skyhawk = playerData.actype == AIRBOSS.AircraftCarrier.A4EC
local tomcat = playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B
local harrier = playerData.actype == AIRBOSS.AircraftCarrier.AV8B
@@ -5809,27 +5822,64 @@ function AIRBOSS:_ScanCarrierZone()
-- Get aircraft type name.
local actype = group:GetTypeName()
-- Create a new flight group
if knownflight then
-- Debug output.
self:T2(self.lid..string.format("Known flight group %s of type %s in CCA.", groupname, actype))
-- Check if flight is AI and if we want to handle it at all.
if knownflight.ai and knownflight.flag == -100 and self.handleai and false then --Disabled AI handling because of incorrect OPSGROUP reference!
if knownflight.ai and self.handleai then
local putintomarshal = false
-- Defines if AI group should be handled by the airboss.
local iscarriersquad=true
-- Get flight group.
local flight = _DATABASE:GetOpsGroup( groupname )
if flight and flight:IsInbound() and flight.destbase:GetName() == self.carrier:GetName() then
if flight.ishelo then
-- Check if AI group is part of the group set if a set was defined.
if self.squadsetAI then
local group=self.squadsetAI:FindGroup(groupname)
if group then
iscarriersquad=true
else
putintomarshal = true
iscarriersquad=false
end
flight.airboss = self
end
-- Send AI flight to marshal stack.
if putintomarshal then
-- Check if group was explicitly excluded.
if self.excludesetAI then
local group=self.excludesetAI:FindGroup(groupname)
if group then
iscarriersquad=false
end
end
-- Get distance to carrier.
local dist=knownflight.group:GetCoordinate():Get2DDistance(self:GetCoordinate())
-- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0.
local closein=knownflight.dist0-dist
-- Debug info.
self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein)))
-- Is this group the tanker?
local istanker=self.tanker and self.tanker.tanker:GetName()==groupname
-- Is this group the AWACS?
local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname
-- Send tanker to marshal stack?
local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true
-- Send AWACS to marhsal stack?
local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true
-- Put flight into Marshal.
local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and istanker==false and isawacs==false
-- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value.
if putintomarshal or tanker2marshal or awacs2marshal then
-- Get the next free stack for current recovery case.
local stack = self:_GetFreeStack( knownflight.ai )
@@ -6251,6 +6301,9 @@ function AIRBOSS:_RefuelAI( flight )
actype==AIRBOSS.AircraftCarrier.F14B or
actype==AIRBOSS.AircraftCarrier.F14A_AI or
actype==AIRBOSS.AircraftCarrier.HORNET or
actype==AIRBOSS.AircraftCarrier.RHINOE or
actype==AIRBOSS.AircraftCarrier.RHINOF or
actype==AIRBOSS.AircraftCarrier.GROWLER or
actype==AIRBOSS.AircraftCarrier.FA18C or
actype==AIRBOSS.AircraftCarrier.S3B or
actype==AIRBOSS.AircraftCarrier.S3BTANKER then
@@ -6348,7 +6401,11 @@ function AIRBOSS:_LandAI( flight )
-- Aircraft speed when flying the pattern.
local Speed = UTILS.KnotsToKmph( 200 )
if flight.actype == AIRBOSS.AircraftCarrier.HORNET or flight.actype == AIRBOSS.AircraftCarrier.FA18C then
if flight.actype == AIRBOSS.AircraftCarrier.HORNET
or flight.actype == AIRBOSS.AircraftCarrier.FA18C
or flight.actype == AIRBOSS.AircraftCarrier.RHINOE
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
Speed = UTILS.KnotsToKmph( 200 )
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
Speed = UTILS.KnotsToKmph( 150 )
@@ -8082,7 +8139,7 @@ end
-- @param Core.Event#EVENTDATA EventData
function AIRBOSS:OnEventBirth( EventData )
self:F3( { eventbirth = EventData } )
-- Nil checks.
if EventData == nil then
self:E( self.lid .. "ERROR: EventData=nil in event BIRTH!" )
@@ -8094,7 +8151,9 @@ function AIRBOSS:OnEventBirth( EventData )
self:E( EventData )
return
end
if EventData.IniObjectCategory ~= Object.Category.UNIT then return end
local _unitName = EventData.IniUnitName
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
@@ -9172,7 +9231,13 @@ function AIRBOSS:_DirtyUp( playerData )
self:_PlayerHint( playerData )
-- Radio call "Say/Fly needles". Delayed by 10/15 seconds.
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET or playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.F14A
or playerData.actype == AIRBOSS.AircraftCarrier.F14B
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER
then
local callsay = self:_NewRadioCall( self.MarshalCall.SAYNEEDLES, nil, nil, 5, playerData.onboard )
local callfly = self:_NewRadioCall( self.MarshalCall.FLYNEEDLES, nil, nil, 5, playerData.onboard )
self:RadioTransmission( self.MarshalRadio, callsay, false, 55, nil, true )
@@ -9766,23 +9831,23 @@ function AIRBOSS:_Groove( playerData )
end
end
-- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)--- Need to think more about this.
-- Long V/STOL groove time Wave Off over 75 seconds to IC - TOPGUN level Only. --pene testing (WIP)--- Need to think more about this.
--if rho>=RAR and rho<=RIC and not playerData.waveoff and playerData.difficulty==AIRBOSS.Difficulty.HARD and playerData.actype== AIRBOSS.AircraftCarrier.AV8B then
-- Get groove time
--local vSlow=groovedata.time
-- If too slow wave off.
--if vSlow >75 then
-- LSO Wave off!
--self:RadioTransmission(self.LSORadio, self.LSOCall.WAVEOFF, nil, nil, nil, true)
--playerData.Tlso=timer.getTime()
-- Player was waved Off
--playerData.waveoff=true
--return
--end
--end
--end
-- Groovedata step.
@@ -10136,7 +10201,7 @@ function AIRBOSS:_GetSternCoord()
elseif case==2 or case==1 then
-- V/Stol: Translate 8 meters port.
self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true)
end
end
elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then
-- Stennis: translate 7 meters starboard wrt Final bearing.
self.sterncoord:Translate( self.carrierparam.sterndist, hdg, true, true ):Translate( 7, FB + 90, true, true )
@@ -10154,6 +10219,28 @@ function AIRBOSS:_GetSternCoord()
return self.sterncoord
end
--- Get wire from draw argument.
-- @param #AIRBOSS self
-- @param Core.Point#COORDINATE Lcoord Landing position.
-- @return #number Trapped wire (1-4) or 99 if no wire was trapped.
function AIRBOSS:_GetWireFromDrawArg()
local wireArgs={}
wireArgs[1]=141
wireArgs[2]=142
wireArgs[3]=143
wireArgs[4]=144
for wire,drawArg in pairs(wireArgs) do
local value=self.carrier:GetDrawArgumentValue(drawArg)
if math.abs(value)>0.001 then
return wire
end
end
return 99
end
--- Get wire from landing position.
-- @param #AIRBOSS self
-- @param Core.Point#COORDINATE Lcoord Landing position.
@@ -10263,7 +10350,10 @@ function AIRBOSS:_Trapped( playerData )
-- Get current wire (estimate). This now based on the position where the player comes to a standstill which should reflect the trapped wire better.
local dcorr = 100
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
dcorr = 100
elseif playerData.actype == AIRBOSS.AircraftCarrier.F14A or playerData.actype == AIRBOSS.AircraftCarrier.F14B then
-- TODO: Check Tomcat.
@@ -11251,7 +11341,7 @@ function AIRBOSS:_GetOptLandingCoordinate()
self.landingcoord:SetAltitude(UTILS.FeetToMeters(120))
end
else
-- Ideally we want to land between 2nd and 3rd wire.
@@ -12463,7 +12553,10 @@ function AIRBOSS:_PlayerHint( playerData, delay, soundoff )
if playerData.step == AIRBOSS.PatternStep.BULLSEYE then
-- Hint follow the needles.
if playerData.difficulty == AIRBOSS.Difficulty.EASY then
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET then
if playerData.actype == AIRBOSS.AircraftCarrier.HORNET
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOE
or playerData.actype == AIRBOSS.AircraftCarrier.RHINOF
or playerData.actype == AIRBOSS.AircraftCarrier.GROWLER then
hint = hint .. string.format( "\nIntercept glideslope and follow the needles." )
else
hint = hint .. string.format( "\nIntercept glideslope." )
@@ -13957,6 +14050,10 @@ function AIRBOSS:_GetACNickname( actype )
nickname = "Tomcat"
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
nickname = "Hornet"
elseif actype == AIRBOSS.AircraftCarrier.RHINOE or actype == AIRBOSS.AircraftCarrier.RHINOF then
nickname = "Rhino"
elseif actype == AIRBOSS.AircraftCarrier.GROWLER then
nickname = "Growler"
elseif actype == AIRBOSS.AircraftCarrier.S3B or actype == AIRBOSS.AircraftCarrier.S3BTANKER then
nickname = "Viking"
end

View File

@@ -26,11 +26,11 @@
--
-- ===
--
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing)
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), The Chosen One (Persistence)
-- @module Ops.CSAR
-- @image OPS_CSAR.jpg
-- Date: October 2022
-- Date: May 2023
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -114,6 +114,7 @@
-- mycsar.countryneutral = country.id.UN_PEACEKEEPERS
-- mycsar.topmenuname = "CSAR" -- set the menu entry name
-- mycsar.ADFRadioPwr = 1000 -- ADF Beacons sending with 1KW as default
-- mycsar.PilotWeight = 80 -- Loaded pilots weigh 80kgs each
--
-- ## 2.1 Experimental Features
--
@@ -196,6 +197,26 @@
--
-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat
-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE)
--
-- ## 6. Save and load downed pilots - Persistance
--
-- You can save and later load back downed pilots to make your mission persistent.
-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts.
-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you.
--
-- Use the following options to manage your saves:
--
-- mycsar.enableLoadSave = true -- allow auto-saving and loading of files
-- mycsar.saveinterval = 600 -- save every 10 minutes
-- mycsar.filename = "missionsave.csv" -- example filename
-- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
--
-- Then use an initial load at the beginning of your mission:
--
-- mycsar:__Load(10)
--
-- **Caveat:**
-- Dropped troop noMessage and forcedesc parameters aren't saved.
--
-- @field #CSAR
CSAR = {
@@ -233,6 +254,7 @@ CSAR = {
allheligroupset = nil,
topmenuname = "CSAR",
ADFRadioPwr = 1000,
PilotWeight = 80,
}
--- Downed pilots info.
@@ -270,7 +292,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.15"
CSAR.version="1.0.18"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -278,7 +300,7 @@ CSAR.version="1.0.15"
-- DONE: SRS Integration (to be tested)
-- TODO: Maybe - add option to smoke/flare closest MASH
-- TODO: shagrat Add cargoWeight to helicopter when pilot boarded
-- DONE: shagrat Add cargoWeight to helicopter when pilot boarded
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -294,6 +316,8 @@ function CSAR:New(Coalition, Template, Alias)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #CSAR
BASE:T({Coalition, Template, Alias})
--set Coalition
if Coalition and type(Coalition)=="string" then
if Coalition=="blue" then
@@ -344,6 +368,8 @@ function CSAR:New(Coalition, Template, Alias)
self:AddTransition("*", "Returning", "*") -- CSAR able to return to base.
self:AddTransition("*", "Rescued", "*") -- Pilot at MASH.
self:AddTransition("*", "KIA", "*") -- Pilot killed in action.
self:AddTransition("*", "Load", "*") -- CSAR load event.
self:AddTransition("*", "Save", "*") -- CSAR save event.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
-- tables, mainly for tracking actions
@@ -418,10 +444,13 @@ function CSAR:New(Coalition, Template, Alias)
self.wetfeettemplate = nil
self.usewetfeet = false
-- added 0.1.8
-- added 1.0.15
self.allowbronco = false -- set to true to use the Bronco mod as a CSAR plane
self.ADFRadioPwr = 1000
-- added 1.0.16
self.PilotWeight = 80
-- WARNING - here\'ll be dragons
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
@@ -437,6 +466,14 @@ function CSAR:New(Coalition, Template, Alias)
self.SRSVolume = 1.0 -- volume 0.0 to 1.0
self.SRSGender = "male" -- male or female
local AliaS = string.gsub(self.alias," ","_")
self.filename = string.format("CSAR_%s_Persist.csv",AliaS)
-- load and save downed pilots
self.enableLoadSave = false
self.filepath = nil
self.saveinterval = 600
------------------------
--- Pseudo Functions ---
------------------------
@@ -466,6 +503,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @function [parent=#CSAR] __Status
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--
-- --- Triggers the FSM event "Load".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Load" after a delay.
-- @function [parent=#CSAR] __Load
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Save".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Save" after a delay.
-- @function [parent=#CSAR] __Save
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- On After "PilotDown" event. Downed Pilot detected.
-- @function [parent=#CSAR] OnAfterPilotDown
@@ -533,6 +588,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @param #string To To state.
-- @param #string Pilotname Name of the pilot KIA.
--- FSM Function OnAfterLoad.
-- @function [parent=#CSAR] OnAfterLoad
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
--- FSM Function OnAfterSave.
-- @function [parent=#CSAR] OnAfterSave
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
return self
end
@@ -845,7 +918,7 @@ end
--- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons.
-- @param #CSAR self
-- @param #string _Point a POINT_VEC2.
-- @param Core.Point#COORDINATE _Point
-- @param #number _coalition Coalition.
-- @param #string _description (optional) Description.
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
@@ -878,7 +951,7 @@ end
--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
-- @param #CSAR self
-- @param #string Point a POINT_VEC2.
-- @param Core.Point#COORDINATE Point
-- @param #number Coalition Coalition.
-- @param #string Description (optional) Description.
-- @param #boolean addBeacon (optional) yes or no.
@@ -888,8 +961,8 @@ end
-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names.
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so:
--
-- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition.
-- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE )
-- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition.
-- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE )
function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
return self
@@ -1397,7 +1470,7 @@ end
-- @return #CSAR self
function CSAR:_UpdateUnitCargoMass(_heliName)
self:T(self.lid .. " _UpdateUnitCargoMass")
local calculatedMass = self:_PilotsOnboard(_heliName)*80
local calculatedMass = self:_PilotsOnboard(_heliName)*(self.PilotWeight or 80)
local Unit = UNIT:FindByName(_heliName)
if Unit then
Unit:SetUnitInternalCargo(calculatedMass)
@@ -1807,7 +1880,7 @@ function CSAR:_SignalFlare(_unitName)
if _SETTINGS:IsImperial() then
_distance = string.format("%.1fnm",UTILS.MetersToNM(_closest.distance))
else
_distance = string.format("%.1fkm",_closest.distance)
_distance = string.format("%.1fkm",_closest.distance/1000)
end
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", self:_GetCustomCallSign(_unitName), _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
@@ -2102,10 +2175,12 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
if _group:IsAlive() then
local _radioUnit = _group:GetUnit(1)
if _radioUnit then
local name = _radioUnit:GetName()
local Frequency = _freq -- Freq in Hertz
local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
end
end
return self
@@ -2213,7 +2288,16 @@ function CSAR:onafterStart(From, Event, To)
self.msrs:SetLabel("CSAR")
self.SRSQueue = MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
@@ -2446,6 +2530,240 @@ function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase)
self:T({From, Event, To, HeliName, Airbase})
return self
end
--- On before "Save" event. Checks if io and lfs are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
-- Thanks to @FunkyFranky
-- Check io module is available.
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
--- On after "Save" event. Player data is saved to file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
-- @param #string filename (Optional) File name for saving. Default is Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
-- Thanks to @FunkyFranky
if not self.enableLoadSave then
return self
end
--- Function that saves data to file
local function _savefile(filename, data)
local f = assert(io.open(filename, "wb"))
f:write(data)
f:close()
end
-- Set path or default.
if lfs then
path=self.filepath or lfs.writedir()
end
-- Set file name.
filename=filename or self.filename
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
local pilots = self.downedPilots
--local data = "LoadedData = {\n"
local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
local n = 0
for _,_grp in pairs(pilots) do
local DownedPilot = _grp -- Wrapper.Group#GROUP
if DownedPilot and DownedPilot.alive then
-- get downed pilot data for saving
local playerName = DownedPilot.player
local group = DownedPilot.group
local coalition = group:GetCoalition()
local country = group:GetCountry()
local description = DownedPilot.desc
local typeName = DownedPilot.typename
local freq = DownedPilot.frequency
local location = group:GetVec3()
local unitName = DownedPilot.originalUnit
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: " .. txt)
data = data .. txt
end
end
_savefile(filename, data)
-- AutoSave
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
--- On before "Load" event. Checks if io and lfs and the file are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that check if a file exists.
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Check io module is available.
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename))
return false
--return self
end
end
--- On after "Load" event. Loads dropped units from file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that loads data from a file.
local function _loadfile(filename)
local f=assert(io.open(filename, "rb"))
local data=f:read("*all")
f:close()
return data
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Info message.
local text=string.format("Loading CSAR state from file %s", filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename, "rb"))
local loadeddata = {}
for line in file:lines() do
loadeddata[#loadeddata+1] = line
end
file:close()
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n
local playerName = dataset[1]
local vec3 = {}
vec3.x = tonumber(dataset[2])
vec3.y = tonumber(dataset[3])
vec3.z = tonumber(dataset[4])
local point = COORDINATE:NewFromVec3(vec3)
local coalition = tonumber(dataset[5])
local country = tonumber(dataset[6])
local description = dataset[7]
local typeName = dataset[8]
local unitName = dataset[9]
local freq = tonumber(dataset[10])
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
end
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- End Ops.CSAR
--------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -198,7 +198,7 @@
-- The first parameter *callsignname* defines the name (1=Texaco, 2=Arco, 3=Shell). The second (optional) parameter specifies the first number and has to be between 1-9.
-- Also see [DCS_enum_callsigns](https://wiki.hoggitworld.com/view/DCS_enum_callsigns) and [DCS_command_setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign).
--
-- TexacoStennis:SetCAllsign(CALLSIGN.Tanker.Arco)
-- TexacoStennis:SetCallsign(CALLSIGN.Tanker.Arco)
--
-- For convenience, MOOSE has a CALLSIGN enumerator introduced.
--

View File

@@ -805,7 +805,9 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
self:T(self.lid..text)
-- Get coordinate of unit.
local coord=unit:GetCoordinate()
--local coord=unit:GetCoordinate()
local Vec3 = EventData.IniDCSUnit:getPoint() -- Vec3
local coord = COORDINATE:NewFromVec3(Vec3)
if coord and self.rescuezone:IsCoordinateInZone(coord) then

View File

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

View File

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

View File

@@ -489,6 +489,7 @@ ENUMS.ReportingName =
--Mosquito = "A-20",
Skyhawk = "A-4E",
Viggen = "AJS37",
Harrier_B = "AV8BNA",
Harrier = "AV-8B",
Spirit = "B-2",
Aviojet = "C-101",

View File

@@ -44,21 +44,24 @@ SOCKET = {
}
--- Data type. This is the keyword the socket listener uses.
-- @type SOCKET.DataType
-- @field #string TEXT Plain text.
-- @field #string BOMBRESULT Range bombing.
-- @field #string STRAFERESULT Range strafeing result.
-- @field #string LSOGRADE Airboss LSO grade.
-- @field #string TTS Text-To-Speech.
SOCKET.DataType={
TEXT="moose_text",
BOMBRESULT="moose_bomb_result",
STRAFERESULT="moose_strafe_result",
LSOGRADE="moose_lso_grade",
TTS="moose_text2speech"
}
--- SOCKET class version.
-- @field #string version
SOCKET.version="0.2.0"
SOCKET.version="0.3.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -139,7 +142,7 @@ end
--- Send a text message.
-- @param #SOCKET self
-- @param #string Text Test message.
-- @param #string Text Text message.
-- @return #SOCKET self
function SOCKET:SendText(Text)
@@ -153,4 +156,32 @@ function SOCKET:SendText(Text)
return self
end
--- Send a text-to-speech message.
-- @param #SOCKET self
-- @param #string Text The text message to speek.
-- @param #number Provider The TTS provider: 0=Microsoft (default), 1=Google.
-- @param #string Voice The specific voice to use, e.g. `"Microsoft David Desktop"` or "`en-US-Standard-A`". If not set, the service will choose a voice based on the other parameters such as culture and gender.
-- @param #string Culture The Culture or language code, *e.g.* `"en-US"`.
-- @param #string Gender The Gender, *i.e.* "male", "female". Default "female".
-- @param #number Volume The volume. Microsoft: [0,100] default 50, Google: [-96, 10] default 0.
-- @return #SOCKET self
function SOCKET:SendTextToSpeech(Text, Provider, Voice, Culture, Gender, Volume)
Text=Text or "Hello World!"
local message={}
message.command = SOCKET.DataType.TTS
message.text = Text
message.provider=Provider
message.voice = Voice
message.culture = Culture
message.gender = Gender
message.volume = Volume
self:SendTable(message)
return self
end

View File

@@ -43,7 +43,7 @@ BIGSMOKEPRESET = {
HugeSmoke=8,
}
--- DCS map as returned by env.mission.theatre.
--- DCS map as returned by `env.mission.theatre`.
-- @type DCSMAP
-- @field #string Caucasus Caucasus map.
-- @field #string Normandy Normandy map.
@@ -53,6 +53,7 @@ BIGSMOKEPRESET = {
-- @field #string Syria Syria map.
-- @field #string MarianaIslands Mariana Islands map.
-- @field #string Falklands South Atlantic map.
-- @field #string Sinai Sinai map.
DCSMAP = {
Caucasus="Caucasus",
NTTR="Nevada",
@@ -62,6 +63,7 @@ DCSMAP = {
Syria="Syria",
MarianaIslands="MarianaIslands",
Falklands="Falklands",
Sinai="SinaiMap"
}
@@ -297,14 +299,14 @@ end
-- @param #table tbl Input table.
UTILS.OneLineSerialize = function( tbl ) -- serialization of a table all on a single line, no comments, made to replace old get_table_string function
lookup_table = {}
local lookup_table = {}
local function _Serialize( tbl )
if type(tbl) == 'table' then --function only works for tables!
if lookup_table[tbl] then
return lookup_table[object]
return lookup_table[tbl]
end
local tbl_str = {}
@@ -385,6 +387,23 @@ UTILS.BasicSerialize = function(s)
end
end
function UTILS.PrintTableToLog(table, indent)
if not table then
BASE:E("No table passed!")
return
end
if not indent then indent = 0 end
for k, v in pairs(table) do
if type(v) == "table" then
BASE:I(string.rep(" ", indent) .. tostring(k) .. " = {")
UTILS.PrintTableToLog(v, indent + 1)
BASE:I(string.rep(" ", indent) .. "}")
else
BASE:I(string.rep(" ", indent) .. tostring(k) .. " = " .. tostring(v))
end
end
end
UTILS.ToDegree = function(angle)
return angle*180/math.pi
@@ -489,6 +508,31 @@ UTILS.hPa2inHg = function( hPa )
return hPa * 0.0295299830714
end
--- Convert indicated airspeed (IAS) to true airspeed (TAS) for a given altitude above main sea level.
-- The conversion is based on the approximation that TAS is ~2% higher than IAS with every 1000 ft altitude above sea level.
-- @param #number ias Indicated air speed in any unit (m/s, km/h, knots, ...)
-- @param #number altitude Altitude above main sea level in meters.
-- @param #number oatcorr (Optional) Outside air temperature correction factor. Default 0.017.
-- @return #number True airspeed in the same unit the IAS has been given.
UTILS.IasToTas = function( ias, altitude, oatcorr )
oatcorr=oatcorr or 0.017
local tas=ias + (ias * oatcorr * UTILS.MetersToFeet(altitude) / 1000)
return tas
end
--- Convert true airspeed (TAS) to indicated airspeed (IAS) for a given altitude above main sea level.
-- The conversion is based on the approximation that TAS is ~2% higher than IAS with every 1000 ft altitude above sea level.
-- @param #number tas True air speed in any unit (m/s, km/h, knots, ...)
-- @param #number altitude Altitude above main sea level in meters.
-- @param #number oatcorr (Optional) Outside air temperature correction factor. Default 0.017.
-- @return #number Indicated airspeed in the same unit the TAS has been given.
UTILS.TasToIas = function( tas, altitude, oatcorr )
oatcorr=oatcorr or 0.017
local ias=tas/(1+oatcorr*UTILS.MetersToFeet(altitude)/1000)
return ias
end
--- Convert knots to altitude corrected KIAS, e.g. for tankers.
-- @param #number knots Speed in knots.
-- @param #number altitude Altitude in feet
@@ -606,10 +650,12 @@ end
-- acc- the accuracy of each easting/northing. 0, 1, 2, 3, 4, or 5.
UTILS.tostringMGRS = function(MGRS, acc) --R2.1
if acc == 0 then
if acc <= 0 then
return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
else
if acc > 5 then acc = 5 end
-- Test if Easting/Northing have less than 4 digits.
--MGRS.Easting=123 -- should be 00123
--MGRS.Northing=5432 -- should be 05432
@@ -640,7 +686,10 @@ function UTILS.Round( num, idp )
return math.floor( num * mult + 0.5 ) / mult
end
-- porting in Slmod's dostring
--- Porting in Slmod's dostring - execute a string as LUA code with error handling.
-- @param #string s The code as string to be executed
-- @return #boolean success If true, code was successfully executed, else false
-- @return #string Outcome Code outcome if successful or error string if not successful
function UTILS.DoString( s )
local f, err = loadstring( s )
if f then
@@ -650,7 +699,15 @@ function UTILS.DoString( s )
end
end
-- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order.
--- Here is a customized version of pairs, which I called spairs because it iterates over the table in a sorted order.
-- @param #table t The table
-- @param #string order (Optional) The sorting function
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.spairs(mytable) do
-- -- your code here
-- end
function UTILS.spairs( t, order )
-- collect the keys
local keys = {}
@@ -675,7 +732,16 @@ function UTILS.spairs( t, order )
end
-- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first.
--- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first.
-- @param #table t The table
-- @param #string getkey The function to determine the keys for sorting
-- @param #string order (Optional) The sorting function itself
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.kpairs(mytable, getkeyfunc) do
-- -- your code here
-- end
function UTILS.kpairs( t, getkey, order )
-- collect the keys
local keys = {}
@@ -700,7 +766,14 @@ function UTILS.kpairs( t, getkey, order )
end
end
-- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order.
--- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order.
-- @param #table t The table
-- @return #string key The index key
-- @return #string value The value at the indexed key
-- @usage
-- for key,value in UTILS.rpairs(mytable) do
-- -- your code here
-- end
function UTILS.rpairs( t )
-- collect the keys
@@ -1059,8 +1132,8 @@ function UTILS.Vec2Norm(a)
end
--- Calculate the distance between two 2D vectors.
-- @param DCS#Vec2 a Vector in 3D with x, y components.
-- @param DCS#Vec2 b Vector in 3D with x, y components.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param DCS#Vec2 b Vector in 2D with x, y components.
-- @return #number Distance between the vectors.
function UTILS.VecDist2D(a, b)
@@ -1195,6 +1268,20 @@ function UTILS.HdgDiff(h1, h2)
return math.abs(delta)
end
--- Returns the heading from one vec3 to another vec3.
-- @param DCS#Vec3 a From vec3.
-- @param DCS#Vec3 b To vec3.
-- @return #number Heading in degrees.
function UTILS.HdgTo(a, b)
local dz=b.z-a.z
local dx=b.x-a.x
local heading=math.deg(math.atan2(dz, dx))
if heading < 0 then
heading = 360 + heading
end
return heading
end
--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
@@ -1305,7 +1392,7 @@ function UTILS.TACANToFrequency(TACANChannel, TACANMode)
end
--- Returns the DCS map/theatre as optained by env.mission.theatre
--- Returns the DCS map/theatre as optained by `env.mission.theatre`.
-- @return #string DCS map name.
function UTILS.GetDCSMap()
return env.mission.theatre
@@ -1361,6 +1448,7 @@ end
-- * Syria +5 (East)
-- * Mariana Islands +2 (East)
-- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole
-- * Sinai +4.8 (East)
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
-- @return #number Declination in degrees.
function UTILS.GetMagneticDeclination(map)
@@ -1385,6 +1473,8 @@ function UTILS.GetMagneticDeclination(map)
declination=2
elseif map==DCSMAP.Falklands then
declination=12
elseif map==DCSMAP.Sinai then
declination=4.8
else
declination=0
end
@@ -1444,6 +1534,30 @@ function UTILS.GetCoalitionName(Coalition)
end
--- Get the enemy coalition for a given coalition.
-- @param #number Coalition The coalition ID.
-- @param #boolean Neutral Include neutral as enemy.
-- @return #table Enemy coalition table.
function UTILS.GetCoalitionEnemy(Coalition, Neutral)
local Coalitions={}
if Coalition then
if Coalition==coalition.side.RED then
Coalitions={coalition.side.BLUE}
elseif Coalition==coalition.side.BLUE then
Coalitions={coalition.side.RED}
elseif Coalition==coalition.side.NEUTRAL then
Coalitions={coalition.side.RED, coalition.side.BLUE}
end
end
if Neutral then
table.insert(Coalitions, coalition.side.NEUTRAL)
end
return Coalitions
end
--- Get the modulation name from its numerical value.
-- @param #number Modulation The modulation enumerator number. Can be either 0 or 1.
-- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown".
@@ -1574,6 +1688,10 @@ function UTILS.GMTToLocalTimeDifference()
return 3 -- Damascus is UTC+3 hours
elseif theatre==DCSMAP.MarianaIslands then
return 10 -- Guam is UTC+10 hours.
elseif theatre==DCSMAP.Falklands then
return -3 -- Fireland is UTC-3 hours.
elseif theatre==DCSMAP.Sinai then
return 2 -- Currently map is +2 but should be +3 (DCS bug?)
else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0
@@ -1905,7 +2023,7 @@ function UTILS.GenerateVHFrequencies()
705,720,722,730,735,740,745,750,770,795,
822,830,862,866,
905,907,920,935,942,950,995,
1000,1025,1030,1050,1065,1116,1175,1182,1210
1000,1025,1030,1050,1065,1116,1175,1182,1210,1215
}
local FreeVHFFrequencies = {}
@@ -1973,7 +2091,9 @@ function UTILS.GenerateUHFrequencies()
local _start = 220000000
while _start < 399000000 do
table.insert(FreeUHFFrequencies, _start)
if _start ~= 243000000 then
table.insert(FreeUHFFrequencies, _start)
end
_start = _start + 500000
end
@@ -2173,10 +2293,29 @@ function UTILS.CheckFileExists(Path,Filename)
end
end
--- Function to obtain a table of typenames from the group given with the number of units of the same type in the group.
-- @param Wrapper.Group#GROUP Group The group to list
-- @return #table Table of typnames and typename counts, e.g. `{["KAMAZ Truck"]=3,["ATZ-5"]=1}`
function UTILS.GetCountPerTypeName(Group)
local units = Group:GetUnits()
local TypeNameTable = {}
for _,_unt in pairs (units) do
local unit = _unt -- Wrapper.Unit#UNIT
local typen = unit:GetTypeName()
if not TypeNameTable[typen] then
TypeNameTable[typen] = 1
else
TypeNameTable[typen] = TypeNameTable[typen] + 1
end
end
return TypeNameTable
end
--- Function to save the state of a list of groups found by name
-- @param #table List Table of strings with groupnames
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
-- @return #boolean outcome True if saving is successful, else false.
-- @usage
-- We will go through the list and find the corresponding group and save the current group size (0 when dead).
@@ -2184,7 +2323,7 @@ end
-- Position is still saved for your usage.
-- The idea is to reduce the number of units when reloading the data again to restart the saved mission.
-- The data will be a simple comma separated list of groupname and size, with one header line.
function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
local filename = Filename or "StateListofGroups"
local data = "--Save Stationary List of Groups: "..Filename .."\n"
for _,_group in pairs (List) do
@@ -2192,7 +2331,16 @@ function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
if group and group:IsAlive() then
local units = group:CountAliveUnits()
local position = group:GetVec3()
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
if Structured then
local structure = UTILS.GetCountPerTypeName(group)
local strucdata = ""
for typen,anzahl in pairs (structure) do
strucdata = strucdata .. typen .. "=="..anzahl..";"
end
data = string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
else
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
end
else
data = string.format("%s%s,0,0,0,0\n",data,_group)
end
@@ -2206,6 +2354,7 @@ end
-- @param Core.Set#SET_BASE Set of objects to save
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
-- @return #boolean outcome True if saving is successful, else false.
-- @usage
-- We will go through the set and find the corresponding group and save the current group size and current position.
@@ -2215,7 +2364,7 @@ end
-- **Note** Do NOT use dashes or hashes in group template names (-,#)!
-- The data will be a simple comma separated list of groupname and size, with one header line.
-- The current task/waypoint/etc cannot be restored.
function UTILS.SaveSetOfGroups(Set,Path,Filename)
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
local filename = Filename or "SetOfGroups"
local data = "--Save SET of groups: "..Filename .."\n"
local List = Set:GetSetObjects()
@@ -2229,7 +2378,16 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename)
end
local units = group:CountAliveUnits()
local position = group:GetVec3()
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
if Structured then
local structure = UTILS.GetCountPerTypeName(group)
local strucdata = ""
for typen,anzahl in pairs (structure) do
strucdata = strucdata .. typen .. "=="..anzahl..";"
end
data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
else
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
end
end
end
-- save the data
@@ -2293,8 +2451,41 @@ end
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number.
-- @param #boolean Structured (Optional, needs Reduce = true) If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
local fires = {}
local function Smokers(name,coord,effect,density)
local eff = math.random(8)
if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units = group:GetUnits()
local reduced = 0
for _,_unit in pairs (units) do
local typo = _unit:GetTypeName()
if typename == typo then
if Cinematic then
local coordinate = _unit:GetCoordinate()
local name = _unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced = reduced + 1
if reduced == anzahl then break end
end
end
end
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofGroups"
@@ -2311,18 +2502,48 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
local posx = tonumber(dataset[3])
local posy = tonumber(dataset[4])
local posz = tonumber(dataset[5])
local structure = dataset[6]
--BASE:I({structure})
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) }
if reduce then
local actualgroup = GROUP:FindByName(groupname)
if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then
local reduction = actualgroup:CountAliveUnits() - size
BASE:I("Reducing groupsize by ".. reduction .. " units!")
-- reduce existing group
local units = actualgroup:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
if Structured and structure then
--BASE:I("Reducing group structure!")
local loadedstructure = {}
local strcset = UTILS.Split(structure,";")
for _,_data in pairs(strcset) do
local datasplit = UTILS.Split(_data,"==")
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
end
--BASE:I({loadedstructure})
local originalstructure = UTILS.GetCountPerTypeName(actualgroup)
--BASE:I({originalstructure})
for _name,_number in pairs(originalstructure) do
local loadednumber = 0
if loadedstructure[_name] then
loadednumber = loadedstructure[_name]
end
local reduce = false
if loadednumber < _number then reduce = true end
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
if reduce then
Cruncher(actualgroup,_name,_number-loadednumber)
end
end
else
local reduction = actualgroup:CountAliveUnits() - size
--BASE:I("Reducing groupsize by ".. reduction .. " units!")
-- reduce existing group
local units = actualgroup:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
@@ -2331,22 +2552,121 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
else
return nil
end
return datatable
return datatable,fires
end
--- Load back a SET of groups from file.
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size.
-- @param #boolean Structured (Optional, needs Spawn=true)If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return Core.Set#SET_GROUP Set of GROUP objects.
-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }`
function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
local fires = {}
local usedtemplates = {}
local spawn = true
if Spawn == false then spawn = false end
BASE:I("Spawn = "..tostring(spawn))
local filename = Filename or "SetOfGroups"
local setdata = SET_GROUP:New()
local datatable = {}
local function Smokers(name,coord,effect,density)
local eff = math.random(8)
if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name)
end
local function Cruncher(group,typename,anzahl)
local units = group:GetUnits()
local reduced = 0
for _,_unit in pairs (units) do
local typo = _unit:GetTypeName()
if typename == typo then
if Cinematic then
local coordinate = _unit:GetCoordinate()
local name = _unit:GetName()
Smokers(name,coordinate,Effect,Density)
end
_unit:Destroy(false)
reduced = reduced + 1
if reduced == anzahl then break end
end
end
end
local function PostSpawn(args)
local spwndgrp = args[1]
local size = args[2]
local structure = args[3]
setdata:AddObject(spwndgrp)
local actualsize = spwndgrp:CountAliveUnits()
if actualsize > size then
if Structured and structure then
local loadedstructure = {}
local strcset = UTILS.Split(structure,";")
for _,_data in pairs(strcset) do
local datasplit = UTILS.Split(_data,"==")
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
end
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
for _name,_number in pairs(originalstructure) do
local loadednumber = 0
if loadedstructure[_name] then
loadednumber = loadedstructure[_name]
end
local reduce = false
if loadednumber < _number then reduce = true end
if reduce then
Cruncher(spwndgrp,_name,_number-loadednumber)
end
end
else
local reduction = actualsize-size
-- reduce existing group
local units = spwndgrp:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
end
local function MultiUse(Data)
local template = Data.template
if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then
-- multispawn
if not usedtemplates[template].done then
local spwnd = 0
local spawngrp = SPAWN:New(template)
spawngrp:InitLimit(0,usedtemplates[template].used)
for _,_entry in pairs(usedtemplates[template].data) do
spwnd = spwnd + 1
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
usedtemplates[template].done = true
end
return true
else
return false
end
end
--BASE:I("Spawn = "..tostring(spawn))
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
@@ -2360,36 +2680,37 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
local posx = tonumber(dataset[4])
local posy = tonumber(dataset[5])
local posz = tonumber(dataset[6])
local structure = dataset[7]
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local group=nil
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template }
table.insert(datatable,data)
if spawn then
local group = SPAWN:New(template)
:InitDelayOff()
:OnSpawnGroup(
function(spwndgrp)
setdata:AddObject(spwndgrp)
local actualsize = spwndgrp:CountAliveUnits()
if actualsize > size then
local reduction = actualsize-size
-- reduce existing group
local units = spwndgrp:GetUnits()
local units2 = UTILS.ShuffleTable(units) -- randomize table
for i=1,reduction do
units2[i]:Destroy(false)
end
end
end
)
:SpawnFromCoordinate(coordinate)
if size > 0 then
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure }
table.insert(datatable,data)
if usedtemplates[template] then
usedtemplates[template].used = usedtemplates[template].used + 1
table.insert(usedtemplates[template].data,data)
else
usedtemplates[template] = {
data = {},
used = 1,
done = false,
}
table.insert(usedtemplates[template].data,data)
end
end
end
for _id,_entry in pairs (datatable) do
if spawn and not MultiUse(_entry) and _entry.size > 0 then
local group = SPAWN:New(_entry.template)
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end
end
else
return nil
end
if spawn then
return setdata
return setdata,fires
else
return datatable
end
@@ -2408,13 +2729,11 @@ function UTILS.LoadSetOfStatics(Path,Filename)
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- staticname,position.x,position.y,position.z
local staticname = dataset[1]
local posx = tonumber(dataset[2])
local posy = tonumber(dataset[3])
local posz = tonumber(dataset[4])
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
datatable:AddObject(STATIC:FindByName(staticname,false))
local StaticObject = STATIC:FindByName(staticname,false)
if StaticObject then
datatable:AddObject(StaticObject)
end
end
else
return nil
@@ -2426,9 +2745,15 @@ end
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #boolean Reduce If false, do not destroy the units with size=0.
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object.
-- @param #boolean Dead (Optional, needs Reduce = true) If Dead is true, re-spawn the dead object as dead and do not just delete it.
-- @param #boolean Cinematic (Optional, needs Dead = true) If true, place a fire/smoke effect on the dead static position.
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}`
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
-- Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
local fires = {}
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofStatics"
@@ -2451,14 +2776,31 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
if size==0 and reduce then
local static = STATIC:FindByName(staticname,false)
if static then
static:Destroy(false)
if Dead then
local deadobject = SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
deadobject:InitDead(true)
local heading = static:GetHeading()
local coord = static:GetCoordinate()
static:Destroy(false)
deadobject:SpawnFromCoordinate(coord,heading,staticname)
if Cinematic then
local effect = math.random(8)
if type(Effect) == "number" then
effect = Effect
end
coord:BigSmokeAndFire(effect,Density,staticname)
table.insert(fires,staticname)
end
else
static:Destroy(false)
end
end
end
end
else
return nil
end
return datatable
return datatable,fires
end
--- Heading Degrees (0-360) to Cardinal
@@ -2483,7 +2825,7 @@ end
-- @return #string Formatted BRAA NATO call
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
local BRAANATO = "Merged."
local GroupNumber = FromGrp:GetSize()
local GroupNumber = ToGrp:GetSize()
local GroupWords = "Singleton"
if GroupNumber == 2 then GroupWords = "Two-Ship"
elseif GroupNumber >= 3 then GroupWords = "Heavy"
@@ -2507,3 +2849,93 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
end
return BRAANATO
end
--- Check if an object is contained in a table.
-- @param #table Table The table.
-- @param #table Object The object to check.
-- @param #string Key (Optional) Key to check. By default, the object itself is checked.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsInTable(Table, Object, Key)
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
return false
end
--- Check if any object of multiple given objects is contained in a table.
-- @param #table Table The table.
-- @param #table Objects The objects to check.
-- @param #string Key (Optional) Key to check.
-- @return #booolen Returns `true` if object is in table.
function UTILS.IsAnyInTable(Table, Objects, Key)
for _,Object in pairs(UTILS.EnsureTable(Objects)) do
for key, object in pairs(Table) do
if Key then
if Object[Key]==object[Key] then
return true
end
else
if object==Object then
return true
end
end
end
end
return false
end
--- Helper function to plot a racetrack on the F10 Map - curtesy of Buur.
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Altitude Altitude in feet
-- @param #number Speed Speed in knots
-- @param #number Heading Heading in degrees
-- @param #number Leg Leg in NM
-- @param #number Coalition Coalition side, e.g. coaltion.side.RED or coaltion.side.BLUE
-- @param #table Color Color of the line in RGB, e.g. {1,0,0} for red
-- @param #number Alpha Transparency factor, between 0.1 and 1
-- @param #number LineType Line type to be used, line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
-- @param #boolean ReadOnly
function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalition, Color, Alpha, LineType, ReadOnly)
local fix_coordinate = Coordinate
local altitude = Altitude
local speed = Speed or 350
local heading = Heading or 270
local leg_distance = Leg or 10
local coalition = Coalition or -1
local color = Color or {1,0,0}
local alpha = Alpha or 1
local lineType = LineType or 1
speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr)
local turn_radius = 0.0211 * speed -3.01
local point_two = fix_coordinate:Translate(UTILS.NMToMeters(leg_distance), heading, true, false)
local point_three = point_two:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
local point_four = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
local circle_center_fix_four = point_two:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
local circle_center_two_three = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
fix_coordinate:LineToAll(point_two, coalition, color, alpha, lineType)
point_four:LineToAll(point_three, coalition, color, alpha, lineType)
circle_center_fix_four:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text)
circle_center_two_three:CircleToAll(UTILS.NMToMeters(turn_radius), coalition, color, alpha, nil, 0, lineType)--, ReadOnly, Text)
end

View File

@@ -11,8 +11,8 @@
-- @module Wrapper.Airbase
-- @image Wrapper_Airbase.JPG
--- @type AIRBASE
---
-- @type AIRBASE
-- @field #string ClassName Name of the class, i.e. "AIRBASE".
-- @field #table CategoryName Names of airbase categories.
-- @field #string AirbaseName Name of the airbase.
@@ -198,8 +198,47 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Needs_Oar_Point
-- * AIRBASE.Normandy.Funtington
-- * AIRBASE.Normandy.Tangmere
-- * AIRBASE.Normandy.Ford_AF
--
-- * AIRBASE.Normandy.Ford
-- * AIRBASE.Normandy.Argentan
-- * AIRBASE.Normandy.Goulet
-- * AIRBASE.Normandy.Barville
-- * AIRBASE.Normandy.Essay
-- * AIRBASE.Normandy.Hauterive
-- * AIRBASE.Normandy.Lymington
-- * AIRBASE.Normandy.Vrigny
-- * AIRBASE.Normandy.Odiham
-- * AIRBASE.Normandy.Conches
-- * AIRBASE.Normandy.West_Malling
-- * AIRBASE.Normandy.Villacoublay
-- * AIRBASE.Normandy.Kenley
-- * AIRBASE.Normandy.Beauvais_Tille
-- * AIRBASE.Normandy.Cormeilles_en_Vexin
-- * AIRBASE.Normandy.Creil
-- * AIRBASE.Normandy.Guyancourt
-- * AIRBASE.Normandy.Lonrai
-- * AIRBASE.Normandy.Dinan_Trelivan
-- * AIRBASE.Normandy.Heathrow
-- * AIRBASE.Normandy.Fecamp_Benouville
-- * AIRBASE.Normandy.Farnborough
-- * AIRBASE.Normandy.Friston
-- * AIRBASE.Normandy.Deanland
-- * AIRBASE.Normandy.Triqueville
-- * AIRBASE.Normandy.Poix
-- * AIRBASE.Normandy.Orly
-- * AIRBASE.Normandy.Stoney_Cross
-- * AIRBASE.Normandy.Amiens_Glisy
-- * AIRBASE.Normandy.Ronai
-- * AIRBASE.Normandy.Rouen_Boos
-- * AIRBASE.Normandy.Deauville
-- * AIRBASE.Normandy.Saint_Aubin
-- * AIRBASE.Normandy.Flers
-- * AIRBASE.Normandy.Avranches_Le_Val_Saint_Pere
-- * AIRBASE.Normandy.Gravesend
-- * AIRBASE.Normandy.Beaumont_le_Roger
-- * AIRBASE.Normandy.Broglie
-- * AIRBASE.Normandy.Bernay_Saint_Martin
-- * AIRBASE.Normandy.Saint_Andre_de_lEure
--
-- @field Normandy
AIRBASE.Normandy = {
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
@@ -232,21 +271,53 @@ AIRBASE.Normandy = {
["Needs_Oar_Point"] = "Needs Oar Point",
["Funtington"] = "Funtington",
["Tangmere"] = "Tangmere",
["Ford_AF"] = "Ford_AF",
["Goulet"] = "Goulet",
["Ford"] = "Ford",
["Argentan"] = "Argentan",
["Vrigny"] = "Vrigny",
["Goulet"] = "Goulet",
["Barville"] = "Barville",
["Essay"] = "Essay",
["Hauterive"] = "Hauterive",
["Barville"] = "Barville",
["Lymington"] = "Lymington",
["Vrigny"] = "Vrigny",
["Odiham"] = "Odiham",
["Conches"] = "Conches",
["West_Malling"] = "West Malling",
["Villacoublay"] = "Villacoublay",
["Kenley"] = "Kenley",
["Beauvais_Tille"] = "Beauvais-Tille",
["Cormeilles_en_Vexin"] = "Cormeilles-en-Vexin",
["Creil"] = "Creil",
["Guyancourt"] = "Guyancourt",
["Lonrai"] = "Lonrai",
["Dinan_Trelivan"] = "Dinan-Trelivan",
["Heathrow"] = "Heathrow",
["Fecamp_Benouville"] = "Fecamp-Benouville",
["Farnborough"] = "Farnborough",
["Friston"] = "Friston",
["Deanland "] = "Deanland ",
["Triqueville"] = "Triqueville",
["Poix"] = "Poix",
["Orly"] = "Orly",
["Stoney_Cross"] = "Stoney Cross",
["Amiens_Glisy"] = "Amiens-Glisy",
["Ronai"] = "Ronai",
["Rouen_Boos"] = "Rouen-Boos",
["Deauville"] = "Deauville",
["Saint_Aubin"] = "Saint-Aubin",
["Flers"] = "Flers",
["Avranches_Le_Val_Saint_Pere"] = "Avranches Le Val-Saint-Pere",
["Gravesend"] = "Gravesend",
["Beaumont_le_Roger"] = "Beaumont-le-Roger",
["Broglie"] = "Broglie",
["Bernay_Saint_Martin"] = "Bernay Saint Martin",
["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure",
}
--- Airbases of the Persion Gulf Map:
--
-- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport
-- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport
-- * AIRBASE.PersianGulf.Al-Bateen_Airport
-- * AIRBASE.PersianGulf.Al_Bateen_Airport
-- * AIRBASE.PersianGulf.Al_Ain_International_Airport
-- * AIRBASE.PersianGulf.Al_Dhafra_AB
-- * AIRBASE.PersianGulf.Al_Maktoum_Intl
@@ -265,7 +336,7 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Lavan_Island_Airport
-- * AIRBASE.PersianGulf.Liwa_Airbase
-- * AIRBASE.PersianGulf.Qeshm_Island
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah_International_Airport
-- * AIRBASE.PersianGulf.Ras_Al_Khaimah
-- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport
-- * AIRBASE.PersianGulf.Sharjah_Intl
-- * AIRBASE.PersianGulf.Shiraz_International_Airport
@@ -355,7 +426,7 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Al_Dumayr
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Hatay
-- * AIRBASE.Syria.Nicosia
-- * AIRBASE.Syria.Nicosia [Deactivated by ED as of June/2023]
-- * AIRBASE.Syria.Pinarbashi
-- * AIRBASE.Syria.Paphos
-- * AIRBASE.Syria.Kingsfield
@@ -396,8 +467,8 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.H3_Northwest
-- * AIRBASE.Syria.H3_Southwest
-- * AIRBASE.Syria.Kharab_Ishk
-- * AIRBASE.Syria.Raj_al_Issa_East
-- * AIRBASE.Syria.Raj_al_Issa_West
-- * AIRBASE.Syria.Raj_al_Issa_East (deleted by ED)
-- * AIRBASE.Syria.Raj_al_Issa_West (deleted by ED)
-- * AIRBASE.Syria.Ruwayshid
-- * AIRBASE.Syria.Sanliurfa
-- * AIRBASE.Syria.Tal_Siman
@@ -419,9 +490,8 @@ AIRBASE.Syria={
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Al_Dumayr"]="Al-Dumayr",
["Gazipasa"]="Gazipasa",
--["Ru_Convoy_4"]="Ru Convoy-4",
["Hatay"]="Hatay",
["Nicosia"]="Nicosia",
--["Nicosia"]="Nicosia",
["Pinarbashi"]="Pinarbashi",
["Paphos"]="Paphos",
["Kingsfield"]="Kingsfield",
@@ -462,8 +532,8 @@ AIRBASE.Syria={
["H3_Northwest"]="H3 Northwest",
["H3_Southwest"]="H3 Southwest",
["Kharab_Ishk"]="Kharab Ishk",
["Raj_al_Issa_East"]="Raj al Issa East",
["Raj_al_Issa_West"]="Raj al Issa West",
-- ["Raj_al_Issa_East"]="Raj al Issa East",
-- ["Raj_al_Issa_West"]="Raj al Issa West",
["Ruwayshid"]="Ruwayshid",
["Sanliurfa"]="Sanliurfa",
["Tal_Siman"]="Tal Siman",
@@ -478,6 +548,8 @@ AIRBASE.Syria={
-- * AIRBASE.MarianaIslands.Saipan_Intl
-- * AIRBASE.MarianaIslands.Tinian_Intl
-- * AIRBASE.MarianaIslands.Olf_Orote
-- * AIRBASE.MarianaIslands.Pagan_Airstrip
-- * AIRBASE.MarianaIslands.North_West_Field
--
-- @field MarianaIslands
AIRBASE.MarianaIslands = {
@@ -487,6 +559,8 @@ AIRBASE.MarianaIslands = {
["Saipan_Intl"] = "Saipan Intl",
["Tinian_Intl"] = "Tinian Intl",
["Olf_Orote"] = "Olf Orote",
["Pagan_Airstrip"] = "Pagan Airstrip",
["North_West_Field"] = "North West Field",
}
--- Airbases of the South Atlantic map:
@@ -504,6 +578,17 @@ AIRBASE.MarianaIslands = {
-- * AIRBASE.SouthAtlantic.Puerto_Williams
-- * AIRBASE.SouthAtlantic.Puerto_Natales
-- * AIRBASE.SouthAtlantic.El_Calafate
-- * AIRBASE.SouthAtlantic.Puerto_Santa_Cruz
-- * AIRBASE.SouthAtlantic.Comandante_Luis_Piedrabuena
-- * AIRBASE.SouthAtlantic.Aerodromo_De_Tolhuin
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
-- * AIRBASE.SouthAtlantic.Rio_Turbio
-- * AIRBASE.SouthAtlantic.Rio_Chico
-- * AIRBASE.SouthAtlantic.Franco_Bianco
-- * AIRBASE.SouthAtlantic.Goose_Green
-- * AIRBASE.SouthAtlantic.Hipico
-- * AIRBASE.SouthAtlantic.CaletaTortel
--
--@field MarianaIslands
AIRBASE.SouthAtlantic={
@@ -520,6 +605,87 @@ AIRBASE.SouthAtlantic={
["Puerto_Williams"]="Puerto Williams",
["Puerto_Natales"]="Puerto Natales",
["El_Calafate"]="El Calafate",
["Puerto_Santa_Cruz"]="Puerto Santa Cruz",
["Comandante_Luis_Piedrabuena"]="Comandante Luis Piedrabuena",
["Aerodromo_De_Tolhuin"]="Aerodromo De Tolhuin",
["Porvenir_Airfield"]="Porvenir Airfield",
["Almirante_Schroeders"]="Almirante Schroeders",
["Rio_Turbio"]="Rio Turbio",
["Rio_Chico"] = "Rio Chico",
["Franco_Bianco"] = "Franco Bianco",
["Goose_Green"] = "Goose Green",
["Hipico_Flying_Club"] = "Hipico Flying Club",
["CaletaTortel"] = "CaletaTortel",
["Aeropuerto_de_Gobernador_Gregores"] = "Aeropuerto de Gobernador Gregores",
["Aerodromo_O_Higgins"] = "Aerodromo O'Higgins",
["Cullen_Airport"] = "Cullen Airport",
["Gull_Point"] = "Gull Point",
}
--- Airbases of the Sinai map:
--
-- * AIRBASE.Sinai.Abu_Suwayr
-- * AIRBASE.Sinai.Sde_Dov
-- * AIRBASE.Sinai.AzZaqaziq
-- * AIRBASE.Sinai.Hatzor
-- * AIRBASE.Sinai.Kedem
-- * AIRBASE.Sinai.Nevatim
-- * AIRBASE.Sinai.Cairo_International_Airport
-- * AIRBASE.Sinai.Al_Ismailiyah
-- * AIRBASE.Sinai.As_Salihiyah
-- * AIRBASE.Sinai.Fayed
-- * AIRBASE.Sinai.Bilbeis_Air_Base
-- * AIRBASE.Sinai.Ramon_Airbase
-- * AIRBASE.Sinai.Kibrit_Air_Base
-- * AIRBASE.Sinai.El_Arish
-- * AIRBASE.Sinai.Ovda
-- * AIRBASE.Sinai.Melez
-- * AIRBASE.Sinai.Al_Mansurah
-- * AIRBASE.Sinai.Palmahim
-- * AIRBASE.Sinai.Baluza
-- * AIRBASE.Sinai.El_Gora
-- * AIRBASE.Sinai.Difarsuwar_Airfield
-- * AIRBASE.Sinai.Wadi_al_Jandali
-- * AIRBASE.Sinai.St_Catherine
-- * AIRBASE.Sinai.Tel_Nof
-- * AIRBASE.Sinai.Abu_Rudeis
-- * AIRBASE.Sinai.Inshas_Airbase
-- * AIRBASE.Sinai.Ben_Gurion
-- * AIRBASE.Sinai.Bir_Hasanah
-- * AIRBASE.Sinai.Cairo_West
--
-- @field Sinai
AIRBASE.Sinai = {
["Hatzerim"] = "Hatzerim",
["Abu_Suwayr"] = "Abu Suwayr",
["Sde_Dov"] = "Sde Dov",
["AzZaqaziq"] = "AzZaqaziq",
["Hatzor"] = "Hatzor",
["Kedem"] = "Kedem",
["Nevatim"] = "Nevatim",
["Cairo_International_Airport"] = "Cairo International Airport",
["Al_Ismailiyah"] = "Al Ismailiyah",
["As_Salihiyah"] = "As Salihiyah",
["Fayed"] = "Fayed",
["Bilbeis_Air_Base"] = "Bilbeis Air Base",
["Ramon_Airbase"] = "Ramon Airbase",
["Kibrit_Air_Base"] = "Kibrit Air Base",
["El_Arish"] = "El Arish",
["Ovda"] = "Ovda",
["Melez"] = "Melez",
["Al_Mansurah"] = "Al Mansurah",
["Palmahim"] = "Palmahim",
["Baluza"] = "Baluza",
["El_Gora"] = "El Gora",
["Difarsuwar_Airfield"] = "Difarsuwar Airfield",
["Wadi_al_Jandali"] = "Wadi al Jandali",
["St_Catherine"] = "St Catherine",
["Tel_Nof"] = "Tel Nof",
["Abu_Rudeis"] = "Abu Rudeis",
["Inshas_Airbase"] = "Inshas Airbase",
["Ben_Gurion"] = "Ben-Gurion",
["Bir_Hasanah"] = "Bir Hasanah",
["Cairo_West"] = "Cairo West",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
@@ -625,7 +791,13 @@ function AIRBASE:Register(AirbaseName)
-- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
-- H2 is bugged
--if self.AirbaseName == "H4" and self.descriptors == nil then
--self:E("***** H4 on Syria map is currently bugged!")
--return nil
--end
-- Set category.
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
@@ -673,6 +845,9 @@ function AIRBASE:Register(AirbaseName)
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
end
-- Debug info.
self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName)))
return self
end
@@ -824,7 +999,7 @@ end
-- Black listed spots overrule white listed spots.
-- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor!
-- @param #AIRBASE self
-- @param #table TerminalIdBlacklist Table of white listed terminal IDs.
-- @param #table TerminalIdWhitelist Table of white listed terminal IDs.
-- @return #AIRBASE self
-- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
@@ -1336,16 +1511,16 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Get the aircraft size, i.e. it's longest side of x,z.
local aircraft = nil -- fix local problem below
local _aircraftsize, ax,ay,az
-- SU27 dimensions as default
local _aircraftsize = 23
local ax = 23 -- l
local ay = 7 -- h
local az = 17 -- w
if group and group.ClassName == "GROUP" then
aircraft=group:GetUnit(1)
_aircraftsize, ax,ay,az=aircraft:GetObjectSize()
else
-- SU27 dimensions
_aircraftsize = 23
ax = 23 -- length
ay = 7 -- height
az = 17 -- width
if aircraft then
_aircraftsize, ax,ay,az=aircraft:GetObjectSize()
end
end
@@ -1353,7 +1528,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
local _nspots=nspots or group:GetSize()
-- Debug info.
self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype)))
-- Table of valid spots.
local validspots={}
@@ -2195,3 +2370,17 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn)
return false
end
--- Get category of airbase.
-- @param #AIRBASE self
-- @return #number Category of airbase from GetDesc().category.
function AIRBASE:GetCategory()
return self.category
end
--- Get category name of airbase.
-- @param #AIRBASE self
-- @return #string Category of airbase, i.e. Airdrome, Ship, or Helipad
function AIRBASE:GetCategoryName()
return AIRBASE.CategoryName[self.category]
end

View File

@@ -62,15 +62,15 @@
--
-- @field #CLIENT
CLIENT = {
ClassName = "CLIENT",
ClientName = nil,
ClientAlive = false,
ClientTransport = false,
ClientBriefingShown = false,
_Menus = {},
_Tasks = {},
Messages = {},
Players = {},
ClassName = "CLIENT",
ClientName = nil,
ClientAlive = false,
ClientTransport = false,
ClientBriefingShown = false,
_Menus = {},
_Tasks = {},
Messages = {},
Players = {},
}
@@ -95,6 +95,22 @@ function CLIENT:Find(DCSUnit, Error)
end
end
--- Finds a CLIENT from the _DATABASE using the relevant player name.
-- @param #CLIENT self
-- @param #string Name Name of the player
-- @return #CLIENT or nil if not found
function CLIENT:FindByPlayerName(Name)
local foundclient = nil
_DATABASE:ForEachClient(
function(client)
if client:GetPlayerName() == Name then
foundclient = client
end
end
)
return foundclient
end
--- Finds a CLIENT from the _DATABASE using the relevant Client Unit Name.
-- As an optional parameter, a briefing text can be given also.
@@ -105,13 +121,13 @@ end
-- @return #CLIENT
-- @usage
-- -- Create new Clients.
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
-- Mission:AddGoal( DeploySA6TroopsGoal )
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
-- Mission:AddGoal( DeploySA6TroopsGoal )
--
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
-- Mission:AddClient( CLIENT:FindByName( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
function CLIENT:FindByName( ClientName, ClientBriefing, Error )
-- Client
@@ -124,7 +140,7 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error )
ClientFound.MessageSwitch = true
return ClientFound
return ClientFound
end
if not Error then
@@ -262,8 +278,8 @@ end
-- @param #CLIENT self
-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client.
function CLIENT:Reset( ClientName )
self:F()
self._Menus = {}
self:F()
self._Menus = {}
end
-- Is Functions
@@ -347,85 +363,85 @@ function CLIENT:GetDCSGroup()
self:F3()
-- local ClientData = Group.getByName( self.ClientName )
-- if ClientData and ClientData:isExist() then
-- self:T( self.ClientName .. " : group found!" )
-- return ClientData
-- else
-- return nil
-- end
-- if ClientData and ClientData:isExist() then
-- self:T( self.ClientName .. " : group found!" )
-- return ClientData
-- else
-- return nil
-- end
local ClientUnit = Unit.getByName( self.ClientName )
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
self:T3( { "CoalitionData:", CoalitionData } )
for UnitId, UnitData in pairs( CoalitionData ) do
self:T3( { "UnitData:", UnitData } )
if UnitData and UnitData:isExist() then
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
self:T3( { "CoalitionData:", CoalitionData } )
for UnitId, UnitData in pairs( CoalitionData ) do
self:T3( { "UnitData:", UnitData } )
if UnitData and UnitData:isExist() then
--self:F(self.ClientName)
if ClientUnit then
local ClientGroup = ClientUnit:getGroup()
if ClientGroup then
self:T3( "ClientGroup = " .. self.ClientName )
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
if ClientGroup:getID() == UnitData:getGroup():getID() then
self:T3( "Normal logic" )
self:T3( self.ClientName .. " : group found!" )
local ClientGroup = ClientUnit:getGroup()
if ClientGroup then
self:T3( "ClientGroup = " .. self.ClientName )
if ClientGroup:isExist() and UnitData:getGroup():isExist() then
if ClientGroup:getID() == UnitData:getGroup():getID() then
self:T3( "Normal logic" )
self:T3( self.ClientName .. " : group found!" )
self.ClientGroupID = ClientGroup:getID()
self.ClientGroupName = ClientGroup:getName()
return ClientGroup
end
else
-- Now we need to resolve the bugs in DCS 1.5 ...
-- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil)
self:T3( "Bug 1.5 logic" )
local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate
self.ClientGroupID = ClientGroupTemplate.groupId
self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName
self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" )
return ClientGroup
end
-- else
-- error( "Client " .. self.ClientName .. " not found!" )
end
else
--self:F( { "Client not found!", self.ClientName } )
end
end
end
end
-- For non player clients
if ClientUnit then
local ClientGroup = ClientUnit:getGroup()
if ClientGroup then
self:T3( "ClientGroup = " .. self.ClientName )
if ClientGroup:isExist() then
self:T3( "Normal logic" )
self:T3( self.ClientName .. " : group found!" )
return ClientGroup
end
end
self.ClientGroupName = ClientGroup:getName()
return ClientGroup
end
else
-- Now we need to resolve the bugs in DCS 1.5 ...
-- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil)
self:T3( "Bug 1.5 logic" )
local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate
self.ClientGroupID = ClientGroupTemplate.groupId
self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName
self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" )
return ClientGroup
end
-- else
-- error( "Client " .. self.ClientName .. " not found!" )
end
else
--self:F( { "Client not found!", self.ClientName } )
end
end
end
end
-- Nothing could be found :(
self.ClientGroupID = nil
self.ClientGroupName = nil
return nil
-- For non player clients
if ClientUnit then
local ClientGroup = ClientUnit:getGroup()
if ClientGroup then
self:T3( "ClientGroup = " .. self.ClientName )
if ClientGroup:isExist() then
self:T3( "Normal logic" )
self:T3( self.ClientName .. " : group found!" )
return ClientGroup
end
end
end
-- Nothing could be found :(
self.ClientGroupID = nil
self.ClientGroupName = nil
return nil
end
@@ -437,7 +453,7 @@ function CLIENT:GetClientGroupID()
-- This updates the ID.
self:GetDCSGroup()
return self.ClientGroupID
return self.ClientGroupID
end
@@ -449,7 +465,7 @@ function CLIENT:GetClientGroupName()
-- This updates the group name.
self:GetDCSGroup()
return self.ClientGroupName
return self.ClientGroupName
end
--- Returns the UNIT of the CLIENT.
@@ -458,23 +474,23 @@ end
function CLIENT:GetClientGroupUnit()
self:F2()
local ClientDCSUnit = Unit.getByName( self.ClientName )
local ClientDCSUnit = Unit.getByName( self.ClientName )
self:T( self.ClientDCSUnit )
if ClientDCSUnit and ClientDCSUnit:isExist() then
local ClientUnit=_DATABASE:FindUnit( self.ClientName )
return ClientUnit
end
return nil
if ClientDCSUnit and ClientDCSUnit:isExist() then
local ClientUnit=_DATABASE:FindUnit( self.ClientName )
return ClientUnit
end
return nil
end
--- Returns the DCSUnit of the CLIENT.
-- @param #CLIENT self
-- @return DCS#Unit
function CLIENT:GetClientGroupDCSUnit()
self:F2()
self:F2()
local ClientDCSUnit = Unit.getByName( self.ClientName )
@@ -489,29 +505,29 @@ end
-- @param #CLIENT self
-- @return #boolean true is a transport.
function CLIENT:IsTransport()
self:F()
return self.ClientTransport
self:F()
return self.ClientTransport
end
--- Shows the @{AI.AI_Cargo#CARGO} contained within the CLIENT to the player as a message.
-- The @{AI.AI_Cargo#CARGO} is shown using the @{Core.Message#MESSAGE} distribution system.
-- @param #CLIENT self
function CLIENT:ShowCargo()
self:F()
self:F()
local CargoMsg = ""
local CargoMsg = ""
for CargoName, Cargo in pairs( CARGOS ) do
if self == Cargo:IsLoadedInClient() then
CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n"
end
end
for CargoName, Cargo in pairs( CARGOS ) do
if self == Cargo:IsLoadedInClient() then
CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n"
end
end
if CargoMsg == "" then
CargoMsg = "empty"
end
if CargoMsg == "" then
CargoMsg = "empty"
end
self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 )
self:Message( CargoMsg, 15, "Co-Pilot: Cargo Status", 30 )
end
@@ -526,39 +542,39 @@ end
-- @param #number MessageInterval is the interval in seconds between the display of the @{Core.Message#MESSAGE} when the CLIENT is in the air.
-- @param #string MessageID is the identifier of the message when displayed with intervals.
function CLIENT:Message( Message, MessageDuration, MessageCategory, MessageInterval, MessageID )
self:F( { Message, MessageDuration, MessageCategory, MessageInterval } )
self:F( { Message, MessageDuration, MessageCategory, MessageInterval } )
if self.MessageSwitch == true then
if MessageCategory == nil then
MessageCategory = "Messages"
end
if MessageID ~= nil then
if self.Messages[MessageID] == nil then
self.Messages[MessageID] = {}
self.Messages[MessageID].MessageId = MessageID
self.Messages[MessageID].MessageTime = timer.getTime()
self.Messages[MessageID].MessageDuration = MessageDuration
if MessageInterval == nil then
self.Messages[MessageID].MessageInterval = 600
else
self.Messages[MessageID].MessageInterval = MessageInterval
end
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
else
if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then
MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self )
self.Messages[MessageID].MessageTime = timer.getTime()
end
else
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
self.Messages[MessageID].MessageTime = timer.getTime()
end
end
end
else
if self.MessageSwitch == true then
if MessageCategory == nil then
MessageCategory = "Messages"
end
if MessageID ~= nil then
if self.Messages[MessageID] == nil then
self.Messages[MessageID] = {}
self.Messages[MessageID].MessageId = MessageID
self.Messages[MessageID].MessageTime = timer.getTime()
self.Messages[MessageID].MessageDuration = MessageDuration
if MessageInterval == nil then
self.Messages[MessageID].MessageInterval = 600
else
self.Messages[MessageID].MessageInterval = MessageInterval
end
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
else
if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + 10 then
MESSAGE:New( Message, MessageDuration , MessageCategory):ToClient( self )
self.Messages[MessageID].MessageTime = timer.getTime()
end
else
if timer.getTime() - self.Messages[MessageID].MessageTime >= self.Messages[MessageID].MessageDuration + self.Messages[MessageID].MessageInterval then
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
self.Messages[MessageID].MessageTime = timer.getTime()
end
end
end
else
MESSAGE:New( Message, MessageDuration, MessageCategory ):ToClient( self )
end
end
end
end

View File

@@ -52,6 +52,7 @@
-- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable.
-- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location.
-- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable.
-- * @{#CONTROLLABLE.TaskGroundEscort}: (AIR/HELO) Escort a ground controllable.
-- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction.
-- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point.
-- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable.
@@ -62,11 +63,11 @@
-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude.
-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed.
-- * @{#CONTROLLABLE.TaskRefueling}: (AIR) Refueling from the nearest tanker. No parameters.
-- * @{#CONTROLLABLE.TaskRecoveryTanker}: (AIR) Set group to act as recovery tanker for a naval group.
-- * @{#CONTROLLABLE.TaskRoute}: (AIR + GROUND) Return a Mission task to follow a given route defined by Points.
-- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point.
-- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point.
-- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone.
-- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase.
--
-- ## 2.2) EnRoute assignment
--
@@ -668,12 +669,63 @@ function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Ch
return self
end
--- Activate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! Also needs Link4 to work.
-- @param #CONTROLLABLE self
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the ACLS system is attached to. Defaults to the UNIT itself.
-- @param #string Name (Optional) Name of the ACLS Beacon
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateACLS( UnitID, Name, Delay )
-- Command to activate ACLS system.
local CommandActivateACLS= {
id = 'ActivateACLS',
params = {
unitId = UnitID or self:GetID(),
name = Name or "ACL",
}
}
self:T({CommandActivateACLS})
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandActivateACLS, { self, UnitID, Name }, Delay )
else
local controller = self:_GetController()
controller:setCommand( CommandActivateACLS )
end
return self
end
--- Deactivate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateACLS( Delay )
-- Command to activate ACLS system.
local CommandDeactivateACLS= {
id = 'DeactivateACLS',
params = { }
}
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandDeactivateACLS, { self }, Delay )
else
local controller = self:_GetController()
controller:setCommand( CommandDeactivateACLS )
end
return self
end
--- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Channel ICLS channel.
-- @param #number UnitID The DCS UNIT ID of the unit the ICLS system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated.
-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
@@ -683,13 +735,13 @@ function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay )
params = {
["type"] = BEACON.Type.ICLS,
["channel"] = Channel,
["unitId"] = UnitID,
["unitId"] = UnitID or self:GetID(),
["callsign"] = Callsign,
},
}
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay )
SCHEDULER:New( nil, self.CommandActivateICLS, { self, Channel, UnitID, Callsign }, Delay )
else
self:SetCommand( CommandActivateICLS )
end
@@ -699,55 +751,32 @@ end
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 (defaults to 336 MHz)
-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the LINK4 system is attached to. Defaults to the UNIT itself.
-- @param #string Callsign (Optional) Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is activated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
local freq = Frequency or 336
-- Command to activate Link4 system.
local CommandActivateLink4= {
id = "ActivateLink4",
params= {
["frequency "] = Frequency*1000,
["unitId"] = UnitID,
["name"] = Callsign,
["frequency"] = freq*1000000,
["unitId"] = UnitID or self:GetID(),
["name"] = Callsign or "LNK",
}
}
self:T({CommandActivateLink4})
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
SCHEDULER:New(nil, self.CommandActivateLink4, {self, Frequency, UnitID, Callsign}, Delay)
else
self:SetCommand(CommandActivateLink4)
end
return self
end
--- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier!
-- @param #CONTROLLABLE self
-- @param #number Frequency Link4 Frequency in MHz, e.g. 336
-- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group.
-- @param #string Callsign Morse code identification callsign.
-- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay)
-- Command to activate Link4 system.
local CommandActivateLink4= {
id = "ActivateLink4",
params= {
["frequency "] = Frequency*1000,
["unitId"] = UnitID,
["name"] = Callsign,
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay)
else
self:SetCommand(CommandActivateLink4)
local controller = self:_GetController()
controller:setCommand(CommandActivateLink4)
end
return self
@@ -785,7 +814,8 @@ function CONTROLLABLE:CommandDeactivateLink4(Delay)
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay)
else
self:SetCommand(CommandDeactivateLink4)
local controller = self:_GetController()
controller:setCommand(CommandDeactivateLink4)
end
return self
@@ -809,24 +839,6 @@ function CONTROLLABLE:CommandDeactivateICLS( Delay )
return self
end
--- Deactivate the active Link4 of the CONTROLLABLE.
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandDeactivateLink4(Delay)
-- Command to deactivate
local CommandDeactivateLink4={id='DeactivateLink4', params={}}
if Delay and Delay>0 then
SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay)
else
self:SetCommand(CommandDeactivateLink4)
end
return self
end
--- Set callsign of the CONTROLLABLE. See [DCS command setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign)
-- @param #CONTROLLABLE self
-- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called.
@@ -1259,7 +1271,7 @@ end
--- (AIR) Orbit at a position with at a given altitude and speed. Optionally, a race track pattern can be specified.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits.
-- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits. Can also be given as a `DCS#Vec3` or `DCS#Vec2` object.
-- @param #number Altitude Altitude in meters of the orbit pattern. Default y component of Coord.
-- @param #number Speed Speed [m/s] flying the orbit pattern. Default 128 m/s = 250 knots.
-- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate.
@@ -1268,11 +1280,11 @@ function CONTROLLABLE:TaskOrbit( Coord, Altitude, Speed, CoordRaceTrack )
local Pattern = AI.Task.OrbitPattern.CIRCLE
local P1 = Coord:GetVec2()
local P1 = {x=Coord.x, y=Coord.z or Coord.y}
local P2 = nil
if CoordRaceTrack then
Pattern = AI.Task.OrbitPattern.RACE_TRACK
P2 = CoordRaceTrack:GetVec2()
P2 = {x=CoordRaceTrack.x, y=CoordRaceTrack.z or CoordRaceTrack.y}
end
local Task = {
@@ -1367,6 +1379,31 @@ function CONTROLLABLE:TaskRefueling()
return DCSTask
end
--- (AIR) Act as Recovery Tanker for a naval/carrier group.
-- @param #CONTROLLABLE self
-- @param Wrapper.Group#GROUP CarrierGroup
-- @param #number Speed Speed in meters per second
-- @param #number Altitude Altitude the tanker orbits at in meters
-- @param #number LastWptNumber (optional) Waypoint of carrier group that when reached, ends the recovery tanker task
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskRecoveryTanker(CarrierGroup, Speed, Altitude, LastWptNumber)
local LastWptFlag = type(LastWptNumber) == "number" and true or false
local DCSTask = {
id = "RecoveryTanker",
params = {
groupId = CarrierGroup:GetID(),
speed = Speed,
altitude = Altitude,
lastWptIndexFlag = LastWptFlag,
lastWptIndex = LastWptNumber
}
}
return DCSTask
end
--- (AIR HELICOPTER) Landing at the ground. For helicopters only.
-- @param #CONTROLLABLE self
-- @param DCS#Vec2 Vec2 The point where to land.
@@ -1390,7 +1427,7 @@ end
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to land.
-- @param #number Duration The duration in seconds to stay on the ground.
-- @return #CONTROLLABLE self
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint )
-- Get landing point
@@ -1444,15 +1481,53 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex )
return DCSTask
end
--- (AIR/HELO) Escort a ground controllable.
-- The unit / controllable will follow lead unit of the other controllable, additional units of both controllables will continue following their leaders.
-- The unit / controllable will also protect that controllable from threats of specified types.
-- @param #CONTROLLABLE self
-- @param #CONTROLLABLE FollowControllable The controllable to be escorted.
-- @param #number LastWaypointIndex (optional) Detach waypoint of another controllable. Once reached the unit / controllable Escort task is finished.
-- @param #number OrbitDistance (optional) Maximum distance helo will orbit around the ground unit in meters. Defaults to 2000 meters.
-- @param DCS#AttributeNameArray TargetTypes (optional) Array of AttributeName that is contains threat categories allowed to engage. Default {"Ground vehicles"}. See [https://wiki.hoggit.us/view/DCS_enum_attributes](https://wiki.hoggit.us/view/DCS_enum_attributes)
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskGroundEscort( FollowControllable, LastWaypointIndex, OrbitDistance, TargetTypes )
-- Escort = {
-- id = 'GroundEscort',
-- params = {
-- groupId = Group.ID, -- must
-- engagementDistMax = Distance, -- Must. With his task it does not appear to actually define the range AI are allowed to attack at, rather it defines the size length of the orbit. The helicopters will fly up to this set distance before returning to the escorted group.
-- lastWptIndexFlag = boolean, -- optional
-- lastWptIndex = number, -- optional
-- targetTypes = array of AttributeName, -- must
-- lastWptIndexFlagChangedManually = boolean, -- must be true
-- }
-- }
local DCSTask = {
id = 'GroundEscort',
params = {
groupId = FollowControllable and FollowControllable:GetID() or nil,
engagementDistMax = OrbitDistance or 2000,
lastWptIndexFlag = LastWaypointIndex and true or false,
lastWptIndex = LastWaypointIndex,
targetTypes = TargetTypes or {"Ground vehicles"},
lastWptIndexFlagChangedManually = true,
},
}
return DCSTask
end
--- (AIR) Escort another airborne controllable.
-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders.
-- The unit / controllable will also protect that controllable from threats of specified types.
-- @param #CONTROLLABLE self
-- @param #CONTROLLABLE FollowControllable The controllable to be escorted.
-- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around.
-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished.
-- @param #number EngagementDistance Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax.
-- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. Default {"Air"}.
-- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Escort task is finished.
-- @param #number EngagementDistance Maximal distance from escorted controllable to threat in meters. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax.
-- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. Default {"Air"}. See https://wiki.hoggit.us/view/DCS_enum_attributes
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes )
@@ -1468,8 +1543,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E
-- }
-- }
local DCSTask
DCSTask = {
local DCSTask = {
id = 'Escort',
params = {
groupId = FollowControllable and FollowControllable:GetID() or nil,
@@ -1639,8 +1713,30 @@ function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority)
return DCSTask
end
--- (AIR) Enroute SEAD task.
-- @param #CONTROLLABLE self
-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Air Defence"}`.
-- @param #number Priority (Optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. Default 0.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes, Priority)
--- (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.
local DCSTask = {
id = 'EngageTargets',
key = "SEAD",
--auto = false,
--enabled = true,
params = {
targetTypes = TargetTypes or {"Air Defence"},
priority = Priority or 0
}
}
return DCSTask
end
--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now;
-- it just allows the unit/controllable to engage the target controllable as well as other assigned targets.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_engageGroup).
-- @param #CONTROLLABLE self
-- @param #CONTROLLABLE AttackGroup The Controllable to be attacked.
-- @param #number Priority 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.
@@ -1689,6 +1785,7 @@ function CONTROLLABLE:EnRouteTaskEngageGroup( AttackGroup, Priority, WeaponType,
end
--- (AIR) Search and attack the Unit.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_engageUnit).
-- @param #CONTROLLABLE self
-- @param Wrapper.Unit#UNIT EngageUnit The UNIT.
-- @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.
@@ -1724,6 +1821,7 @@ function CONTROLLABLE:EnRouteTaskEngageUnit( EngageUnit, Priority, GroupAttack,
end
--- (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters.
-- [hoggit](https://wiki.hoggitworld.com/view/DCS_task_awacs).
-- @param #CONTROLLABLE self
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskAWACS()
@@ -1737,6 +1835,7 @@ function CONTROLLABLE:EnRouteTaskAWACS()
end
--- (AIR) Aircraft will act as a tanker for friendly units. No parameters.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_tanker).
-- @param #CONTROLLABLE self
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskTanker()
@@ -1752,6 +1851,7 @@ end
-- En-route tasks for ground units/controllables
--- (GROUND) Ground unit (EW-radar) will act as an EWR for friendly units (will provide them with information about contacts). No parameters.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_ewr).
-- @param #CONTROLLABLE self
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskEWR()
@@ -1769,14 +1869,17 @@ end
--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets.
-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC.
-- If the task is assigned to the controllable lead unit will be a FAC.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_fac_engageGroup).
-- @param #CONTROLLABLE self
-- @param #CONTROLLABLE AttackGroup Target CONTROLLABLE.
-- @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 is 0.
-- @param #number WeaponType (Optional) Bitmask of weapon types those allowed to use. Default is "Auto".
-- @param DCS#AI.Task.Designation Designation (Optional) Designation type.
-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default.
-- @param #number CallsignID CallsignID, e.g. `CALLSIGN.JTAC.Anvil` for ground or `CALLSIGN.Aircraft.Ford` for air.
-- @param #number CallsignNumber Callsign first number, e.g. 2 for `Ford-2`.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink )
function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignID, CallsignNumber )
local DCSTask = {
id = 'FAC_EngageControllable',
@@ -1785,6 +1888,10 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT
weaponType = WeaponType or "Auto",
designation = Designation,
datalink = Datalink and Datalink or false,
frequency = (Frequency or 133)*1000000,
modulation = Modulation or radio.modulation.AM,
callname = CallsignID,
number = CallsignNumber,
priority = Priority or 0,
},
}
@@ -1793,27 +1900,26 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT
end
--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets.
-- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC.
-- If the task is assigned to the controllable lead unit will be a FAC.
-- Assigns the controlled group to act as a Forward Air Controller or JTAC. Any detected targets will be assigned as targets to the player via the JTAC radio menu.
-- Target designation is set to auto and is dependent on the circumstances.
-- See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_fac).
-- @param #CONTROLLABLE self
-- @param DCS#Distance Radius The maximal distance from the FAC to a target.
-- @param #number Frequency Frequency in MHz. Default 133 MHz.
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
-- @param #number CallsignID CallsignID, e.g. `CALLSIGN.JTAC.Anvil` for ground or `CALLSIGN.Aircraft.Ford` for air.
-- @param #number CallsignNumber Callsign first number, e.g. 2 for `Ford-2`.
-- @param #number Priority 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.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority )
-- FAC = {
-- id = 'FAC',
-- params = {
-- radius = Distance,
-- priority = number
-- }
-- }
function CONTROLLABLE:EnRouteTaskFAC( Frequency, Modulation, CallsignID, CallsignNumber, Priority )
local DCSTask = {
id = 'FAC',
params = {
radius = Radius,
priority = Priority
frequency = (Frequency or 133)*1000000,
modulation = Modulation or radio.modulation.AM,
callname = CallsignID,
number = CallsignNumber,
priority = Priority or 0
}
}
@@ -2369,7 +2475,7 @@ do -- Route methods
-- @return DCS#Task Task.
-- @return #boolean If true, path on road is possible. If false, task will route the group directly to its destination.
function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, OffRoadFormation, Shortcut, FromCoordinate, WaypointFunction, WaypointFunctionArguments )
self:I( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } )
self:T( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } )
-- Defaults.
Speed = Speed or 20
@@ -3928,4 +4034,4 @@ function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType)
end
end
return self
end
end

View File

@@ -624,10 +624,9 @@ function GROUP:GetRange()
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}.
-- @return #table of Wrapper.Unit#UNIT objects, indexed by number.
function GROUP:GetUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
@@ -645,7 +644,6 @@ function GROUP:GetUnits()
return nil
end
--- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}.
@@ -676,41 +674,38 @@ function GROUP:IsPlayer()
return self:GetUnit(1):IsPlayer()
end
--- Returns the UNIT wrapper class with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
--- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit.
-- If no underlying DCS Units exist, the method will return nil.
-- @param #GROUP self
-- @param #number UnitNumber The number of the UNIT wrapper class to be returned.
-- @return Wrapper.Unit#UNIT The UNIT wrapper class.
-- @return Wrapper.Unit#UNIT The UNIT object or nil
function GROUP:GetUnit( UnitNumber )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
if DCSGroup then
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
for _,_unit in pairs(units) do
local UnitFound = UNIT:Find(_unit)
-- Maybe fixed with 2.8?
local units = DCSGroup:getUnits() or {}
if units[UnitNumber] then
local UnitFound = UNIT:Find(units[UnitNumber])
if UnitFound then
return UnitFound
end
else
for _,_unit in pairs(units) do
local UnitFound = UNIT:Find(_unit)
if UnitFound then
return UnitFound
end
end
end
end
return nil
return nil
end
--- Returns the DCS Unit with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
-- If the underlying DCS Unit does not exist, the method will return try to find the next unit. Returns nil if no units are found.
-- @param #GROUP self
-- @param #number UnitNumber The number of the DCS Unit to be returned.
-- @return DCS#Unit The DCS Unit.
@@ -723,8 +718,7 @@ function GROUP:GetDCSUnit( UnitNumber )
if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then
return DCSGroup:getUnit( UnitNumber )
else
local UnitFound = nil
-- 2.7.1 dead event bug, return the first alive unit instead
local units = DCSGroup:getUnits() or {}
@@ -803,7 +797,20 @@ function GROUP:GetFirstUnitAlive()
return nil
end
--- Get the first unit of the group. Might be nil!
-- @param #GROUP self
-- @return Wrapper.Unit#UNIT First unit or nil if it does not exist.
function GROUP:GetFirstUnit()
self:F3({self.GroupName})
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local units=self:GetUnits()
return units[1]
end
return nil
end
--- Returns the average velocity Vec3 vector.
-- @param Wrapper.Group#GROUP self
@@ -1646,7 +1653,7 @@ function GROUP:GetMinHeight()
return nil
end
--- Returns the current maximum height of the group.
--- Returns the current maximum height of the group, i.e. the highest unit height of that group.
-- Each unit within the group gets evaluated, and the maximum height (= the unit which is the highest elevated) is returned.
-- @param #GROUP self
-- @return #number Maximum height found.
@@ -1661,7 +1668,7 @@ function GROUP:GetMaxHeight()
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
local UnitData = UnitData -- DCS#Unit
local UnitHeight = UnitData:getPoint()
local UnitHeight = UnitData:getPoint().p.y -- Height -- found by @Heavydrinker
if UnitHeight > GroupHeightMax then
GroupHeightMax = UnitHeight
@@ -2778,11 +2785,16 @@ end
-- @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
-- @param #table CallsignTranslations Table to translate between DCS standard callsigns and bespoke ones. Overrides personal/parsed callsigns if set
-- callsigns from playername or group name.
-- @return #string Callsign
-- @usage
-- -- Set Custom CAP Flight Callsigns for use with TTS
-- -- suppose there are three groups with one (client) unit each:
-- -- Slot 1 -- with mission editor callsign Enfield-1
-- -- Slot 2 # Apollo 403 -- with mission editor callsign Enfield-2
-- -- Slot 3 | Apollo -- with mission editor callsign Enfield-3
-- -- Slot 4 | Apollo -- with mission editor callsign Devil-4
-- -- and suppose these Custom CAP Flight Callsigns for use with TTS are set
-- mygroup:GetCustomCallSign(true,false,{
-- Devil = 'Bengal',
-- Snake = 'Winder',
@@ -2790,12 +2802,12 @@ end
-- Enfield = 'Victory',
-- Uzi = 'Evil Eye'
-- })
--
-- results in this outcome if the group has Callsign "Enfield 9 1" on the 1st #UNIT of the group:
--
-- 'Victory 9'
--
--
-- -- then GetCustomCallsign will return
-- -- Enfield-1 for Slot 1
-- -- Apollo for Slot 2 or Apollo 403 if Keepnumber is set
-- -- Apollo for Slot 3
-- -- Bengal-4 for Slot 4
function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
--self:I("GetCustomCallSign")
@@ -2803,14 +2815,18 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
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
local callsignroot = string.match(shortcallsign, '(%a+)') or "Ghost" -- 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
-- prioritize bespoke callsigns over parsing, prefer parsing over default callsigns
if CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
elseif IsPlayer and string.find(groupname,"#") then
-- personalized flight name in group naming
if Keepnumber then
shortcallsign = string.match(groupname,"#(.+)") or "Ghost 111" -- Ghostrider 219
@@ -2823,35 +2839,58 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations)
shortcallsign = string.match(self:GetPlayerName(),"|%s*([%a]+)") or string.match(self:GetPlayerName(),"|%s*([%d]+)") or "Ghost" -- Ghostrider
personalized = true
end
if (not personalized) and CallsignTranslations and CallsignTranslations[callsignroot] then
callsignroot = CallsignTranslations[callsignroot]
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
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
-- AI or not personalized
if ShortCallsign then
callsign = callsignroot.." "..callnumbermajor -- Uzi/Victory 9
else
callsign = shortcallsign.." "..callnumbermajor.." "..callnumberminor -- Ghostrider 9 1
callsign = callsignroot.." "..callnumbermajor.." "..callnumberminor -- Uzi/Victory 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
---
-- @param #GROUP self
-- @param Wrapper.Group#GROUP CarrierGroup.
-- @param #number Speed Speed in knots.
-- @param #boolean ToKIAS If true, adjust speed to altitude (KIAS).
-- @param #number Altitude Altitude the tanker orbits at in feet.
-- @param #number Delay (optional) Set the task after this many seconds. Defaults to one.
-- @param #number LastWaypoint (optional) Waypoint number of carrier group that when reached, ends the recovery tanker task.
-- @return #GROUP self
function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,LastWaypoint)
local speed = ToKIAS == true and UTILS.KnotsToAltKIAS(Speed,Altitude) or Speed
speed = UTILS.KnotsToMps(speed)
local alt = UTILS.FeetToMeters(Altitude)
local delay = Delay or 1
local task = self:TaskRecoveryTanker(CarrierGroup,speed,alt,LastWaypoint)
self:SetTask(task,delay)
local tankertask = self:EnRouteTaskTanker()
self:PushTask(tankertask,delay+2)
return self
end

View File

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

View File

@@ -0,0 +1,834 @@
--- **Wrapper** - DCS net functions.
--
-- Encapsules **multiplayer server** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
--
-- ===
--
-- ### Author: **Applevangelist**
-- # Last Update June 2023
--
-- ===
--
-- @module Wrapper.Net
-- @image Utils_Profiler.jpg
do
--- The NET class
-- @type NET
-- @field #string ClassName
-- @field #string Version
-- @field #string lid
-- @field #number BlockTime
-- @field #table BlockedPilots
-- @field #table KnownPilots
-- @field #string BlockMessage
-- @field #string UnblockMessage
-- @field #table BlockedUCIDs
-- @field #table BlockedSlots
-- @field #table BlockedSides
-- @extends Core.Fsm#FSM
---
-- @type NET.PlayerData
-- @field #string name
-- @field #string ucid
-- @field #number id
-- @field #number side
-- @field #number slot
--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net)
-- with some added FSM functions and options to block/unblock players in MP environments.
--
-- @field #NET
NET = {
ClassName = "NET",
Version = "0.1.2",
BlockTime = 600,
BlockedPilots = {},
BlockedUCIDs = {},
BlockedSides = {},
BlockedSlots = {},
KnownPilots = {},
BlockMessage = nil,
UnblockMessage = nil,
lid = nil,
}
--- Instantiate a new NET object.
-- @param #NET self
-- @return #NET self
function NET:New()
-- Inherit base.
local self = BASE:Inherit(self, FSM:New()) -- #NET
self.BlockTime = 600
self.BlockedPilots = {}
self.KnownPilots = {}
self:SetBlockMessage()
self:SetUnblockMessage()
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Run", "Running") -- Start FSM.
self:AddTransition("*", "PlayerJoined", "*")
self:AddTransition("*", "PlayerLeft", "*")
self:AddTransition("*", "PlayerDied", "*")
self:AddTransition("*", "PlayerEjected", "*")
self:AddTransition("*", "PlayerBlocked", "*")
self:AddTransition("*", "PlayerUnblocked", "*")
self:AddTransition("*", "Status", "*")
self:AddTransition("*", "Stop", "Stopped")
self.lid = string.format("NET %s | ",self.Version)
--- FSM Function OnAfterPlayerJoined.
-- @function [parent=#NET] OnAfterPlayerJoined
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Client#CLIENT Client Object.
-- @param #string Name Name of joining Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerLeft.
-- @function [parent=#NET] OnAfterPlayerLeft
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of leaving Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerEjected.
-- @function [parent=#NET] OnAfterPlayerEjected
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of leaving Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerDied.
-- @function [parent=#NET] OnAfterPlayerDied
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Unit#UNIT Client Unit Object, might be nil.
-- @param #string Name Name of dead Pilot.
-- @return #NET self
--- FSM Function OnAfterPlayerBlocked.
-- @function [parent=#NET] OnAfterPlayerBlocked
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Client#CLIENT Client Client Object, might be nil.
-- @param #string Name Name of blocked Pilot.
-- @param #number Seconds Blocked for this number of seconds
-- @return #NET self
--- FSM Function OnAfterPlayerUnblocked.
-- @function [parent=#NET] OnAfterPlayerUnblocked
-- @param #NET self
-- @param #string From State.
-- @param #string Event Trigger.
-- @param #string To State.
-- @param Wrapper.Client#CLIENT Client Client Object, might be nil.
-- @param #string Name Name of unblocked Pilot.
-- @return #NET self
self:Run()
return self
end
--- [Internal] Check any blockers
-- @param #NET self
-- @param #string UCID
-- @param #string Name
-- @param #number PlayerID
-- @param #number PlayerSide
-- @param #string PlayerSlot
-- @return #boolean IsBlocked
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
local blocked = false
local TNow = timer.getTime()
-- UCID
if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then
return true
end
-- ID/Name
if PlayerID and not Name then
Name = self:GetPlayerIDByName(Name)
end
-- Name
if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then
return true
end
-- Side
if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then
return true
end
-- Slot
if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then
return true
end
return blocked
end
--- [Internal] Event Handler
-- @param #NET self
-- @param Core.Event#EVENTDATA EventData
-- @return #NET self
function NET:_EventHandler(EventData)
self:T(self.lid .. " _EventHandler")
self:T2({Event = EventData.id})
local data = EventData -- Core.Event#EVENTDATA EventData
if data.id and data.IniUnit and (data.IniPlayerName or data.IniUnit:GetPlayerName()) then
-- Get Player Data
local name = data.IniPlayerName and data.IniPlayerName or data.IniUnit:GetPlayerName()
local ucid = self:GetPlayerUCID(nil,name) or "none"
local PlayerID = self:GetPlayerIDByName(name) or "none"
local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit)
local TNow = timer.getTime()
self:T(self.lid.."Event for: "..name.." | UCID: "..ucid)
-- Joining
if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid)
-- Check for blockages
local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
if blocked and PlayerID and tonumber(PlayerID) ~= 1 then
-- block pilot
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
else
self.KnownPilots[name] = {
name = name,
ucid = ucid,
id = PlayerID,
side = PlayerSide,
slot = PlayerSlot,
}
local client = CLIENT:FindByPlayerName(name) or data.IniUnit
self:__PlayerJoined(1,client,name)
return self
end
end
-- Leaving
if data.id == EVENTS.PlayerLeaveUnit and self.KnownPilots[name] then
self:T(self.lid.."Pilot Leaving: "..name.." | UCID: "..ucid)
self:__PlayerLeft(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
-- Ejected
if data.id == EVENTS.Ejection and self.KnownPilots[name] then
self:T(self.lid.."Pilot Ejecting: "..name.." | UCID: "..ucid)
self:__PlayerEjected(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
-- Dead, Crash, Suicide
if (data.id == EVENTS.PilotDead or data.id == EVENTS.SelfKillPilot or data.id == EVENTS.Crash) and self.KnownPilots[name] then
self:T(self.lid.."Pilot Dead: "..name.." | UCID: "..ucid)
self:__PlayerDied(1,data.IniUnit,name)
self.KnownPilots[name] = false
return self
end
end
return self
end
--- Block a player.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client CLIENT object.
-- @param #string PlayerName (optional) Name of the player.
-- @param #number Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:BlockPlayer(Client,PlayerName,Seconds,Message)
self:T({PlayerName,Seconds,Message})
local name = PlayerName
if Client and (not PlayerName) then
name = Client:GetPlayerName()
elseif PlayerName then
name = PlayerName
else
self:F(self.lid.."Block: No Client or PlayerName given or nothing found!")
return self
end
local ucid = self:GetPlayerUCID(Client,name)
local addon = Seconds or self.BlockTime
self.BlockedPilots[name] = timer.getTime()+addon
self.BlockedUCIDs[ucid] = timer.getTime()+addon
local message = Message or self.BlockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerBlocked(1,Client,name,Seconds)
local PlayerID = self:GetPlayerIDByName(name)
if PlayerID and tonumber(PlayerID) ~= 1 then
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
end
return self
end
--- Block a SET_CLIENT of players
-- @param #NET self
-- @param Core.Set#SET_CLIENT PlayerSet The SET to block.
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:BlockPlayerSet(PlayerSet,Seconds,Message)
self:T({PlayerSet.Set,Seconds,Message})
local addon = Seconds or self.BlockTime
local message = Message or self.BlockMessage
for _,_client in pairs(PlayerSet.Set) do
local name = _client:GetPlayerName()
self:BlockPlayer(_client,name,addon,message)
end
return self
end
--- Unblock a SET_CLIENT of players
-- @param #NET self
-- @param Core.Set#SET_CLIENT PlayerSet The SET to unblock.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:UnblockPlayerSet(PlayerSet,Message)
self:T({PlayerSet.Set,Seconds,Message})
local message = Message or self.UnblockMessage
for _,_client in pairs(PlayerSet.Set) do
local name = _client:GetPlayerName()
self:UnblockPlayer(_client,name,message)
end
return self
end
--- Block a specific UCID of a player, does NOT automatically kick the player with the UCID if already joined.
-- @param #NET self
-- @param #string ucid
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockUCID(ucid,Seconds)
self:T({ucid,Seconds})
local addon = Seconds or self.BlockTime
self.BlockedUCIDs[ucid] = timer.getTime()+addon
return self
end
--- Unblock a specific UCID of a player
-- @param #NET self
-- @param #string ucid
-- @return #NET self
function NET:UnblockUCID(ucid)
self:T({ucid})
self.BlockedUCIDs[ucid] = nil
return self
end
--- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players
-- @param #NET self
-- @param #number side The side to block - 1 : Red, 2 : Blue
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = timer.getTime()+addon
end
return self
end
--- Unblock a specific coalition side. Does NOT unblock specifically blocked playernames or UCIDs.
-- @param #number side The side to block - 1 : Red, 2 : Blue
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:UnblockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = nil
end
return self
end
--- Block a specific player slot, does NOT automatically kick a player in that slot or kick out joined players
-- @param #NET self
-- @param #string slot The slot to block
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockSlot(Slot,Seconds)
self:T({Slot,Seconds})
local addon = Seconds or self.BlockTime
self.BlockedSlots[Slot] = timer.getTime()+addon
return self
end
--- Unblock a specific slot.
-- @param #string slot The slot to block
-- @return #NET self
function NET:UnblockSlot(Slot)
self:T({Slot})
self.BlockedSlots[Slot] = nil
return self
end
--- Unblock a player.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client CLIENT object
-- @param #string PlayerName (optional) Name of the player.
-- @param #string Message (optional) Message to be sent via chat.
-- @return #NET self
function NET:UnblockPlayer(Client,PlayerName,Message)
local name = PlayerName
if Client then
name = Client:GetPlayerName()
elseif PlayerName then
name = PlayerName
else
self:F(self.lid.."Unblock: No PlayerName given or not found!")
return self
end
local ucid = self:GetPlayerUCID(Client,name)
self.BlockedPilots[name] = nil
self.BlockedUCIDs[ucid] = nil
local message = Message or self.UnblockMessage
if name then
self:SendChatToPlayer(message,name)
else
self:SendChat(name..": "..message)
end
self:__PlayerUnblocked(1,Client,name)
return self
end
--- Set block chat message.
-- @param #NET self
-- @param #string Text The message
-- @return #NET self
function NET:SetBlockMessage(Text)
self.BlockMessage = Text or "You are blocked from joining. Wait time is: "..self.BlockTime.." seconds!"
return self
end
--- Set block time in seconds.
-- @param #NET self
-- @param #number Seconds Numnber of seconds this block will last. Defaults to 600.
-- @return #NET self
function NET:SetBlockTime(Seconds)
self.BlockTime = Seconds or 600
return self
end
--- Set unblock chat message.
-- @param #NET self
-- @param #string Text The message
-- @return #NET self
function NET:SetUnblockMessage(Text)
self.UnblockMessage = Text or "You are unblocked now and can join again."
return self
end
--- Send chat message.
-- @param #NET self
-- @param #string Message Message to send
-- @param #boolean ToAll (Optional)
-- @return #NET self
function NET:SendChat(Message,ToAll)
if Message then
net.send_chat(Message, ToAll)
end
return self
end
--- Find the PlayerID by name
-- @param #NET self
-- @param #string Name The player name whose ID to find
-- @return #number PlayerID or nil
function NET:GetPlayerIDByName(Name)
if not Name then return nil end
local playerList = self:GetPlayerList()
self:T({playerList})
for i=1,#playerList do
local playerName = net.get_name(i)
self:T({playerName})
if playerName == Name then
return playerList[i]
end
end
return nil
end
--- Find the PlayerID from a CLIENT object.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #number PlayerID or nil
function NET:GetPlayerIDFromClient(Client)
if Client then
local name = Client:GetPlayerName()
local id = self:GetPlayerIDByName(name)
return id
else
return nil
end
end
--- Send chat message to a specific player using the CLIENT object.
-- @param #NET self
-- @param #string Message The text message
-- @param Wrapper.Client#CLIENT ToClient Client receiving the message
-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message
-- @return #NET self
function NET:SendChatToClient(Message, ToClient, FromClient)
local PlayerId = self:GetPlayerIDFromClient(ToClient)
local FromId = self:GetPlayerIDFromClient(FromClient)
if Message and PlayerId and FromId then
net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId))
elseif Message and PlayerId then
net.send_chat_to(Message, tonumber(PlayerId))
end
return self
end
--- Send chat message to a specific player using the player name
-- @param #NET self
-- @param #string Message The text message
-- @param #string ToPlayer Player receiving the message
-- @param #string FromPlayer(Optional) Player sending the message
-- @return #NET self
function NET:SendChatToPlayer(Message, ToPlayer, FromPlayer)
local PlayerId = self:GetPlayerIDByName(ToPlayer)
local FromId = self:GetPlayerIDByName(FromPlayer)
if Message and PlayerId and FromId then
net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId))
elseif Message and PlayerId then
net.send_chat_to(Message, tonumber(PlayerId))
end
return self
end
--- Load a specific mission.
-- @param #NET self
-- @param #string Path and Mission
-- @return #boolean success
-- @usage
-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz')
function NET:LoadMission(Path)
local outcome = false
if Path then
outcome = net.load_mission(Path)
end
return outcome
end
--- Load next mission. Returns false if at the end of list.
-- @param #NET self
-- @return #boolean success
function NET:LoadNextMission()
local outcome = false
outcome = net.load_next_mission()
return outcome
end
--- Return a table of players currently connected to the server.
-- @param #NET self
-- @return #table PlayerList
function NET:GetPlayerList()
local plist = nil
plist = net.get_player_list()
return plist
end
--- Returns the playerID of the local player. Always returns 1 for server.
-- @param #NET self
-- @return #number ID
function NET:GetMyPlayerID()
return net.get_my_player_id()
end
--- Returns the playerID of the server. Currently always returns 1.
-- @param #NET self
-- @return #number ID
function NET:GetServerID()
return net.get_server_id()
end
--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client.
-- @param #string Attribute (Optional) The attribute to obtain. List see below.
-- @return #table PlayerInfo or nil if it cannot be found
-- @usage
-- Table holds these attributes:
--
-- 'id' : playerID
-- 'name' : player name
-- 'side' : 0 - spectators, 1 - red, 2 - blue
-- 'slot' : slotID of the player or
-- 'ping' : ping of the player in ms
-- 'ipaddr': IP address of the player, SERVER ONLY
-- 'ucid' : Unique Client Identifier, SERVER ONLY
--
function NET:GetPlayerInfo(Client,Attribute)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_player_info(tonumber(PlayerID), Attribute)
else
return nil
end
end
--- Get player UCID from player CLIENT object or player name. Provide either one.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client object to be used.
-- @param #string Name Player name to be used.
-- @return #boolean success
function NET:GetPlayerUCID(Client,Name)
local PlayerID = nil
if Client then
PlayerID = self:GetPlayerIDFromClient(Client)
elseif Name then
PlayerID = self:GetPlayerIDByName(Name)
else
self:E(self.lid.."Neither client nor name provided!")
end
local ucid = net.get_player_info(tonumber(PlayerID), 'ucid')
return ucid
end
--- Kicks a player from the server. Can display a message to the user.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #string Message (Optional) The message to send.
-- @return #boolean success
function NET:Kick(Client,Message)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID) ~= 1 then
return net.kick(tonumber(PlayerID), Message)
else
return false
end
end
--- Return a statistic for a given client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #number StatisticID The statistic to obtain
-- @return #number Statistic or nil
-- @usage
-- StatisticIDs are:
--
-- net.PS_PING (0) - ping (in ms)
-- net.PS_CRASH (1) - number of crashes
-- net.PS_CAR (2) - number of destroyed vehicles
-- net.PS_PLANE (3) - ... planes/helicopters
-- net.PS_SHIP (4) - ... ships
-- net.PS_SCORE (5) - total score
-- net.PS_LAND (6) - number of landings
-- net.PS_EJECT (7) - of ejects
--
-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects
function NET:GetPlayerStatistic(Client,StatisticID)
local PlayerID = self:GetPlayerIDFromClient(Client)
local stats = StatisticID or 0
if stats > 7 or stats < 0 then stats = 0 end
if PlayerID then
return net.get_stat(tonumber(PlayerID),stats)
else
return nil
end
end
--- Return the name of a given client. Effectively the same as CLIENT:GetPlayerName().
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #string Name or nil if not obtainable
function NET:GetName(Client)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
return net.get_name(tonumber(PlayerID))
else
return nil
end
end
--- Returns the SideId and SlotId of a given client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @return #number SlotID
function NET:GetSlot(Client)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID then
local side,slot = net.get_slot(tonumber(PlayerID))
return side,slot
else
return nil,nil
end
end
--- Force the slot for a specific client.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @param #number SlotID Slot number
-- @return #boolean Success
function NET:ForceSlot(Client,SideID,SlotID)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID) ~= 1 then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' )
else
return false
end
end
--- Force a client back to spectators.
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #boolean Succes
function NET:ReturnToSpectators(Client)
local outcome = self:ForceSlot(Client,0)
return outcome
end
--- Converts a lua value to a JSON string.
-- @param #string Lua Anything lua
-- @return #table Json
function NET.Lua2Json(Lua)
return net.lua2json(Lua)
end
--- Converts a JSON string to a lua value.
-- @param #string Json Anything JSON
-- @return #table Lua
function NET.Lua2Json(Json)
return net.json2lua(Json)
end
--- Executes a lua string in a given lua environment in the game.
-- @param #NET self
-- @param #string State The state in which to execute - see below.
-- @param #string DoString The lua string to be executed.
-- @return #string Output
-- @usage
-- States are:
-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings
-- 'mission': holds current mission
-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API
function NET:DoStringIn(State,DoString)
return net.dostring_in(State,DoString)
end
--- Write an "INFO" entry to the DCS log file, with the message Message.
-- @param #NET self
-- @param #string Message The message to be logged.
-- @return #NET self
function NET:Log(Message)
net.log(Message)
return self
end
--- Get some data of pilots who have currently joined
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client Provide either the client object whose data to find **or**
-- @param #string Name The player name whose data to find
-- @return #table Table of #NET.PlayerData or nil if not found
function NET:GetKnownPilotData(Client,Name)
local name = Name
if Client and not Name then
name = Client:GetPlayerName()
end
if name then
return self.KnownPilots[name]
else
return nil
end
end
--- Status - housekeeping
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterStatus(From,Event,To)
self:T({From,Event,To})
local function HouseHold(tavolo)
local TNow = timer.getTime()
for _,entry in pairs (tavolo) do
if entry >= TNow then entry = nil end
end
end
HouseHold(self.BlockedPilots)
HouseHold(self.BlockedSides)
HouseHold(self.BlockedSlots)
HouseHold(self.BlockedUCIDs)
if self:Is("Running") then
self:__Status(-60)
end
return self
end
--- Stop the event functions
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterRun(From,Event,To)
self:T({From,Event,To})
self:HandleEvent(EVENTS.PlayerEnterUnit,self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterAircraft,self._EventHandler)
self:HandleEvent(EVENTS.PlayerLeaveUnit,self._EventHandler)
self:HandleEvent(EVENTS.PilotDead,self._EventHandler)
self:HandleEvent(EVENTS.Ejection,self._EventHandler)
self:HandleEvent(EVENTS.Crash,self._EventHandler)
self:HandleEvent(EVENTS.SelfKillPilot,self._EventHandler)
self:__Status(-10)
end
--- Stop the event functions
-- @param #NET self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #NET self
function NET:onafterStop(From,Event,To)
self:T({From,Event,To})
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PlayerLeaveUnit)
self:UnHandleEvent(EVENTS.PilotDead)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.Crash)
self:UnHandleEvent(EVENTS.SelfKillPilot)
return self
end
-------------------------------------------------------------------------------
-- End of NET
-------------------------------------------------------------------------------
end

View File

@@ -237,22 +237,23 @@ end
-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetVec3()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
--local status, vec3 = pcall(
-- function()
-- local vec3 = DCSPositionable:getPoint()
-- return vec3
--end
--)
local vec3 = DCSPositionable:getPoint()
if vec3 then
--if status then
return vec3
else
self:E( "ERROR: Cannot get vec3!" )
end
--else
--self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } )
--end
end
-- ERROR!
self:E( { "Cannot GetVec3", Positionable = self, Alive = self:IsAlive() } )
self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } )
return nil
end
@@ -394,6 +395,35 @@ function POSITIONABLE:GetCoordinate()
return nil
end
--- Triggers an explosion at the coordinates of the positionable.
-- @param #POSITIONABLE self
-- @param #number power Power of the explosion in kg TNT. Default 100 kg TNT.
-- @param #number delay (Optional) Delay of explosion in seconds.
-- @return #POSITIONABLE self
function POSITIONABLE:Explode(power, delay)
-- Default.
power=power or 100
-- Check if delay or not.
if delay and delay>0 then
-- Delayed call.
self:ScheduleOnce(delay, POSITIONABLE.Explode, self, power, 0)
else
local coord=self:GetCoord()
if coord then
-- Create an explotion at the coordinate of the positionable.
coord:Explosion(power)
end
end
return self
end
--- Returns a COORDINATE object, which is offset with respect to the orientation of the POSITIONABLE.
-- @param #POSITIONABLE self
-- @param #number x Offset in the direction "the nose" of the unit is pointing in meters. Default 0 m.
@@ -435,6 +465,115 @@ function POSITIONABLE:GetOffsetCoordinate( x, y, z )
return coord
end
--- Returns a COORDINATE object, which is transformed to be relative to the POSITIONABLE. Inverse of @{#POSITIONABLE.GetOffsetCoordinate}.
-- @param #POSITIONABLE self
-- @param #number x Offset along the world x-axis in meters. Default 0 m.
-- @param #number y Offset along the world y-axis in meters. Default 0 m.
-- @param #number z Offset along the world z-axis in meters. Default 0 m.
-- @return Core.Point#COORDINATE The relative COORDINATE with respect to the orientation of the POSITIONABLE.
function POSITIONABLE:GetRelativeCoordinate( x, y, z )
-- Default if nil.
x = x or 0
y = y or 0
z = z or 0
-- Vector from the origin of the map to self.
local selfPos = self:GetVec3()
-- Vectors making up self's local coordinate system.
local X = self:GetOrientationX()
local Y = self:GetOrientationY()
local Z = self:GetOrientationZ()
-- Offset from self to self's position (still in the world coordinate system).
local off = {
x = x - selfPos.x,
y = y - selfPos.y,
z = z - selfPos.z
}
-- The end result
local res = { x = 0, y = 0, z = 0 }
-- Matrix equation to solve:
-- | X.x, Y.x, Z.x | | res.x | | off.x |
-- | X.y, Y.y, Z.y | . | res.y | = | off.y |
-- | X.z, Y.z, Z.z | | res.z | | off.z |
-- Use gaussian elimination to solve the system of equations.
-- https://en.wikipedia.org/wiki/Gaussian_elimination
local mat = {
{ X.x, Y.x, Z.x, off.x },
{ X.y, Y.y, Z.y, off.y },
{ X.z, Y.z, Z.z, off.z }
}
-- Matrix dimensions
local m = 3
local n = 4
-- Pivot indices
local h = 1
local k = 1
while h <= m and k <= n do
local v_max = math.abs( mat[h][k] )
local i_max = h
for i = h,m,1 do
local value = math.abs( mat[i][k] )
if value > v_max then
i_max = i
v_max = value
end
end
if mat[i_max][k] == 0 then
-- Already all 0s, nothing to do.
k = k + 1
else
-- Swap rows h and i_max
local tmp = mat[h]
mat[h] = mat[i_max]
mat[i_max] = tmp
for i = h + 1,m,1 do
-- The scaling factor to use to reduce all values in this column
local f = mat[i][k] / mat[h][k]
mat[i][k] = 0
for j = k+1,n,1 do
mat[i][j] = mat[i][j] - f*mat[h][j]
end
end
h = h + 1
k = k + 1
end
end
-- mat is now in row echelon form:
-- | #, #, #, # |
-- | 0, #, #, # |
-- | 0, 0, #, # |
--
-- and the linear equation is now effectively:
-- | #, #, # | | res.x | | # |
-- | 0, #, # | . | res.y | = | # |
-- | 0, 0, # | | res.z | | # |
-- this resulting system of equations can be easily solved via substitution.
res.z = mat[3][4] / mat[3][3]
res.y = (mat[2][4] - res.z * mat[2][3]) / mat[2][2]
res.x = (mat[1][4] - res.y * mat[1][2] - res.z * mat[1][3]) / mat[1][1]
local coord = COORDINATE:NewFromVec3( res )
-- Return the relative coordinate.
return coord
end
--- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission.
-- @param #POSITIONABLE self
-- @param #number Radius
@@ -703,11 +842,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() } )
@@ -845,6 +984,78 @@ function POSITIONABLE:GetVelocityKNOTS()
return UTILS.MpsToKnots( self:GetVelocityMPS() )
end
--- Returns the true airspeed (TAS). This is calculated from the current velocity minus wind in 3D.
-- @param #POSITIONABLE self
-- @return #number TAS in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetAirspeedTrue()
-- TAS
local tas=0
-- Get current coordinate.
local coord=self:GetCoord()
if coord then
-- Altitude in meters.
local alt=coord.y
-- Wind velocity vector.
local wvec3=coord:GetWindVec3(alt, false)
-- Velocity vector.
local vvec3=self:GetVelocityVec3()
--GS=TAS+WIND ==> TAS=GS-WIND
local tasvec3=UTILS.VecSubstract(vvec3, wvec3)
-- True airspeed in m/s
tas=UTILS.VecNorm(tasvec3)
end
return tas
end
--- Returns the indicated airspeed (IAS).
-- The IAS is calculated from the TAS under the approximation that TAS increases by ~2% with every 1000 feet altitude ASL.
-- @param #POSITIONABLE self
-- @param #number oatcorr (Optional) Outside air temperature (OAT) correction factor. Default 0.017 (=1.7%).
-- @return #number IAS in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetAirspeedIndicated(oatcorr)
-- Get true airspeed.
local tas=self:GetAirspeedTrue()
-- Get altitude.
local altitude=self:GetAltitude()
-- Convert TAS to IAS.
local ias=UTILS.TasToIas(tas, altitude, oatcorr)
return ias
end
--- Returns the horizonal speed relative to eath's surface. The vertical component of the velocity vector is projected out (set to zero).
-- @param #POSITIONABLE self
-- @return #number Ground speed in m/s. Returns 0 if the POSITIONABLE does not exist.
function POSITIONABLE:GetGroundSpeed()
local gs=0
local vel=self:GetVelocityVec3()
if vel then
local vec2={x=vel.x, y=vel.z}
gs=UTILS.Vec2Norm(vel)
end
return gs
end
--- Returns the Angle of Attack of a POSITIONABLE.
-- @param #POSITIONABLE self
-- @return #number Angle of attack in degrees.
@@ -1696,6 +1907,7 @@ do -- Cargo
["tt_DSHK"] = 6,
["HL_KORD"] = 6,
["HL_DSHK"] = 6,
["CCKW_353"] = 16, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers
}
-- Assuming that each passenger weighs 95 kg on average.

View File

@@ -4,7 +4,7 @@
--
-- ### Author: **FlightControl**
--
-- ### Contributions: **Applevangelist**
-- ### Contributions: **Applevangelist**, **funkyfranky**
--
-- ===
--
@@ -12,12 +12,12 @@
-- @image Wrapper_Scenery.JPG
--- @type SCENERY
-- @field #string ClassName
-- @field #string SceneryName
-- @field #DCS.Object SceneryObject
-- @field #number Life0
--- SCENERY Class
-- @type SCENERY
-- @field #string ClassName Name of the class.
-- @field #string SceneryName Name of the scenery object.
-- @field DCS#Object SceneryObject DCS scenery object.
-- @field #number Life0 Initial life points.
-- @extends Wrapper.Positionable#POSITIONABLE
@@ -37,12 +37,16 @@ SCENERY = {
--- Register scenery object as POSITIONABLE.
--@param #SCENERY self
--@param #string SceneryName Scenery name.
--@param #DCS.Object SceneryObject DCS scenery object.
--@param DCS#Object SceneryObject DCS scenery object.
--@return #SCENERY Scenery object.
function SCENERY:Register( SceneryName, SceneryObject )
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
self.SceneryName = SceneryName
self.SceneryObject = SceneryObject
if self.SceneryObject then
self.Life0 = self.SceneryObject:getLife()
else
@@ -53,23 +57,29 @@ end
--- Obtain DCS Object from the SCENERY Object.
--@param #SCENERY self
--@return #DCS.Object DCS scenery object.
--@return DCS#Object DCS scenery object.
function SCENERY:GetDCSObject()
return self.SceneryObject
end
--- Get current life points from the SCENERY Object.
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120%
-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success
-- criteria for a bombing task.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife()
local life = 0
if self.SceneryObject then
life = self.SceneryObject:getLife()
if life > self.Life0 then
self.Life0 = math.floor(life * 1.2)
end
end
return life
end
--- Get current initial life points from the SCENERY Object.
--- Get initial life points of the SCENERY Object.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife0()
@@ -90,7 +100,7 @@ function SCENERY:IsDead()
return self:GetLife() < 1 and true or false
end
--- Get the threat level of a SCENERY object. Always 0.
--- Get the threat level of a SCENERY object. Always 0 as scenery does not pose a threat to anyone.
--@param #SCENERY self
--@return #number Threat level 0.
--@return #string "Scenery".
@@ -106,11 +116,13 @@ end
--@param #number Radius (optional) Search radius around coordinate, defaults to 100
--@return #SCENERY Scenery Object or `nil` if it cannot be found
function SCENERY:FindByName(Name, Coordinate, Radius)
local radius = Radius or 100
local name = Name or "unknown"
local scenery = nil
BASE:T({name, radius, Coordinate:GetVec2()})
---
-- @param Core.Point#COORDINATE coordinate
-- @param #number radius
@@ -166,6 +178,7 @@ function SCENERY:FindByZoneName( ZoneName )
zone = ZONE:FindByName(ZoneName)
end
local _id = zone:GetProperty('OBJECT ID')
BASE:T("Object ID ".._id)
if not _id then
-- this zone has no object ID
BASE:E("**** Zone without object ID: "..ZoneName.." | Type: "..tostring(zone.ClassName))
@@ -225,3 +238,10 @@ function SCENERY:FindAllByZoneName( ZoneName )
end
end
end
--- SCENERY objects cannot be destroyed via the API (at the punishment of game crash).
--@param #SCENERY self
--@return #SCENERY self
function SCENERY:Destroy()
return self
end

View File

@@ -25,6 +25,7 @@
-- @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.
-- @field #table DCSUnit The DCS Unit object from the API.
-- @extends Wrapper.Controllable#CONTROLLABLE
--- For each DCS Unit object alive within a running mission, a UNIT wrapper object (instance) will be created within the global _DATABASE object (an instance of @{Core.Database#DATABASE}).
@@ -89,9 +90,10 @@
--
-- @field #UNIT
UNIT = {
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
ClassName="UNIT",
UnitName=nil,
GroupName=nil,
DCSUnit = nil,
}
@@ -124,6 +126,7 @@ function UNIT:Register( UnitName )
if group then
self.GroupName=group:getName()
end
self.DCSUnit = unit
end
-- Set event prio.
@@ -167,7 +170,7 @@ end
--- Get the DCS unit object.
-- @param #UNIT self
-- @return DCS#Unit
-- @return DCS#Unit The DCS unit object.
function UNIT:GetDCSObject()
local DCSUnit = Unit.getByName( self.UnitName )
@@ -175,7 +178,11 @@ function UNIT:GetDCSObject()
if DCSUnit then
return DCSUnit
end
if self.DCSUnit then
return self.DCSUnit
end
return nil
end
@@ -229,7 +236,7 @@ function UNIT:ReSpawnAt( Coordinate, Heading )
SpawnGroupTemplate.y = Coordinate.z
self:F( #SpawnGroupTemplate.units )
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do
local GroupUnit = UnitData -- #UNIT
self:F( GroupUnit:GetName() )
if GroupUnit:IsAlive() then
@@ -313,26 +320,48 @@ function UNIT:IsActive()
return nil
end
--- Returns if the Unit is alive.
-- If the Unit is not alive, nil is returned.
-- If the Unit is alive and active, true is returned.
-- If the Unit is alive but not active, false is returned.
--- Returns if the unit is exists in the mission.
-- If not even the DCS unit object does exist, `nil` is returned.
-- If the unit object exists, the value of the DCS API function [isExist](https://wiki.hoggitworld.com/view/DCS_func_isExist) is returned.
-- @param #UNIT self
-- @return #boolean `true` if Unit is alive and active. `false` if Unit is alive but not active. `nil` if the Unit is not existing or is not alive.
-- @return #boolean Returns `true` if unit exists in the mission.
function UNIT:IsExist()
local DCSUnit = self:GetDCSObject() -- DCS#Unit
if DCSUnit then
local exists = DCSUnit:isExist()
return exists
end
return nil
end
--- Returns if the Unit is alive.
-- If the Unit is not alive/existent, `nil` is returned.
-- If the Unit is alive and active, `true` is returned.
-- If the Unit is alive but not active, `false`` is returned.
-- @param #UNIT self
-- @return #boolean Returns `true` if Unit is alive and active, `false` if it exists but is not active and `nil` if the object does not exist or DCS `isExist` function returns false.
function UNIT:IsAlive()
self:F3( self.UnitName )
local DCSUnit = self:GetDCSObject() -- DCS#Unit
if DCSUnit then
local UnitIsAlive = DCSUnit:isExist() and DCSUnit:isActive()
if DCSUnit and DCSUnit:isExist() then
local UnitIsAlive = DCSUnit:isActive()
return UnitIsAlive
end
return nil
end
--- Returns if the Unit is dead.
-- @param #UNIT self
-- @return #boolean `true` if Unit is dead, else false or nil if the unit does not exist
function UNIT:IsDead()
return not self:IsAlive()
end
--- Returns the Unit's callsign - the localized string.
-- @param #UNIT self
@@ -626,25 +655,27 @@ function UNIT:IsFuelSupply()
return false
end
--- Returns the unit's group if it exist and nil otherwise.
--- Returns the unit's group if it exists and nil otherwise.
-- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
function UNIT:GetGroup()
self:F2( self.UnitName )
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() )
self:F2( self.UnitName )
local UnitGroup = GROUP:FindByName(self.GroupName)
if UnitGroup then
return UnitGroup
else
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local grp = DCSUnit:getGroup()
if grp then
local UnitGroup = GROUP:FindByName( grp:getName() )
return UnitGroup
end
end
end
return nil
end
-- Need to add here functions to check if radar is on and which object etc.
--- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign.
-- DCS Units spawned with the @{Core.Spawn#SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name.
-- The spawn sequence number and unit number are contained within the name after the '#' sign.
@@ -667,20 +698,27 @@ end
--- Returns the Unit's ammunition.
-- @param #UNIT self
-- @return DCS#Unit.Ammo Table with ammuntion of the unit (or nil). This can be a complex table!
-- @return DCS#Unit.Ammo Table with ammuntion of the unit (or nil). This can be a complex table!
function UNIT:GetAmmo()
self:F2( self.UnitName )
local DCSUnit = self:GetDCSObject()
if DCSUnit then
--local status, unitammo = pcall(
-- function()
-- local UnitAmmo = DCSUnit:getAmmo()
-- return UnitAmmo
--end
--)
--if status then
--return unitammo
--end
local UnitAmmo = DCSUnit:getAmmo()
return UnitAmmo
end
return nil
end
--- Sets the Unit's Internal Cargo Mass, in kg
-- @param #UNIT self
-- @param #number mass to set cargo to
@@ -913,7 +951,7 @@ function UNIT:GetLife()
local DCSUnit = self:GetDCSObject()
if DCSUnit then
if DCSUnit and DCSUnit:isExist() then
local UnitLife = DCSUnit:getLife()
return UnitLife
end
@@ -965,6 +1003,24 @@ function UNIT:GetDamageRelative()
return 1
end
--- Returns the current value for an animation argument on the external model of the given object.
-- Each model animation has an id tied to with different values representing different states of the model.
-- Animation arguments can be figured out by opening the respective 3d model in the modelviewer.
-- @param #UNIT self
-- @param #number AnimationArgument Number corresponding to the animated part of the unit.
-- @return #number Value of the animation argument [-1, 1]. If draw argument value is invalid for the unit in question a value of 0 will be returned.
function UNIT:GetDrawArgumentValue(AnimationArgument)
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local value = DCSUnit:getDrawArgumentValue(AnimationArgument or 0)
return value
end
return 0
end
--- Returns the category of the #UNIT from descriptor. Returns one of
--
-- * Unit.Category.AIRPLANE

View File

@@ -0,0 +1,839 @@
--- **Wrapper** - Weapon functions.
--
-- ## Main Features:
--
-- * Convenient access to DCS API functions
-- * Track weapon and get impact position
-- * Get launcher and target of weapon
-- * Define callback function when weapon impacts
-- * Define callback function when tracking weapon
-- * Mark impact points on F10 map
-- * Put coloured smoke on impact points
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper%20-%20Weapon).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Wrapper.Weapon
-- @image Wrapper_Weapon.png
--- WEAPON class.
-- @type WEAPON
-- @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 DCS#Weapon weapon The DCS weapon object.
-- @field #string name Name of the weapon object.
-- @field #string typeName Type name of the weapon.
-- @field #number category Weapon category 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB, 4=TORPEDO (Weapon.Category.X).
-- @field #number categoryMissile Missile category 0=AAM, 1=SAM, 2=BM, 3=ANTI_SHIP, 4=CRUISE, 5=OTHER (Weapon.MissileCategory.X).
-- @field #number coalition Coalition ID.
-- @field #number country Country ID.
-- @field DCS#Desc desc Descriptor table.
-- @field DCS#Unit launcher Launcher DCS unit.
-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit.
-- @field #string launcherName Name of launcher unit.
-- @field #number dtTrack Time step in seconds for tracking scheduler.
-- @field #function impactFunc Callback function for weapon impact.
-- @field #table impactArg Optional arguments for the impact callback function.
-- @field #function trackFunc Callback function when weapon is tracked and alive.
-- @field #table trackArg Optional arguments for the track callback function.
-- @field DCS#Vec3 vec3 Last known 3D position vector of the tracked weapon.
-- @field DCS#Position3 pos3 Last known 3D position and direction vector of the tracked weapon.
-- @field Core.Point#COORDINATE coordinate Coordinate object of the weapon. Can be used in other classes.
-- @field DCS#Vec3 impactVec3 Impact 3D vector.
-- @field Core.Point#COORDINATE impactCoord Impact coordinate.
-- @field #number trackScheduleID Tracking scheduler ID. Can be used to remove/destroy the scheduler function.
-- @field #boolean tracking If `true`, scheduler will keep tracking. Otherwise, function will return nil and stop tracking.
-- @field #boolean impactMark If `true`, the impact point is marked on the F10 map. Requires tracking to be started.
-- @field #boolean impactSmoke If `true`, the impact point is marked by smoke. Requires tracking to be started.
-- @field #number impactSmokeColor Colour of impact point smoke.
-- @field #boolean impactDestroy If `true`, destroy weapon before impact. Requires tracking to be started and sufficiently small time step.
-- @field #number impactDestroyDist Distance in meters to the estimated impact point. If smaller, then weapon is destroyed.
-- @field #number distIP Distance in meters for the intercept point estimation.
-- @field Wrapper.Unit#UNIT target Last known target.
-- @extends Wrapper.Positionable#POSITIONABLE
--- *In the long run, the sharpest weapon of all is a kind and gentle spirit.* -- Anne Frank
--
-- ===
--
-- # The WEAPON Concept
--
-- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions.
--
-- Probably, the most striking highlight is that the position of the weapon can be tracked and its impact position can be determined, which is not
-- possible with the native DCS scripting engine functions.
--
-- **Note** that this wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`.
-- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data.
--
-- # Tracking
--
-- The status of the weapon can be tracked with the @{#WEAPON.StartTrack} function. This function will try to determin the position of the weapon in (normally) relatively
-- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 seconds.
--
-- Once the position cannot be retrieved any more, the weapon has impacted (or was destroyed otherwise) and the last known position is safed as the impact point.
-- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions.
--
-- ## Impact Point Marking
--
-- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}.
--
-- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}.
--
-- ## Callback functions
--
-- It is possible to define functions that are called during the tracking of the weapon and upon impact, which help you to customize further actions.
--
-- ### Callback on Impact
--
-- The function called on impact can be set with @{#WEAPON.SetFuncImpact}
--
-- ### Callback when Tracking
--
-- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack}
--
-- # Target
--
-- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT
-- but it could also be a STATIC object.
--
-- Also note that the weapon does not always have a target, it can loose a target and re-aquire it and the target might change to another unit.
--
-- You can get the target name with the @{#WEAPON.GetTargetName} function.
--
-- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function.
--
-- # Category
--
-- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function.
--
-- You can check if the weapon is a
--
-- * bomb with @{#WEAPON.IsBomb}
-- * rocket with @{#WEAPON.IsRocket}
-- * missile with @{#WEAPON.IsMissile}
-- * shell with @{#WEAPON.IsShell}
-- * torpedo with @{#WEAPON.IsTorpedo}
--
-- # Parameters
--
-- You can get various parameters of the weapon, *e.g.*
--
-- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate}
-- * speed: @{#WEAPON.GetSpeed}
-- * coalition: @{#WEAPON.GetCoalition}
-- * country: @{#WEAPON.GetCountry}
--
-- # Dependencies
--
-- This class is used (at least) in the MOOSE classes:
--
-- * RANGE (to determine the impact points of bombs and missiles)
-- * ARTY (to destroy and replace shells with smoke or illumination)
-- * FOX (to destroy the missile before it hits the target)
--
-- @field #WEAPON
WEAPON = {
ClassName = "WEAPON",
verbose = 0,
}
--- WEAPON class version.
-- @field #string version
WEAPON.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- TODO: Destroy before impact.
-- TODO: Monitor target.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new WEAPON object from the DCS weapon object.
-- @param #WEAPON self
-- @param DCS#Weapon WeaponObject The DCS weapon object.
-- @return #WEAPON self
function WEAPON:New(WeaponObject)
-- Nil check on object.
if WeaponObject==nil then
env.error("ERROR: Weapon object does NOT exist")
return nil
end
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON
-- Set DCS weapon object.
self.weapon=WeaponObject
-- Descriptors containing a lot of info.
self.desc=WeaponObject:getDesc()
-- This gives the object category which is always Object.Category.WEAPON!
--self.category=WeaponObject:getCategory()
-- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X)
self.category = self.desc.category
if self:IsMissile() and self.desc.missileCategory then
self.categoryMissile=self.desc.missileCategory
end
-- Get type name.
self.typeName=WeaponObject:getTypeName() or "Unknown Type"
-- Get name of object. Usually a number like "1234567".
self.name=WeaponObject:getName()
-- Get coaliton of weapon.
self.coalition=WeaponObject:getCoalition()
-- Get country of weapon.
self.country=WeaponObject:getCountry()
-- Get DCS unit of the launcher.
self.launcher=WeaponObject:getLauncher()
-- Get launcher of weapon.
self.launcherName="Unknown Launcher"
if self.launcher then
self.launcherName=self.launcher:getName()
self.launcherUnit=UNIT:Find(self.launcher)
end
-- Init the coordinate of the weapon from that of the launcher.
self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint())
-- Set log ID.
self.lid=string.format("[%s] %s | ", self.typeName, self.name)
-- Set default parameters
self:SetTimeStepTrack()
self:SetDistanceInterceptPoint()
-- Debug info.
local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s",
self.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName)
self:T(self.lid..text)
-- Descriptors.
self:T2(self.desc)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set verbosity level.
-- @param #WEAPON self
-- @param #number VerbosityLevel Level of output (higher=more). Default 0.
-- @return #WEAPON self
function WEAPON:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
return self
end
--- Set track position time step.
-- @param #WEAPON self
-- @param #number TimeStep Time step in seconds when the position is updated. Default 0.01 sec ==> 100 evaluations per second.
-- @return #WEAPON self
function WEAPON:SetTimeStepTrack(TimeStep)
self.dtTrack=TimeStep or 0.01
return self
end
--- Set distance of intercept point for estimated impact point.
-- If the weapon cannot be tracked any more, the intercept point from its last known position and direction is used to get
-- a better approximation of the impact point. Can be useful when using longer time steps in the tracking and still achieve
-- a good result on the impact point.
-- It uses the DCS function [getIP](https://wiki.hoggitworld.com/view/DCS_func_getIP).
-- @param #WEAPON self
-- @param #number Distance Distance in meters. Default is 50 m. Set to 0 to deactivate.
-- @return #WEAPON self
function WEAPON:SetDistanceInterceptPoint(Distance)
self.distIP=Distance or 50
return self
end
--- Mark impact point on the F10 map. This requires that the tracking has been started.
-- @param #WEAPON self
-- @param #boolean Switch If `true` or nil, impact is marked.
-- @return #WEAPON self
function WEAPON:SetMarkImpact(Switch)
if Switch==false then
self.impactMark=false
else
self.impactMark=true
end
return self
end
--- Put smoke on impact point. This requires that the tracking has been started.
-- @param #WEAPON self
-- @param #boolean Switch If `true` or nil, impact is smoked.
-- @param #number SmokeColor Color of smoke. Default is `SMOKECOLOR.Red`.
-- @return #WEAPON self
function WEAPON:SetSmokeImpact(Switch, SmokeColor)
if Switch==false then
self.impactSmoke=false
else
self.impactSmoke=true
end
self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red
return self
end
--- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object.
-- Note that this can be called many times per second. So be careful for performance reasons.
-- @param #WEAPON self
-- @param #function FuncTrack Function called during tracking.
-- @param ... Optional function arguments.
-- @return #WEAPON self
function WEAPON:SetFuncTrack(FuncTrack, ...)
self.trackFunc=FuncTrack
self.trackArg=arg or {}
return self
end
--- Set callback function when weapon impacted or was destroyed otherwise, *i.e.* cannot be tracked any more.
-- @param #WEAPON self
-- @param #function FuncImpact Function called once the weapon impacted.
-- @param ... Optional function arguments.
-- @return #WEAPON self
--
-- @usage
-- -- Function called on impact.
-- local function OnImpact(Weapon)
-- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon")
-- end
--
-- -- Set which function to call.
-- myweapon:SetFuncImpact(OnImpact)
--
-- -- Start tracking.
-- myweapon:Track()
--
function WEAPON:SetFuncImpact(FuncImpact, ...)
self.impactFunc=FuncImpact
self.impactArg=arg or {}
return self
end
--- Get the unit that launched the weapon.
-- @param #WEAPON self
-- @return Wrapper.Unit#UNIT Laucher unit.
function WEAPON:GetLauncher()
return self.launcherUnit
end
--- Get the target, which the weapon is guiding to.
-- @param #WEAPON self
-- @return Wrapper.Object#OBJECT The target object, which can be a UNIT or STATIC object.
function WEAPON:GetTarget()
local target=nil --Wrapper.Object#OBJECT
if self.weapon then
-- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase.
local object=self.weapon:getTarget()
if object then
-- Get object category.
local category=object:getCategory()
--Target name
local name=object:getName()
-- Debug info.
self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category))
if category==Object.Category.UNIT then
target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then
target=STATIC:FindByName(name, false)
elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
end
end
end
return target
end
--- Get the distance to the current target the weapon is guiding to.
-- @param #WEAPON self
-- @param #function ConversionFunction (Optional) Conversion function from meters to desired unit, *e.g.* `UTILS.MpsToKmph`.
-- @return #number Distance from weapon to target in meters.
function WEAPON:GetTargetDistance(ConversionFunction)
-- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT
local distance=nil
if target then
-- Current position of target.
local tv3=target:GetVec3()
-- Current position of weapon.
local wv3=self:GetVec3()
if tv3 and wv3 then
distance=UTILS.VecDist3D(tv3, wv3)
if ConversionFunction then
distance=ConversionFunction(distance)
end
end
end
return distance
end
--- Get name the current target the weapon is guiding to.
-- @param #WEAPON self
-- @return #string Name of the target or "None" if no target.
function WEAPON:GetTargetName()
-- Get the target of the weapon.
local target=self:GetTarget() --Wrapper.Unit#UNIT
local name="None"
if target then
name=target:GetName()
end
return name
end
--- Get velocity vector of weapon.
-- @param #WEAPON self
-- @return DCS#Vec3 Velocity vector with x, y and z components in meters/second.
function WEAPON:GetVelocityVec3()
local Vvec3=nil
if self.weapon then
Vvec3=self.weapon:getVelocity()
end
return Vvec3
end
--- Get speed of weapon.
-- @param #WEAPON self
-- @param #function ConversionFunction (Optional) Conversion function from m/s to desired unit, *e.g.* `UTILS.MpsToKmph`.
-- @return #number Speed in meters per second.
function WEAPON:GetSpeed(ConversionFunction)
local speed=nil
if self.weapon then
local v=self:GetVelocityVec3()
speed=UTILS.VecNorm(v)
if ConversionFunction then
speed=ConversionFunction(speed)
end
end
return speed
end
--- Get the current 3D position vector.
-- @param #WEAPON self
-- @return DCS#Vec3 Current position vector in 3D.
function WEAPON:GetVec3()
local vec3=nil
if self.weapon then
vec3=self.weapon:getPoint()
end
return vec3
end
--- Get the current 2D position vector.
-- @param #WEAPON self
-- @return DCS#Vec2 Current position vector in 2D.
function WEAPON:GetVec2()
local vec3=self:GetVec3()
if vec3 then
local vec2={x=vec3.x, y=vec3.z}
return vec2
end
return nil
end
--- Get type name.
-- @param #WEAPON self
-- @return #string The type name.
function WEAPON:GetTypeName()
return self.typeName
end
--- Get coalition.
-- @param #WEAPON self
-- @return #number Coalition ID.
function WEAPON:GetCoalition()
return self.coalition
end
--- Get country.
-- @param #WEAPON self
-- @return #number Country ID.
function WEAPON:GetCountry()
return self.country
end
--- Get DCS object.
-- @param #WEAPON self
-- @return DCS#Weapon The weapon object.
function WEAPON:GetDCSObject()
-- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE
return self.weapon
end
--- Get the impact position vector. Note that this might not exist if the weapon has not impacted yet!
-- @param #WEAPON self
-- @return DCS#Vec3 Impact position vector (if any).
function WEAPON:GetImpactVec3()
return self.impactVec3
end
--- Get the impact coordinate. Note that this might not exist if the weapon has not impacted yet!
-- @param #WEAPON self
-- @return Core.Point#COORDINATE Impact coordinate (if any).
function WEAPON:GetImpactCoordinate()
return self.impactCoord
end
--- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS...
-- @param #WEAPON self
-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`.
function WEAPON:InAir()
local inAir=nil
if self.weapon then
inAir=self.weapon:inAir()
end
return inAir
end
--- Check if weapon object (still) exists.
-- @param #WEAPON self
-- @return #boolean If `true`, the weapon object still exists and `false` otherwise. Returns `nil` if weapon object itself is `nil`.
function WEAPON:IsExist()
local isExist=nil
if self.weapon then
isExist=self.weapon:isExist()
end
return isExist
end
--- Check if weapon is a bomb.
-- @param #WEAPON self
-- @return #boolean If `true`, is a bomb.
function WEAPON:IsBomb()
return self.category==Weapon.Category.BOMB
end
--- Check if weapon is a missile.
-- @param #WEAPON self
-- @return #boolean If `true`, is a missile.
function WEAPON:IsMissile()
return self.category==Weapon.Category.MISSILE
end
--- Check if weapon is a rocket.
-- @param #WEAPON self
-- @return #boolean If `true`, is a missile.
function WEAPON:IsRocket()
return self.category==Weapon.Category.ROCKET
end
--- Check if weapon is a shell.
-- @param #WEAPON self
-- @return #boolean If `true`, is a shell.
function WEAPON:IsShell()
return self.category==Weapon.Category.SHELL
end
--- Check if weapon is a torpedo.
-- @param #WEAPON self
-- @return #boolean If `true`, is a torpedo.
function WEAPON:IsTorpedo()
return self.category==Weapon.Category.TORPEDO
end
--- Destroy the weapon object.
-- @param #WEAPON self
-- @param #number Delay Delay before destroy in seconds.
-- @return #WEAPON self
function WEAPON:Destroy(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0)
else
if self.weapon then
self:T(self.lid.."Destroying Weapon NOW!")
self:StopTrack()
self.weapon:destroy()
end
end
return self
end
--- Start tracking the weapon until it impacts or is destroyed otherwise.
-- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is
-- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many
-- calculations per second need to be carried out.
-- @param #WEAPON self
-- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec.
-- @return #WEAPON self
function WEAPON:StartTrack(Delay)
-- Set delay before start.
Delay=math.max(Delay or 0.001, 0.001)
-- Debug info.
self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay))
-- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds.
self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay)
return self
end
--- Stop tracking the weapon by removing the scheduler function.
-- @param #WEAPON self
-- @param #number Delay (Optional) Delay in seconds before the tracking is stopped.
-- @return #WEAPON self
function WEAPON:StopTrack(Delay)
if Delay and Delay>0 then
-- Delayed call.
self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0)
else
if self.trackScheduleID then
timer.removeFunction(self.trackScheduleID)
end
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Track weapon until impact.
-- @param #WEAPON self
-- @param DCS#Time time Time in seconds.
-- @return #number Time when called next or nil if not called again.
function WEAPON:_TrackWeapon(time)
-- Debug info.
if self.verbose>=20 then
self:I(self.lid..string.format("Tracking at T=%.5f", time))
end
-- Protected call to get the weapon position. If the position cannot be determined any more, the weapon has impacted and status is nil.
local status, pos3= pcall(
function()
local point=self.weapon:getPosition()
return point
end
)
if status then
-------------------------------
-- Weapon is still in exists --
-------------------------------
-- Update last known position.
self.pos3 = pos3
-- Update last known vec3.
self.vec3 = UTILS.DeepCopy(self.pos3.p)
-- Update coordinate.
self.coordinate:UpdateFromVec3(self.vec3)
-- Keep on tracking by returning the next time below.
self.tracking=true
-- Callback function.
if self.trackFunc then
self.trackFunc(self, unpack(self.trackArg))
end
-- Verbose output.
if self.verbose>=5 then
-- Get vec2 of current position.
local vec2={x=self.vec3.x, y=self.vec3.z}
-- Land hight.
local height=land.getHeight(vec2)
-- Current height above ground level.
local agl=self.vec3.y-height
-- Estimated IP (if any)
local ip=self:_GetIP(self.distIP)
-- Distance between positon and estimated impact.
local d=0
if ip then
d=UTILS.VecDist3D(self.vec3, ip)
end
-- Output.
self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d))
end
else
---------------------------
-- Weapon does NOT exist --
---------------------------
-- Get intercept point from position (p) and direction (x) in 50 meters.
local ip = self:_GetIP(self.distIP)
if self.verbose>=10 and ip then
-- Output.
self:I(self.lid.."Got intercept point!")
-- Coordinate of the impact point.
local coord=COORDINATE:NewFromVec3(ip)
-- Mark coordinate.
coord:MarkToAll("Intercept point")
coord:SmokeBlue()
-- Distance to last known pos.
local d=UTILS.VecDist3D(ip, self.vec3)
-- Output.
self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d))
end
-- Safe impact vec3.
self.impactVec3=ip or self.vec3
-- Safe impact coordinate.
self.impactCoord=COORDINATE:NewFromVec3(self.vec3)
-- Mark impact point on F10 map.
if self.impactMark then
self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName))
end
-- Smoke on impact point.
if self.impactSmoke then
self.impactCoord:Smoke(self.impactSmokeColor)
end
-- Call callback function.
if self.impactFunc then
self.impactFunc(self, unpack(self.impactArg or {}))
end
-- Stop tracking by returning nil below.
self.tracking=false
end
-- Return next time the function is called or nil to stop the scheduler.
if self.tracking then
if self.dtTrack and self.dtTrack>=0.00001 then
return time+self.dtTrack
else
return nil
end
end
return nil
end
--- Compute estimated intercept/impact point (IP) based on last known position and direction.
-- @param #WEAPON self
-- @param #number Distance Distance in meters. Default 50 m.
-- @return DCS#Vec3 Estimated intercept/impact point. Can also return `nil`, if no IP can be determined.
function WEAPON:_GetIP(Distance)
Distance=Distance or 50
local ip=nil --DCS#Vec3
if Distance>0 and self.pos3 then
-- Get intercept point from position (p) and direction (x) in 20 meters.
ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3
end
return ip
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -34,6 +34,7 @@ Core/MarkerOps_Base.lua
Core/Astar.lua
Core/Condition.lua
Core/TextAndSound.lua
Core/Pathline.lua
Wrapper/Object.lua
Wrapper/Identifiable.lua
@@ -46,6 +47,8 @@ Wrapper/Static.lua
Wrapper/Airbase.lua
Wrapper/Scenery.lua
Wrapper/Marker.lua
Wrapper/Weapon.lua
Wrapper/Net.lua
Cargo/Cargo.lua
Cargo/CargoUnit.lua

View File

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