Compare commits

...

1479 Commits

Author SHA1 Message Date
Frank d52e427238 Merge branch 'FF/Ops' into FF/Fox2 2022-05-09 08:54:01 +02:00
Frank f8e5efc874 Merge branch 'develop' into FF/Ops 2022-05-09 08:41:55 +02:00
Frank 2c61c695d5 Update Chief.lua
- Added target in zone check to abort missions
2022-05-09 08:41:33 +02:00
Frank b6e806adbc Update FlightGroup.lua
-No despawn after player landed.
2022-05-08 00:29:36 +02:00
Applevangelist e74aa160af Group - small change 2022-05-07 19:42:10 +02:00
Clément Fassot 6793228fbe Add an option to display arrows if clusters are moving (#1728) 2022-05-07 15:32:48 +02:00
Applevangelist e83ea8124e GROUP - change to GetUnits(n) to make it more robust, now returns first alive unit,actually. Similar changes to GetHeading() 2022-05-07 11:54:48 +02:00
Frank 313f99d09d OPS
**AUFTRAG**
- Added option to specify mission type for escorts, *e.g.* `AUFTRAG.Type.SEAD`

**LEGION** and **COMMANDER**
- Improved handling of SEAD escorts.
2022-05-06 21:50:23 +02:00
Applevangelist 28de292107 ##AWACS Alpha 0.0.17
* Added RejectZone (foreign border)
* Option to suppress screen output
* Option to add google key in SetSRS
* Some corrections to callouts
* Menu option to show base info
* WIP - Basic player task assignment
2022-05-06 16:36:57 +02:00
Applevangelist 10efb98eb3 Fallout fixes 2022-05-06 11:44:59 +02:00
Applevangelist 8ac2241372 AI Patrol - life check on route 2022-05-06 10:36:51 +02:00
Applevangelist 092434a103 UNIT Register - small fix for trains 2022-05-06 08:01:18 +02:00
Applevangelist a777997f79 AI_CAP - more fallout from the dead units in the API 2022-05-05 17:40:17 +02:00
Applevangelist 36cf7f718b AI_AIR - restrict AB on RTB 2022-05-05 16:41:15 +02:00
Applevangelist 7989267d51 AI/ZONE - Some fixes for units unreachable 2022-05-05 12:08:43 +02:00
Applevangelist ef4398a5f1 SRS - adding volume setting and a test on OS and IO available
OpsGroup - added volume options for `SetSRS`
2022-05-05 11:35:27 +02:00
Applevangelist 0da8afc108 SRS - adding volume setting and a test on OS and IO available 2022-05-05 08:57:15 +02:00
Frank 0e74d36227 Update Range.lua
**RANGE** v2.4.0
- Copy from updated
2022-05-04 22:40:22 +02:00
Applevangelist 290cc151bc SET - further event related stuff not working any more
AWACS - update
2022-05-04 18:10:20 +02:00
Applevangelist 21f93aa7e8 AWACS - 0.0.12 2022-05-04 13:29:14 +02:00
Applevangelist 04c77e9760 Some fixes to make the SpawnScheduled work again 2022-05-04 09:58:30 +02:00
Frank c6da6544da Merge pull request #1725 from FlightControl-Master/FF/Ops
Dead Event
2022-05-04 09:49:37 +02:00
Frank 94d3e38bc7 Merge branch 'develop' into FF/Ops 2022-05-04 09:47:30 +02:00
Frank 5fcf992920 Dead Event
**EVENT**
- Improved handling of delayed dead event

**SPAWN**
- Improved handling of delayed dead event
2022-05-04 09:46:00 +02:00
Frank 22cc9eabc0 Merge pull request #1724 from Ked57/develop
Replace log MESSAGE when updating cluster markers in INTEL with a log
2022-05-03 23:31:15 +02:00
Clément Fassot b191c0d341 Replace log MESSAGE when updating cluster markers in INTEL with a log (#1) 2022-05-03 23:08:26 +02:00
Frank d2d431ce2e OPS (#1723)
**INTEL**
- Added nil check for cluster position update

**OPSGROUP**
- Improved fire at point task to take only max avail shots and relative shot amount
- Fixed tasks not executed after teleport
2022-05-03 09:41:22 +02:00
Frank 3a6b58ea8c Merge branch 'develop' into FF/Ops 2022-05-03 09:25:15 +02:00
Frank 2d8fc40307 OPS
**INTEL**
- Added nil check for cluster position update

**OPSGROUP**
- Improved fire at point task to take only max avail shots and relative shot amount
- Fixed tasks not executed after teleport
2022-05-03 09:23:53 +02:00
Applevangelist 68dce2f247 extra nil checks for dead units 2022-05-03 08:46:23 +02:00
Applevangelist bc03eb2e65 FIFO - FIFO:HasUniqueID(UniqueID)
GROUP - added GROUP:IsPlayer()
INTEL - added clustering by flight deck (10'k ft) for AIR
SET - added option to NOT set the cargo bay weight limit automatically on SET_GROUP:AddGroup( group, DontSetCargoBayLimit )
2022-05-02 17:18:48 +02:00
Frank f725709e5b Merge pull request #1721 from FlightControl-Master/FF/Ops
OPS
2022-04-29 22:02:24 +02:00
Frank ba591c9dc5 OPS
**AUFTRAG**
- Added new type `NOTHING` (similar to `ALERT5` but for ground/naval)
2022-04-29 22:00:23 +02:00
Applevangelist def5d33055 GROUP - making GetCoordinate() a bit more resilient
POINT - slight changes to ToStringBRAANATO
2022-04-29 18:48:27 +02:00
Applevangelist 1aaa51c4be COORDINATE - added bogey option to COORDINATE:ToStringBRAANATO(FromCoordinate,Bogey,Spades) 2022-04-29 12:19:06 +02:00
Applevangelist 37206bcc83 Range - added nil check for playerData 2022-04-29 12:10:16 +02:00
Frank c2b86bac4b Update Airboss.lua
- Fixed DATABASE:GetFlightGroup to DATABASE:GetOpsGroup
2022-04-28 20:52:47 +02:00
Frank 02dad179cd Recovery tanker 2022-04-28 20:50:30 +02:00
Applevangelist adb94f8773 Nicefy docs 2022-04-28 17:10:49 +02:00
Applevangelist 035110df75 AWACS - removed banner from docs 2022-04-28 16:48:24 +02:00
Applevangelist 5adb65899c USERSOUND - added USERSOUND:ToUnit() 2022-04-28 16:48:07 +02:00
Applevangelist 764a373e7d POSITIONABLE - added POSITIONABLE:MessageToUnit(), and :MessageToSetUnit() 2022-04-28 16:47:42 +02:00
Applevangelist 8114327c7e MESSAGE - added MESSAGE:ToUnit(), altered MESSAGE:ToClient() to message the unit only 2022-04-28 16:46:28 +02:00
Applevangelist 6404417dad luacheck barking 2022-04-28 13:48:48 +02:00
Applevangelist d89cb8f1e9 Added Awacs WIP to working tree, Enums - corrected Hawkeye, added Super_Hercules 2022-04-28 13:11:04 +02:00
Applevangelist dee95c2f08 Update Moose.files 2022-04-28 13:04:22 +02:00
Frank 3a907ec7c2 Merge pull request #1720 from FlightControl-Master/FF/Ops
Ff/ops
2022-04-28 09:19:01 +02:00
Frank 851dff6e8c Update Intelligence.lua
- Added function to add detection group
- Added functions to retrieve contact details
2022-04-28 09:17:33 +02:00
Frank 47e7364976 Merge branch 'develop' into FF/Ops 2022-04-27 22:46:36 +02:00
Frank 41f8207b3a Merge pull request #1719 from FlightControl-Master/FF/Ops
**CHIEF**
- Added resources as parameters to `:AddStrategicZone` function

**COMMANDER**
- Added function to relocate cohorts `:RelocateCohort`

**AUFTRAG**
- Added new type `AIRDEFENSE`
- Added new type `EWR`
- Added option to teleport assets to the mission ingress waypoint via `:SetTeleport`
- Added `:SetRequiredAttribute` and `:SetRequiredProperty` functions
- Added `:SetEmission` function

**LEGION**
- Fixed bug that assets on GCI dont get additional score for INTERCEPT missions
- Assets on ONGUARD or PATROLZONE are not considered for ARTY and GROUNDATTACK missions
- Added option for transport to `RelocateCohort` function
- Ground/naval assets now automatically return when out of ammo

**OPSGROUP**
- Immobile groups are teleported to mission ingress point

**RECOVERYTANKER**
- Added parameter to set TACAN mode/band (e.g. "X")

**GROUP**
- Fixed bug in `:GetSpeedMax` function

**BEACON**
- Allowed TACAN "X" mode for AA
2022-04-27 22:38:34 +02:00
Frank ed0a3a22ab OPS
**CHIEF**
- Added resources as parameters to `:AddStrategicZone` function

**COMMANDER**
- Added function to relocate cohorts `:RelocateCohort`

**AUFTRAG**
- Added new type `AIRDEFENSE`
- Added new type `EWR`
- Added option to teleport assets to the mission ingress waypoint via `:SetTeleport`
- Added `:SetRequiredAttribute` and `:SetRequiredProperty` functions
- Added `:SetEmission` function

**LEGION**
- Fixed bug that assets on GCI dont get additional score for INTERCEPT missions
- Assets on ONGUARD or PATROLZONE are not considered for ARTY and GROUNDATTACK missions
- Added option for transport to `RelocateCohort` function
- Ground/naval assets now automatically return when out of ammo

**OPSGROUP**
- Immobile groups are teleported to mission ingress point

**RECOVERYTANKER**
- Added parameter to set TACAN mode/band (e.g. "X")

**GROUP**
- Fixed bug in `:GetSpeedMax` function

**BEACON**
- Allowed TACAN "X" mode for AA
2022-04-27 22:36:13 +02:00
Applevangelist 3a513da942 Awacs next Alpha 2022-04-27 14:37:38 +02:00
Applevangelist a4f1c60da2 Enums - added a couple of names 2022-04-26 10:09:05 +02:00
Applevangelist de0bf7d814 typo 2022-04-25 10:36:10 +02:00
Applevangelist 71fd72e462 adding ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) (might be called from POSITIONABLE), closes gap for GROUP/UNIT:IsInZone(Zone_Polygon)
GROUP - better docu
2022-04-25 09:59:44 +02:00
Applevangelist 619cc3c047 various 2022-04-24 19:11:08 +02:00
Applevangelist 68c75a4e34 Menu Refresh cleanup 2022-04-24 14:06:24 +02:00
Applevangelist 37671cefa3 GROUP - overwrite GetHeight() inherited from POSITIONABLE with something that is actually working for groups 2022-04-24 13:50:23 +02:00
Applevangelist d9f409069a INTEL - add platform type for AIR contacts (Foxbat ... ), defaults to Bogey for AIR and Unknown for everything else
SRS- added option to set a label for the SRS radio overlay
OpsGroup - added options to use said label, and option to override a frequency for an SRS (TTS) sender
2022-04-24 12:46:11 +02:00
Applevangelist fd5a190490 AWACS Alpha 2022-04-23 18:58:27 +02:00
Applevangelist 2f4d5b32b6 GROUP/UNIT - added :GetNatoReportingName() 2022-04-23 16:41:47 +02:00
Applevangelist 4c7ac68858 UTILS - UTILS.GetReportingName(Typename)
ENUMS - added ENUMS.ReportingName
2022-04-23 15:09:02 +02:00
Applevangelist ad3a3c5266 AWACS a0.0.7 - cleaned up AWACS calls, implemented DECLARE 2022-04-22 20:34:24 +02:00
Applevangelist 51d924c1b8 BRAANato - make bearing a 3digit number 2022-04-22 13:32:34 +02:00
Applevangelist 83491f535a Point - BRAANATO - will return BRA only if no aspect can be generated 2022-04-21 18:56:51 +02:00
Applevangelist 380731a244 AWACS 0.0.6 2022-04-21 18:56:09 +02:00
Applevangelist 60c78da0f6 Utils/Point - Corrections 2022-04-21 12:56:40 +02:00
Frank 2d02f4f962 Merge branch 'develop' into FF/Ops 2022-04-21 08:40:45 +02:00
Frank 982bd89924 Merge branch 'develop' into FF/Ops 2022-04-20 22:03:24 +02:00
Frank 04f60747d9 Merge pull request #1716 from FlightControl-Master/FF/Ops
OPS
2022-04-20 21:51:09 +02:00
Frank 28c107b089 OPS
**AIRWING**
- Added option to start in air `:SetTakeoffAir()`
- Added option to despawn near airbase `:SetDespawnAfterHolding()`
- Added option to despawn after landing `:SetDepawnAfterLanding()`

**SQUADRON**
- Added option to start in air `:SetTakeoffAir()`
- Added option to despawn near airbase `:SetDespawnAfterHolding()`
- Added option to despawn after landing `:SetDepawnAfterLanding()`
- Squadron settings overrule Airwing settings

**OPSGROUP**
- Improved `:Teleport()` function
2022-04-20 21:48:03 +02:00
Applevangelist e6cc8757ac Utils typo 2022-04-20 19:14:19 +02:00
Applevangelist 9169bfc608 COORDINATE - Added Precision to GetDistanceText as an option 2022-04-20 19:04:37 +02:00
Applevangelist 610e33e4a4 COORDINATE - added ToStringBRAANATO (works hopfully ;)) 2022-04-20 18:54:51 +02:00
Applevangelist 77dd40fb4a SET - clarified return value of SET_GROUP:GetAliveSET() (table, not SET)
UTILS - BRAANATO
ZONE - added examples to ZONE_RADIUS:Scan()
2022-04-20 14:01:26 +02:00
Applevangelist 5a63c51316 Utils - docu spelling 2022-04-20 12:45:33 +02:00
Applevangelist eabc8b5854 UTILS - added BearingToCardinal, added ToStringBRAANATO 2022-04-20 12:43:24 +02:00
Applevangelist 1a798886a2 AIRWING - added function to callback AWACS "onafterflightonmission" 2022-04-20 08:33:49 +02:00
Applevangelist 8735dadbca Awacs updates 2022-04-20 08:33:11 +02:00
Applevangelist 2c53ebc0e4 FIFO Added Routines 2022-04-20 08:32:57 +02:00
Applevangelist f93033695d Alpha Updates 2022-04-19 15:38:29 +02:00
Applevangelist 2d0f5817d0 Minor Changes 2022-04-19 15:36:44 +02:00
Applevangelist 2033e22216 AWACS Alpha 0.0.2 (WIP) (#1714)
TBD
2022-04-18 16:57:33 +02:00
Applevangelist bada6f64ae Update Moose.files (#1713) 2022-04-18 12:27:58 +02:00
Applevangelist bc141698c8 FIFO - make own file in Utils
AUFTRAG - Add base missions params for NewNOTHING
Marker - typo
Modules - FiFo added
UTILS - removed FiFo code
2022-04-18 12:27:14 +02:00
Applevangelist 75fddd9349 FIFO/LIFO - Read functions. Count as alternative to GetSize() 2022-04-17 11:25:48 +02:00
Applevangelist b1a417295f TextAndSound - Docu 2022-04-17 11:24:36 +02:00
Applevangelist 0522036b80 Docu 2022-04-16 18:58:43 +02:00
Applevangelist 061469840b FIFO 2022-04-14 15:55:51 +02:00
Applevangelist 6e218ed908 LIFO/FIFO - enforce unique ID 2022-04-14 15:05:49 +02:00
Applevangelist 626b48c3d1 Added LIFO/FIFO:HasUniqueID(UniqueID) 2022-04-14 11:11:18 +02:00
Applevangelist dd957237e2 Nicefy docs 2022-04-14 08:54:42 +02:00
Applevangelist fc9e237dbb UTILS - LiFo/FiFo Stacks 2022-04-14 08:12:02 +02:00
Frank faac265373 Merge pull request #1712 from FlightControl-Master/FF/Ops
Ff/ops
2022-04-13 22:44:52 +02:00
Frank 998935e2a3 Merge branch 'develop' into FF/Ops 2022-04-13 22:40:19 +02:00
Frank 24f8116fce OPS
**ZONE**
- Fixed incorrect fillcolor alpha.

**COORDINATE**
- Fixed incorrect fillcolor alpha.

**AUFTRAG**
- Fixed `CASENHANCED` order of arguments wrong in docs.

**FLEET**
- Improved docs.
- Added ``:SetPathfinding` function

**OPSGROUP**
- Fixed RECON mission behaviour if not random.
- Improved stuck check if engaging.

**LEGION**
- Added weapon type as parameter for selecting cohort assets.

**COHORT**
- Added `:GetMissionRange` function that accounts for long range weapons.
- Added counting of asset ammo.
2022-04-13 22:39:48 +02:00
Applevangelist 3415330871 UTILS - added FiFo 2022-04-13 16:13:02 +02:00
Applevangelist eadeaae6db CTLD Corrected imperial hover message to say ft not m 2022-04-13 15:30:43 +02:00
Penecruz 32bdaf4f24 Update Ops Airboss for develop (#1711)
* Update Airboss.lua

* Revert "Update Airboss.lua"

This reverts commit 61b0f843eb.

* Update Airboss.lua
2022-04-13 09:35:18 +02:00
Applevangelist 7525fe9357 UTILS - corrected door check for AH64 2022-04-12 08:23:37 +02:00
Applevangelist b24f31922f Soundfile - missing backslash 2022-04-10 18:26:27 +02:00
Applevangelist 4c79349b8d MSRS - repair command string for .bat processing 2022-04-10 18:23:20 +02:00
Applevangelist 44f7a27387 AICSAR - missing \ in path 2022-04-10 18:22:31 +02:00
Applevangelist 369425bec1 Some SRS changes 2022-04-10 15:12:51 +02:00
Applevangelist 3dc284335e SoundOutput - Honor self.useSRS to find a path to sound files located inside the miz folder 2022-04-10 14:59:54 +02:00
Applevangelist bfc284994a SRS - port set not honored 2022-04-10 14:59:05 +02:00
Applevangelist 277409a807 Text and Sound: Spelling error 2022-04-10 14:58:47 +02:00
Applevangelist d1f1f14bc3 SRS - add port to docu 2022-04-10 10:18:26 +02:00
Applevangelist 5ee1f5d3af CSAR/AICSAR - added port options for SRS 2022-04-10 09:28:47 +02:00
Frank f4569fb5cc Merge pull request #1710 from FlightControl-Master/FF/Ops
OPS
2022-04-08 22:19:42 +02:00
Frank 4ba0a2dafb OPS
**FLEET v0.0.1**
- New class for naval units corresponding to AIRWING and BRIGADE

**FLOTILLA v0.0.1**
- New class for naval units corresponding to SQUADRON and PLATOON
2022-04-08 22:18:35 +02:00
Applevangelist c2b1c2b1d8 SET - logic correction in :Remove() 2022-04-08 11:26:59 +02:00
Frank 2e119e2efd Merge pull request #1709 from FlightControl-Master/FF/Ops
OPS
2022-04-07 12:39:49 +02:00
Frank 4641918486 OPS
**LEGION**
- Added function `:RelocateCohort()` to relocate cohorts from one legion to another.
2022-04-07 12:11:17 +02:00
Frank 407f6f2b0f Merge pull request #1707 from FlightControl-Master/FF/Ops
OPS
2022-04-04 22:31:33 +02:00
Frank f01d7ecd96 OPS
**AUFTRAG v0.9.1**
- Added new mission type `CARGOTRANSPORT`
2022-04-04 22:30:30 +02:00
Frank e3d987bbdf Update Database.lua
- Static templates saved under first unit name so they can be found.
2022-04-04 16:53:52 +02:00
Applevangelist 89845883d0 Some nil checks to the A2A dispatcher, keeping it from detecting nil units, added locale to Settings and use of it to TextAndSound 2022-04-04 12:57:31 +02:00
Frank 4e7ff94b33 Merge pull request #1706 from FlightControl-Master/FF/Ops
static spawn
2022-04-04 12:16:48 +02:00
Frank 2fb9a1b1d7 static spawn 2022-04-04 12:15:29 +02:00
Applevangelist 5c0ab36662 CTLD - Build object at the first crate location found, not randomly around the helo 2022-04-04 11:29:21 +02:00
Frank fc6dac326d Merge pull request #1705 from FlightControl-Master/FF/Ops
OPS
2022-04-04 00:00:51 +02:00
Frank 426dcff085 OPS
FLIGHTGROUP
- Improved homebase for spawned units
- Enabled helo inAir check due to DCS bug that inAir returns true when spawned at airbase or farp

SET_ZONE
- Added DrawZone() function

ARMYGROUP
- Improved EngageTarget() function

AUFTRAG
- ARMORATTACK is not falling back to GROUNDATTACK
2022-04-03 23:59:43 +02:00
Applevangelist e623f45c04 Update AICSAR.lua (#1704)
Added localization and fitting german (locale "de") texts
2022-04-02 12:49:09 +02:00
Applevangelist 3b9a9cb0fa some code cleanup 2022-04-01 19:37:55 +02:00
Applevangelist c018a86bdb adding missing goshawk variable 2022-04-01 19:37:37 +02:00
Applevangelist 559be38baf Update TextAndSound.lua (#1703)
Slight doc changes
2022-04-01 19:36:00 +02:00
Applevangelist e3e05e5003 Adding TextAndSound (#1702)
Adding TextAndSound localization class
2022-04-01 19:20:35 +02:00
Applevangelist 95649d7d46 Adding localisation class TEXTANDSOUND 2022-04-01 19:17:31 +02:00
Frank 61097de76d Merge pull request #1701 from FlightControl-Master/FF/Ops
OPS
2022-04-01 12:33:31 +02:00
Frank e49fff5028 Update Auftrag.lua 2022-04-01 12:11:28 +02:00
Frank 20ae7945e0 Merge branch 'develop' into FF/Ops 2022-04-01 12:09:33 +02:00
Frank c5a50d23b6 OPS
CHIEF
- Added option to customize reaction on strategic zones

AUFTRAG
- Added new type GROUNDATTACK
2022-04-01 11:17:43 +02:00
Applevangelist ad37e5ca27 AIRBASE, added ["Deir_ez-Zor"] = "Deir ez-Zor", 2022-03-30 12:05:04 +02:00
Frank e8303064b9 Update OpsGroup.lua 2022-03-29 16:32:50 +02:00
Applevangelist f8a577749a OPS - Some changes to ARMORATTACK to update route if target is moving 2022-03-29 12:03:45 +02:00
Applevangelist e113817293 CTLD - docu nicefy 2022-03-29 12:02:35 +02:00
Applevangelist 0d15cdd370 CTLD - corrected error in setting Hercules min and max drop height, added documentation 2022-03-29 08:52:21 +02:00
Applevangelist b5db08eec3 AIRBASE - Add'l AB for the Channels map 2022-03-28 10:13:02 +02:00
Frank c3591c1fae Merge pull request #1699 from FlightControl-Master/FF/Ops
OPS
2022-03-26 22:39:44 +01:00
Frank e1a4d5497a Merge branch 'develop' into FF/Ops 2022-03-26 22:38:20 +01:00
Frank 4e83162adc OPS
CHIEF
- Added `:RemoveStrategicZone()` function
- Added `:SetLimitMission()` function to limit number of total or specific mission types.
- Improved Tactical Overview

OPSZONE
- Improved `Stop()` function
2022-03-26 22:38:03 +01:00
Applevangelist 42c6b8a016 And don't forget Fahrenheit 2022-03-26 14:47:10 +01:00
Applevangelist 48f9d808cf Correcting Celcius to Celsius 2022-03-26 14:42:36 +01:00
Applevangelist 6546c27cf5 OPS - added HOVER mission type 2022-03-25 17:54:02 +01:00
Frank fc0c0f87dc Merge pull request #1697 from FlightControl-Master/FF/Ops
OPS
2022-03-25 12:52:31 +01:00
Frank 9d703c0af6 OPS
- CHIEF: added tactical overview
2022-03-25 12:50:45 +01:00
Frank 24559c475b Merge branch 'develop' into FF/Ops 2022-03-25 11:00:17 +01:00
Frank f92e8a285a OPS
- Added nil check on vec3 for _UndateEngageTarget in ARMYGROUP and NAVYGROUP
2022-03-25 10:27:32 +01:00
Frank 0411120551 OPS
OPSGROUP
- Added damage check for groups that are not alive.
- Added `:ReturnToLegion` function.
- Added `:Teleport` function

LEGION
- Aircraft dont start hot if on Alert5 mission

AUFTRAG
- Added Formation parameter to PATROLZONE (for ground only)

ARMYGROUP
- Set Vee formation for engage group
2022-03-25 09:49:26 +01:00
Applevangelist 5f57d4ddcd docu changes 2022-03-23 07:57:06 +01:00
Applevangelist 4164bbeba8 CSAR/CTLD - added type to script 2022-03-22 10:38:07 +01:00
Frank 14dea99ccd Update Utils.lua
- Removed useless UTILS.GetDate() function
2022-03-22 09:31:16 +01:00
Frank 3ea1881ff5 OPS INTEL v0.3.0
- Added option to detect statics via `INTEL:SetDetectStatics` function.
- Added `INTEL:KnowObject` function to make intel aware of GROUPs or STATICs.
- Improved cluster analysis.
- Changed `NewCluster` event: removed contact as first parameter.
2022-03-20 22:20:09 +01:00
Applevangelist 5192c188f4 AIRBASE - added 10 new AB names in Syria 2022-03-18 09:49:06 +01:00
Applevangelist c1ffa47e9d changed descriptions 2022-03-18 07:59:49 +01:00
Applevangelist 2808a9dcc5 CONTROLLABLE - added SetSpeed() and SetAltitude() from latest release 2022-03-18 07:41:10 +01:00
Applevangelist e1ab6b6c93 CSAR - remove timer check for "open the door" message to make behaviour more realistic 2022-03-14 09:12:06 +01:00
Frank 229868bb20 OPS
AUFTRAG: Trooptransport set pickup radius to 100 meters.
OPSGROUP: enabled pickup radius for trooptransport auftrag
ARMYGOUP: removed GetPathOnRoad as it seems unncessary
2022-03-14 08:32:09 +01:00
Frank 520eb4cd1d OPS
- Improved recovery of retreated groups
2022-03-11 22:36:00 +01:00
Applevangelist 2d0b4d6ae5 SCORING: Corrected calc error in summary scoring functions 2022-03-11 10:19:30 +01:00
Frank 5dae9a197a OPS
- CHIEF: fixed bug in LEGION.RecruitCohortAssets() function call
- COMMANDER: added total weight to LEGION.RecruitCohortAssets() function call
- POSITIONABLE: fixed bug in relFuel calculation for cargo bay size
2022-03-10 10:05:21 +01:00
Frank ff1ebf9775 OPS
- Legion: Improved max number of transport assets.
- OPSGROUP: fixed bugs in speed parameter
2022-03-09 19:07:24 +01:00
Applevangelist e6f388518a Update CTLD.lua (#1693)
minor nil check
2022-03-09 10:28:26 +01:00
Frank 3557706e3a OPS
- Added new AUFTRAG type  CASENHANCED
- Improved capturing of OPSZONES by CHIEF
2022-03-05 14:43:39 +01:00
Applevangelist f3d0d55a2f CTLD - small extra nil check in _GetUnitCargoMass(Unit 2022-03-03 12:34:55 +01:00
Applevangelist b0c2bad1d8 CTLD - small fix for finding crates when using engineers 2022-03-03 11:02:14 +01:00
Applevangelist 53c9ca3b3a SEAD/MANTIS - added Silkworm ASM 2022-03-03 11:02:14 +01:00
Frank e41ba1be45 Troop Transport
- Auftrag TROOPTRANSPORT: troops will wait in a zone and not at the exact position where the carrier lands.
2022-03-02 09:34:18 +01:00
Applevangelist 594febaece CSAR - remove double class 2022-02-21 19:36:13 +01:00
Applevangelist 890dae8ba7 SEAD - adding workaround for AGM_154 which lost target data 2022-02-21 08:35:56 +01:00
Applevangelist 8a3120be39 AUTOLASE - added nil check for CanLase() 2022-02-19 13:26:02 +01:00
Applevangelist 6b4975559d CSAR - added wet feet check if also using csarUsePara (no landing event triggered) 2022-02-18 08:22:57 +01:00
Applevangelist 3326550705 CSAR - added "wet feet" option for a 2nd template to be used over water 2022-02-17 17:41:20 +01:00
Frank 6be0d82fc6 Merge branch 'develop' into FF/Ops 2022-02-16 12:23:03 +01:00
Applevangelist 44ad841d05 CTLD - fix side effects in weight check - fixes #1688 (#1689)
Fixed _FindCratesNearby() to optionally ignore the helo's max load capacity, which prevented CTLD from finding, listing, building crates is weights are used. Fixes #1688
2022-02-16 10:02:44 +01:00
Applevangelist 53f45ace4c CTLD - corrected default weight limits when using CTLD:UnitCapabilities() - was setting loadable weight to zero 2022-02-15 18:07:14 +01:00
Applevangelist 42e9cae876 added back GROUP:GetHighestThreat() 2022-02-15 14:41:45 +01:00
Applevangelist e46922d341 small correction 2022-02-15 07:30:41 +01:00
Applevangelist 21c242ee05 Added CTLD:SetTroopDropZoneRadius(Radius) 2022-02-13 12:07:57 +01:00
Frank 33b7a0cef2 Merge branch 'develop' into FF/Ops 2022-02-08 08:53:03 +01:00
Frank 9cddca1af5 AUFTRAG and WAREHOUSE
- Added check for AUFTRAG that if it is executing and all groups are dead, it's done
- Added check in WAREHOUSE find parking that units need to be alive
2022-02-08 08:52:47 +01:00
Applevangelist 8cdf0d9fce CSAR - CSAR:SpawnCSARAtZone(Zone ...) - Zone can now be a ZONE object as well as a string 2022-02-08 07:49:16 +01:00
Applevangelist a079ac4207 CTLD - allow AFB zones smoke/flare 2022-02-07 12:17:41 +01:00
Applevangelist 099238a2e5 CTLD - Allow option to create sub-category names for crates (normal, repair), added function to create zones from AFB/FARP names directly 2022-02-07 11:54:35 +01:00
Applevangelist 595bc16d92 DETECTION - added 3 missing functions 2022-02-04 08:56:34 +01:00
Applevangelist e9875c8288 small correction 2022-02-04 08:54:22 +01:00
Applevangelist ec600ab672 CTLD - Added Hercules support for crates, troops & vehicles loaded with the help of the ground crew and dropped from the plane. 2022-02-03 09:59:35 +01:00
Applevangelist c3689aee93 AICSAR - added limiter for available helos 2022-02-01 11:47:36 +01:00
Applevangelist 60a728d521 SET - correct error in intersection 2022-02-01 08:03:02 +01:00
Applevangelist 7bd7071ec5 CTLD Docu updates 2022-01-31 11:36:20 +01:00
Applevangelist bb787aa9ca Update Range.lua (#1686)
Target sheet saving abilities as added by @TonyG
2022-01-31 11:27:44 +01:00
Applevangelist d2f629d100 CTLD - added maxweight, load weight checks for crates cargo, template checks 2022-01-31 11:15:24 +01:00
Applevangelist 5d949de0ee Point - fix typos 2022-01-30 09:49:42 +01:00
Applevangelist e21bc8a930 POINT - added option to name and stop smoke/fire 2022-01-30 09:39:03 +01:00
Applevangelist af79f63870 CTLD - small correction for Hercules 2022-01-27 15:56:10 +01:00
Applevangelist 4ff32342e3 AI Dispatchers - add ability to add/remove resources to/from a squad 2022-01-24 09:54:30 +01:00
Applevangelist 98d55faf4e CTLD - added selectable smoke drop color, droppable beacons 2022-01-23 11:37:53 +01:00
Applevangelist bd532ea808 Added Callsigns as per 2.7.9 2022-01-23 11:37:28 +01:00
Applevangelist d4bd57ee70 Autolase - small update on nil check 2022-01-21 10:09:12 +01:00
Applevangelist 90dbfb31a6 DETECTION - corrected call for Vec2 in zone 2022-01-19 07:51:43 +01:00
Applevangelist 55ca34cad3 Added doors check for UH-60L 2022-01-16 17:07:32 +01:00
Applevangelist 36a5e919cd CTLD, CSAR - added support for UH-60L 2022-01-16 11:39:37 +01:00
Applevangelist 3263ba1440 CTLD - make container shape configureable 2022-01-15 11:32:51 +01:00
Tony 9721c19e48 Update Range.lua (#1683)
Addition of self.rangename to results for Rangeboss
2022-01-10 15:10:49 +01:00
Applevangelist de71213eed AICSAR - added SRS and DCS radio capabilities, changed order of messages for initial spawning, documentation 2022-01-06 13:47:47 +01:00
Applevangelist 65abbf9563 AICSAR - added SRS option, made it easy to customize messages 2022-01-04 15:09:47 +01:00
Applevangelist a94098494a Docu updates 2022-01-01 14:57:40 +01:00
Applevangelist ec935356aa Mark visible as deprecated 2021-12-31 17:51:48 +01:00
Applevangelist 6ab419dfad Fix ATC new NTTR AFB names 2021-12-31 17:51:48 +01:00
Applevangelist b1ffcff064 Update Moose.files (#1679) 2021-12-31 15:37:08 +01:00
Applevangelist 4ce09a3845 New Module AI CSAR 2021-12-31 15:36:35 +01:00
Applevangelist 9686e74feb Update Airbase.lua (#1678)
Changed (again) Airport names on the Nevada map
2021-12-30 07:46:14 +01:00
Frank e97e24762d Merge pull request #1676 from FlightControl-Master/Applevangelist-patch-1
Update Range.lua
2021-12-29 15:41:48 +01:00
Applevangelist b3fd0805e8 Update Range.lua 2021-12-29 08:59:45 +01:00
Applevangelist 300092d07d SEAD - fix a rare case when a coordinate of a weapon cannot be found 2021-12-28 08:28:19 +01:00
Applevangelist 56ed3a11d6 Scoring - docu changes 2021-12-28 08:28:19 +01:00
Applevangelist 6938b20ab6 Group - added docu 2021-12-28 08:28:19 +01:00
Frank 77f2cf5089 Merge pull request #1672 from FlightControl-Master/FF/Ops
AIRWING
2021-12-23 19:55:27 +01:00
Frank 19e3b015c5 Merge branch 'develop' into FF/Ops 2021-12-23 19:54:15 +01:00
Frank 2702d0fc2a AIRWING
- Fix for missions not being assigned
2021-12-23 19:54:01 +01:00
Applevangelist 472a4883f0 ZONE: Set down trials for getting a random coordinate on a given land type to 100
Cohort -ensure fallback of mission range
2021-12-22 11:48:42 +01:00
Frank 485e2e18bf Merge pull request #1671 from FlightControl-Master/FF/Ops
OPS
2021-12-21 11:57:09 +01:00
Frank 40c0f69eff OPS
- Airwing added :SetPayloadAmount() and SetAirboss() functions
- AUFTRAG fixed Failure to Failed event name
- CHIEF added statics as targets
- LEGION added check that runway is operational and carrier is not recovering for mission selection
2021-12-21 11:54:55 +01:00
Frank 854a1e5723 Update ArmyGroup.lua
- Fixed speed nil in ofafterUpdateRoute
2021-12-17 15:57:28 +01:00
Applevangelist f4cd28cdb9 fixes 2021-12-17 14:31:06 +01:00
Applevangelist 5bae790dd8 Fix a rare occasion 2021-12-17 14:15:08 +01:00
Frank d1d43c38cd Merge pull request #1668 from FlightControl-Master/FF/Ops
OPS
2021-12-17 13:29:08 +01:00
Frank 7e1552eeb4 Merge branch 'develop' into FF/Ops 2021-12-17 13:27:13 +01:00
Frank 7306bf28e0 OPS
- CHIEF: fixed ground groups deployment
- AUFTRAG: minor fixes
- OPSGROUP: emission option
2021-12-17 13:26:27 +01:00
Applevangelist 07e00a8faf Noise reducing measures 2021-12-15 13:46:33 +01:00
Applevangelist ab93866366 Removing noise 2021-12-14 17:28:16 +01:00
Applevangelist d7801b59e7 OPS - making a couple of modules much less noisy as the logs on a large mission get rather big 2021-12-14 13:40:16 +01:00
Applevangelist ea926f173a CSAR - make beacon length configureable 2021-12-14 09:50:13 +01:00
Applevangelist f6b55da0c6 CSAR - override suppressmessages for menu driven information 2021-12-12 19:48:49 +01:00
Applevangelist ee4e562874 CTLD - corrected landheight of dropped smoke 2021-12-12 19:30:00 +01:00
Applevangelist abfe5ffc45 Typos 2021-12-11 19:41:21 +01:00
Applevangelist 9629b0e1a3 SET - Added Zone Filter for STATIC 2021-12-11 14:24:07 +01:00
Applevangelist 80b03dd194 CTLD Added menu for smoke/flare 2021-12-11 14:13:43 +01:00
Applevangelist 3f92f8d2aa SET - added a couple of zone filters for GROUP, UNIT, CLIENT and PLAYER 2021-12-10 16:02:50 +01:00
Applevangelist 823c94cace BRIGADE - Added function to load platoons back into the field 2021-12-10 12:09:04 +01:00
Frank 421ac6c427 Fixes in AUFTRAG and LEGION
- Object in AUFTRAG:_TargetFromObject(Object) can be nil
- Assets are optimized wrt later mission type for assets on alert5
2021-12-10 10:38:34 +01:00
Applevangelist 72550d6d84 small changes Legion & Auftrag 2021-12-09 17:44:44 +01:00
Applevangelist 72999cc7b0 Update Zone.lua (#1660) 2021-12-09 07:16:29 +01:00
Applevangelist 706cf641b3 Multiple fixes 2021-12-08 19:27:22 +01:00
Applevangelist d42bdb2505 Revert "Ops - Various"
This reverts commit 23ea5d9d06.
2021-12-07 18:13:13 +01:00
Applevangelist 9082054ba9 Revert "legion"
This reverts commit abc69bc838.
2021-12-07 18:10:21 +01:00
Applevangelist abc69bc838 legion 2021-12-07 17:35:56 +01:00
Applevangelist 23ea5d9d06 Ops - Various 2021-12-07 16:09:02 +01:00
Applevangelist 8d009d6366 Some false values seem to be in need of being set explicitly 2021-12-06 15:15:46 +01:00
Applevangelist 8213b51bd6 Update Utils.lua (#1654)
Small bugfix in UTILS.LoadSetOfGroups
2021-12-06 14:56:42 +01:00
Applevangelist a18fc2080d SET - slight change in remove function 2021-12-06 10:26:01 +01:00
Applevangelist 9c0ac0ff57 CHIEF - ArmoredGuard ensure on road 2021-12-04 18:40:14 +01:00
Applevangelist c34dc9f946 CHIEF/AUFTRAG/OPSGROUP Fixed RECON type mission adhereing to paramters adinfinitum and randomly, fixed CAS mission in CHIEF 2021-12-04 16:24:45 +01:00
Applevangelist 1c5c205614 OpsGroup - corrected speed calculation from DCSTask 2021-12-03 16:51:06 +01:00
Applevangelist 897df1b8fa CHIEF - Option to allow ground transport. Set CAS speed to 70% max as Russian Helos are overly slow 2021-12-03 14:27:03 +01:00
Applevangelist 3fb22b7c55 AUFTRAG - correct wrongly corrected speed conversion 2021-12-02 16:07:34 +01:00
Applevangelist 7819a30c57 BRIGADE - Fuel zones landing in the wrong table. Corrected 2021-12-02 12:17:48 +01:00
Applevangelist 37d2c72945 MANTIS - added variable ranges for short, med and long range systems. Altered engage range to 95% and shoradactivation to 25km as defaults
AUFTRAG/CHIEF/OPSGROUP - added Auftrag type ARMOREDGUARD to have an ONGUARD mission for tanks w/o triggering transport by CHIEF
Made classes around CHIEF & AIRBOSS a bit less noisy as it kills my logfile
2021-12-02 10:13:38 +01:00
Applevangelist 5631e2c09f Minor changes 2021-11-29 08:03:58 +01:00
Applevangelist c7ddd6ec35 CSAR - added check smoke/flare distance is valid 2021-11-27 17:27:58 +01:00
Applevangelist 7280ceac32 ARMYGROUP - in tracing, formation flag can be empty 2021-11-26 15:36:46 +01:00
Applevangelist f8b9128d8e CHIEF - Added catch if coalition us given as string 2021-11-26 15:36:09 +01:00
Applevangelist c482f1dccd Corrected typo in Unit.Category.HELICOPTER 2021-11-26 15:35:45 +01:00
Applevangelist 7e306be8aa UTILS - Added functionality to save & load groups and statics information 2021-11-26 15:34:47 +01:00
Frank 035e6913b5 Merge pull request #1643 from FlightControl-Master/Applevangelist-armor-1
CHIEF - Added use of ARMORATTACK
2021-11-24 13:38:25 +01:00
Applevangelist e12f233afc Update Chief.lua 2021-11-24 13:32:39 +01:00
Applevangelist 26c7ff8c18 Update Chief.lua 2021-11-24 13:32:14 +01:00
Frank 5647364293 Merge pull request #1644 from FlightControl-Master/Applevangelist-armor-1-1
OpsGroup - Added support for ARMORATTACK
2021-11-24 13:17:45 +01:00
Frank 06fa585f69 Merge pull request #1642 from FlightControl-Master/Applevangelist-Armor-1
Auftrag - Added type ARMORATTACK
2021-11-24 13:17:13 +01:00
Applevangelist 035eac2f1e OpsGroup - Added support for ARMORATTACK
OpsGroup - Added support for ARMORATTACK
2021-11-24 12:09:07 +01:00
Applevangelist f01889cbf7 CHIEF - Added use of ARMORATTACK 2021-11-24 12:07:03 +01:00
Applevangelist 73025bfd4b Auftrag - Added type ARMORATTACK
Auftrag - Added type ARMORATTACK
2021-11-24 12:06:02 +01:00
Applevangelist aec6dd1246 SEAD/SHORAD - Add AGM-88 player modes handling 2021-11-22 11:37:10 +01:00
Applevangelist ab4411e5bb SEAD - calculate HARM impact point from firing position if no target data is given
SHORAD - ignore HARM AGM-88 is not fired at a specific target
2021-11-21 18:40:04 +01:00
Frank 0ac00efda6 Merge pull request #1641 from FlightControl-Master/FF/Ops
Ff/ops
2021-11-20 22:38:38 +01:00
Frank f32eb8d710 OPS
AUFTRAG
- Added mission task param for RECON, PATROLZONE and TROOPTRANSPORT

FLIGHTGROUP
- Fixed CheckGroupDone if group is engaging and has a paused mission

TARGET
- Added GetThreatLevelMax functions

CHIEF
- Added :AddGciCapZone() function
2021-11-20 22:36:15 +01:00
Frank ec33dfb76c Merge branch 'develop' into FF/Ops 2021-11-20 20:38:40 +01:00
Frank 43dfbac2fa Update Auftrag.lua
- Changed ROE in ESCORT auftrag from OpenFireWeaponFree to OpenFire
2021-11-20 20:38:23 +01:00
Frank 7a483abec2 Merge pull request #1639 from FlightControl-Master/Applevangelist-ArmyGroup-1
Update ArmyGroup.lua
2021-11-20 17:50:36 +01:00
Frank 35e6b6faf4 Merge pull request #1637 from FlightControl-Master/Applevangelist-Chief-1
CHIEF - Updates
2021-11-20 17:49:30 +01:00
Frank 1066b58fb6 Merge pull request #1640 from FlightControl-Master/Applevangelist-OpsZone-2
Update OpsZone.lua
2021-11-20 17:47:43 +01:00
Applevangelist b41b8f251f Update Chief.lua 2021-11-20 17:26:42 +01:00
Applevangelist a1426c3e81 Update OpsZone.lua 2021-11-20 17:12:33 +01:00
Applevangelist aa3967d88f Update ArmyGroup.lua
Added code to obey routing on roads
2021-11-20 17:09:55 +01:00
Applevangelist c610c4cba9 Update Chief.lua
Added some improvements to threat calculation
2021-11-20 17:08:20 +01:00
Applevangelist abb3a2dbd2 CHIEF - Updates
Added alignment to OpsZone Mission table. Added ARTY. Added Tanks for patrol
2021-11-19 16:03:25 +01:00
Frank 1f035301d9 Merge pull request #1636 from IsmaelB83/InboundCallMarshall
Inbound call marshall
2021-11-19 15:48:28 +01:00
Frank ebf7f76d48 Merge pull request #1635 from FlightControl-Master/Applevangelist-OpsZone-1
OpsZone -Mission Table
2021-11-19 15:47:18 +01:00
Ismael 397ab77105 This pull request enhances Airboss class functionality to provide with the
1) Inbound calls both from Players requesting marshal and AI sent to marshal:
"Marshal, [MODEX], marking mom's [BEARING] for [DISTANCE], angels [HEIGHT], state [FUEL_STATE]
2) Commencing call and voiceover (both AI and Players)

For that, two boolean fields have been added to Airboss class to trigger or not extra voice overs:
xtVoiceOvers
xtVoiceOversAI

These fields can be modified trough methods:
AIRBOSS:SetExtraVoiceOvers(true/false)
AIRBOSS:SetExtraVoiceOversAI(true/false)

Note. At the moment both methods initialize to "false" at Airboss:NEW, so in case you want to have this functionality activated, you need to call the methods with (true) when initializing your Airboss class.
The new methods in charge of creating the radio calls are
AIRBOSS:_MarshalAI: for AI flights send to marshal
AIRBOSS:_RequestMarshal: for player flighs requesting marshal from F10 menu

2) AIRBOSS:_CommencingCall(unit, modex), which is called from
AIRBOSS:_ClearForLanding: for AI flights cleared for landing
AIRBOSS:_RequestCommence: for player flights requesting commencing from F10 menu (disregarding if the player call is right or not)

This enhacement of the Airboss class requires 5 new sound files (.ogg) to be included to folder "\Airboss Soundfiles" in your .miz file:
PILOT-Angels.ogg
PILOT-For.ogg
PILOT-MarkingMoms.ogg
PILOT-Marshal.ogg
PILOT-State.ogg
PILOT-Commencing.ogg
2021-11-19 15:31:49 +01:00
Applevangelist f37abc69ea Update OpsZone.lua 2021-11-19 14:29:18 +01:00
Applevangelist ee85b0e057 OpsZone -Mission Table
Added functionality to admin missions attached to an OpsZone (for CHIEF)
2021-11-19 14:20:53 +01:00
Ismael eab28abc86 Subject: Add Player and AI Inbound radio calls to better inmersion in Airboss #16311
This commit enhances Airboss class functionality to provide with the inbound calls both from Players requesting marshal and AI sent to marshal:
- "Marshal, [MODEX], marking mom's [BEARING] for [DISTANCE], angels [HEIGHT], state [FUEL_STATE]

Two new boolean fields have been added to Airboss class to trigger or not the inbound calls:
- inRadioCall
- inRadioCallAI

These fields can be modified trough methods:
- AIRBOSS:SetInboundMessagesPlayer(false)
- AIRBOSS:SetInboundMessagesAI(false)
Note. At the moment both methods initialize to "false" at Airboss:NEW, so in case you want to have this functionality activated, you need to call the methods with (true) when initializing your Airboss class.

This enhacement of the Airboss class requires 5 new sound files (.ogg) to be included to folder "\Airboss Soundfiles" in your .miz file:
- PILOT-Angels.ogg
- PILOT-For.ogg
- PILOT-MarkingMoms.ogg
- PILOT-Marshal.ogg
- PILOT-State.ogg

To get them, please refer to the SOUNDS MOOSE repository or the discord channel. Where they will be available.
2021-11-19 01:11:49 +01:00
Applevangelist 16c5307fc2 COMMANDER - Zone parameter doc not correctly mentioned in Luadocs 2021-11-18 10:58:42 +01:00
Applevangelist 8c8cae1e27 AUFTRAG - wrong speed conversion lines 925/295 corrected to KnotsToKmph 2021-11-18 10:58:13 +01:00
Frank 925c645821 Merge pull request #1633 from FlightControl-Master/FF/Ops
OPS
2021-11-17 17:17:11 +01:00
Frank c1c97a86b7 OPS
- COMMANDER: Added GCICAP zones
- AIRWING: Improved patrol zone counting, fixed bug in NewPatrolPoint
- OPSGROUP: mission speed acknowledged
2021-11-17 17:15:44 +01:00
Applevangelist 1fc1016148 Update Auftrag.lua
Added missionSpeed to Orbit
2021-11-17 16:42:58 +01:00
Frank e73545d296 Merge pull request #1630 from dogjutsu/dogutsu/develop
Fixed some typos of forms of the word 'strategy'.
2021-11-16 22:08:00 +01:00
dogjutsu f2fb7d43d0 Fixed some typos of forms of the word 'strategy'. 2021-11-16 12:45:14 -08:00
Applevangelist 781d421e1c slightly changes message 2021-11-16 15:48:00 +01:00
Applevangelist a3a7464a29 Small fix fox autoshorad parameters 2021-11-16 10:34:42 +01:00
Frank ac936f5650 Merge pull request #1629 from FlightControl-Master/FF/Ops
Scheduler
2021-11-16 10:34:12 +01:00
Frank c0f5dc2dc8 Scheduler
- Fixed bug
2021-11-16 10:33:35 +01:00
Applevangelist 31c113180e MANTIS - Zones - added conflict zones 2021-11-16 09:26:05 +01:00
Applevangelist 239df2deb2 SEAD - Updated TTI calculation for Tomahawk long shots 2021-11-16 09:25:38 +01:00
Applevangelist 31a8c4b0b9 SHORAD - Updates for missile without set target intel 2021-11-15 16:18:21 +01:00
Applevangelist f394bd45c7 SEAD - Updated to handle missiles w/o clear targets 2021-11-15 16:18:21 +01:00
Applevangelist 63999c9ec3 MANTIS - Added Attacker group info on planned/started suppression events 2021-11-15 16:18:21 +01:00
Frank 82c24cee7e Merge pull request #1628 from FlightControl-Master/FF/Ops
scheduler
2021-11-15 10:22:35 +01:00
Frank 5e94b0c009 Update Base.lua 2021-11-15 10:20:34 +01:00
Frank 452ff62134 SCHEDULER 2021-11-15 10:19:21 +01:00
Frank 3df494cacd Merge pull request #1627 from FlightControl-Master/FF/Ops
Ff/ops
2021-11-14 23:31:03 +01:00
Frank d62bb59df7 OPS
- Clean up tracing
2021-11-14 22:12:41 +01:00
Applevangelist b367f9320b CSAR - don't make usePara default. Added coalition check if using the parachute landing event 2021-11-14 13:33:31 +01:00
Applevangelist 4bc3dbbf6c MANTIS - added auto-setup of SHORAD 2021-11-14 13:13:41 +01:00
Applevangelist e7b7e3ac96 Merge pull request #1626 from FlightControl-Master/Applevangelist-unit-2
Wrapper Unit fix missile count
2021-11-14 13:00:35 +01:00
Applevangelist f3c4aea12f Wrapper Unit fix missile count
missile count for SAM and CRUISE types #1624
2021-11-14 12:59:24 +01:00
Frank b9b5938a91 OPS
- Fixed refuel system always true.
2021-11-13 20:08:22 +01:00
Applevangelist e7fdcf0db4 SEAD - Slightly wider escape radius 2021-11-13 19:11:25 +01:00
Applevangelist c1f22aa556 MANTIS - Added support for HighDigitSAM mod, added blindspot management, corrected distinguising between SA-2 and SA-3 2021-11-12 15:27:05 +01:00
Applevangelist 991b4089d6 SHORAD radius clarification 2021-11-12 15:25:25 +01:00
Frank a8132552de OPS
- Fixed bug in egress coord
- Improved barrage mission
- improved pickup at legion
- fixed bug in SetPathfindingOff function
2021-11-11 21:16:48 +01:00
Applevangelist a0323aef19 MANTIS - Some docu updates 2021-11-11 18:34:06 +01:00
Applevangelist a38abc2f7f MANTIS - latest updates 2021-11-11 17:45:25 +01:00
Applevangelist a6beecf510 MANTIS - Added Auto Mode 2021-11-11 17:22:21 +01:00
Applevangelist c5dece0e59 SEAD - make emissions on/off standard 2021-11-11 16:03:52 +01:00
Applevangelist ce935bafc1 Small fix to switch off pattern matching 2021-11-09 09:53:23 +01:00
Applevangelist fd426a96aa File functions 2021-11-09 09:51:31 +01:00
Applevangelist fad8800842 Added Docu 2021-11-09 09:51:05 +01:00
Applevangelist 273a473db1 MANTIS/SEAD make docu Pikes-friendly 2021-11-08 15:21:20 +01:00
Applevangelist a7588e517d SEAD - (re-)added emissions on/off option and ability to link object functions for state changes. 2021-11-08 13:11:58 +01:00
Applevangelist 84eff098eb MANTIS - Added FSM Events for SeadSuppressionPlanned/Start/ENd 2021-11-08 13:11:58 +01:00
Frank f157f3b5d6 OPS
- Fixed mission alt for patrol zone auftrag
2021-11-07 22:32:17 +01:00
Frank 6cd00c60a7 Update OpsGroup.lua
- Fixed waypoint alt for missions
2021-11-07 21:39:38 +01:00
Frank 2ea951db61 Removed WIP classes
- F10MENUE and FLIGHTCONTROL
2021-11-07 19:43:02 +01:00
Frank 9d0bd0aabb Update OpsGroup.lua 2021-11-07 19:39:53 +01:00
Frank 3d88d16b4b Merge branch 'FF/Ops' into FF/OpsDev 2021-11-07 18:24:48 +01:00
Frank 02724dc26d Merge branch 'develop' into FF/Ops 2021-11-07 18:20:27 +01:00
Frank 21a93652cd Update OpsGroup.lua
- Fix for AUFTRAG carried out only after the group has passed all waypoints
2021-11-07 16:08:11 +01:00
Frank fb4caf1a42 OPS
- Improved transport path
2021-11-07 16:03:44 +01:00
Frank 7a2508bf17 OPSTRANSPORT
- Improved pre-defined paths for pickup and transport
2021-11-06 18:10:49 +01:00
Applevangelist eeeab86952 added altered message 2021-11-06 18:07:42 +01:00
Applevangelist a149ff1705 Switch for fratricide and treason (coalition changes) 2021-11-06 15:15:30 +01:00
Applevangelist 2c73c9c815 Merge pull request #1620 from FlightControl-Master/Applevangelist-casevac-1
CSAR additions by Shagrat
2021-11-05 16:31:22 +01:00
Applevangelist f6f29db9f1 CSAR additions by Shagrat
Added functionality for Casevac
2021-11-04 17:23:29 +01:00
Frank c823a68616 OPS
- Fixed several bugs
2021-11-02 21:14:15 +01:00
Applevangelist e7e2184760 Autolase - fixed error when not using a pilotset 2021-11-01 19:46:56 +01:00
Applevangelist 6fe2422bc9 SRS Google addition by rollnthndr 2021-10-31 11:53:53 +01:00
Applevangelist cd62776be6 AIRBOSS additions by Pene 2021-10-31 11:53:24 +01:00
Applevangelist 037357efe9 Merge pull request #1619 from bbirchnz/ctld-crate-leak
fix cleanup of crates on load or build.
2021-10-31 11:31:19 +01:00
Ben Birch 8d3910ea4c fix cleanup of crates on load or build. 2021-10-31 19:37:26 +11:00
Applevangelist 3e5926f706 Speedmax returning 0 not nil 2021-10-30 16:32:59 +02:00
Applevangelist a1d67bf539 Speedmax returning 0 not nil 2021-10-30 16:32:42 +02:00
Applevangelist 3e65457041 doc impro 2021-10-29 18:30:22 +02:00
Applevangelist f70180eb66 SET_BASE - cmpleted GetSetComplement 2021-10-29 10:20:34 +02:00
Applevangelist c0a558e229 Merge pull request #1616 from Zonr0/Zonr0-ExternalToolsFix
Fixes broken external tools
2021-10-28 15:45:02 +02:00
Applevangelist 801069f5ac Update Moose_Create.lua 2021-10-28 15:39:57 +02:00
Frank f9dcac8264 Merge branch 'master' into develop 2021-10-28 10:21:16 +02:00
Frank 902c001aa4 OPS
Improved OPSTRANSPORT
2021-10-28 10:04:01 +02:00
Applevangelist 0cf2a4353d doc build - next try 2021-10-28 09:13:36 +02:00
Applevangelist 13c8cc90f2 doc build fix 2021-10-28 08:41:10 +02:00
Applevangelist db231fafeb Docs build test 2021-10-28 08:12:27 +02:00
Andrew Waugh c3f7deb602 Fixes broken external tools
Several small changes that should hopefully be a nice QoL upgrade for generating the imports and lead to less newbie confusion.

* Adds a lua binary directly to the repository instead of just expecting the user to have installed it via choco. Added binary is 5.4 as that's the lowest 5.x exe that's easily downloaded, and it works fine for what we need.
* Modifies the launch targets to use workspace_loc macros instead of resource_loc macros. Workspace_loc requires the user to have correctly set the name of their project, but that is already stressed in the documentation.  resource_loc was just wrong. (project_loc would cause problems if the user had selected something outside of the moose project before running).
* Modifies launch targets to use the folder structure that the project is actually structured with.
* Adds the include folder and files so Eclipse doesn't explode when they are missing.
* Small modifications to Moose_Create, also includes a path conversion function that in my testing doesn't make a difference eitherway on Windows, but is there for a troubleshooting option.
* Adds courtesy instructions when generating the dynamic include file.
2021-10-27 20:18:10 -07:00
Applevangelist 300e3f2f58 Merge pull request #1615 from FlightControl-Master/Applevangelist-docpatch
Update Task_Cargo_Transport.lua
2021-10-27 13:10:40 +02:00
Applevangelist 513406f0e5 Update Task_Cargo_Transport.lua 2021-10-27 13:09:30 +02:00
Applevangelist 27e21e77f9 Update Task_Cargo_Transport.lua 2021-10-27 11:19:32 +02:00
Applevangelist dadfd803f7 Update Autolase.lua
added docu
2021-10-26 13:58:11 +02:00
Frank 02b0ba2278 OPS
- Improved behaviour of OPSTRANSPORT when cargo or carrier is on mission.
2021-10-25 22:17:56 +02:00
Applevangelist 3e1005aef1 Small correction to silence wall of text when adding tasks to a mission 2021-10-25 16:29:14 +02:00
Applevangelist e2eb13739c Commented out wall of text when joining a unit as a player 2021-10-25 16:28:40 +02:00
Applevangelist c52e30ceae Added smoke color per RECCE 2021-10-25 16:28:11 +02:00
Applevangelist 07754cdf5f Merge pull request #1614 from madmoney99/develop
Forrestal Wire Corrections
2021-10-24 09:14:10 +02:00
madmoney99 c74c475a29 Forrestal Wire Corrections
Tested on MP and SP.  Both were recording incorrect wires on 1/2 and 4 wires regularly with an occasional 3 wire miss.  This tested correct in the Hornet.

TonyG
2021-10-23 16:34:21 -07:00
Applevangelist ad36ab520b Added Bell-47 2021-10-22 17:04:03 +02:00
Applevangelist bc32ff5db9 Added Bell-47 2021-10-22 17:03:57 +02:00
Frank 6009432933 OPS
- NAVYGROUP added engage target
- Barrage improvments
2021-10-22 11:23:29 +02:00
Frank 99eaa37c76 Merge branch 'master' into develop 2021-10-20 19:56:31 +02:00
Frank ed6d5f727a OPS 2021-10-20 19:49:26 +02:00
Frank c6995d6e58 OPS
- Fixed AUFTRAG :Repeat()
- Fixed bug in BRIGADE
- Improved cargo bay calculation in POSITIONABLE (includes rel. fuel)
- Changed carrier event names in OPSGROUP

- New class F10MENU (not sure about it)
2021-10-19 10:19:26 +02:00
Frank cb0f453a8d Merge branch 'FF/Ops' into FF/OpsDev 2021-10-16 13:41:40 +02:00
Frank d9866846ad Merge branch 'FF/MasterDevel' into FF/Ops 2021-10-16 13:40:37 +02:00
Frank de5d9195f3 OPSGROUP
- Cargo bay of element remains after respawn
- Added some docs for CHIEF
2021-10-16 09:32:47 +02:00
Frank fd1fe8562c Merge branch 'develop' into FF/Ops 2021-10-12 22:20:20 +02:00
Frank b1298223aa Merge branch 'master' into develop 2021-10-12 22:18:43 +02:00
Applevangelist c48128d92e CSAR - Added country options for spawned pilots 2021-10-12 08:30:44 +02:00
Frank 0f4d466953 OPS
- Changed mission, legion in MissionAssign functions (LEGION, COMMANDER, CHIEF)
- OPSTRANSPORT: improved TZC selection
- ARMYGROUP needs to
2021-10-11 21:28:35 +02:00
Applevangelist 1b752fcaf6 CTLD - bug fix for open doors 2021-10-11 18:47:39 +02:00
Applevangelist d06db9909c AUTOLASE Small update 2021-10-11 14:11:58 +02:00
Applevangelist 921024035c AUTOLASE - cleanup, added SRS option 2021-10-08 12:33:33 +02:00
Applevangelist 3b1c8c3deb AUTOLASE - added LOS check, maxlasingunits valid per lasing Recce 2021-10-07 18:16:40 +02:00
Applevangelist 69862ab67d CSAR - try to avoid spawning under water 2021-10-07 18:15:50 +02:00
Applevangelist b05c321306 CTLD - added door check for troops 2021-10-07 18:15:13 +02:00
Applevangelist 9b74a58dde UTILS - added door check for Hercules 2021-10-07 18:14:50 +02:00
Applevangelist d112ffaf6a Autolase - better calculation of LoS for lasing, somewhat faster detection 2021-10-07 09:49:06 +02:00
Applevangelist 554f063def INTEL - allow variable update times 2021-10-07 09:46:06 +02:00
Frank f6dce02203 INTEL
- Introduced pcall to getName() of detected objects. Found a problem with an object of ID 5,000,031
2021-10-06 22:03:02 +02:00
Frank 653dfe82fa OPS CHIEF
- Improved target queue for assigning new missions
2021-10-06 13:25:57 +02:00
Frank 5dbf743052 OPS
- Fixed a couple of minor bugs.
- FC added _PlayerAbortInbound function
2021-10-05 23:59:24 +02:00
Applevangelist f7e7e2e41c ZONE_CAPTURE_COALITION - fix an error when monitoring hits but the Event Function delivers SCENERY as a hit UNIT 2021-10-05 19:07:59 +02:00
Applevangelist b28b4db7fd docu fixes 2021-10-05 19:06:49 +02:00
Applevangelist c34f30e4a3 Docu fixes 2021-10-05 19:06:40 +02:00
Applevangelist aa7f11185d Small update 2021-10-05 19:06:30 +02:00
Frank 1790d19809 FC 2021-10-03 23:09:45 +02:00
Frank fa6fbca67b OPS
- Added tanker and CAP zones
2021-10-03 21:33:33 +02:00
Applevangelist cd79c57a27 AUTOLASE - added Cancel FSM to stop, docu changes, option to notify pilots in the set only 2021-10-02 14:45:37 +02:00
Applevangelist 4c3a97e2b2 Autolase! 2021-10-01 17:29:22 +02:00
Applevangelist f126dceeae Merge pull request #1610 from FlightControl-Master/Applevangelist-Autolase-2
Update Moose.files
2021-10-01 17:26:50 +02:00
Applevangelist 414521acc5 Merge pull request #1611 from FlightControl-Master/Applevangelist-autolase-3
Update Modules.lua
2021-10-01 17:26:40 +02:00
Applevangelist 9147422340 Merge pull request #1609 from FlightControl-Master/Applevangelist-Autolase-1
Create Autolase.lua
2021-10-01 17:26:29 +02:00
Applevangelist 920646ea74 Update Modules.lua
added autolase
2021-10-01 17:24:25 +02:00
Applevangelist c57d839927 Update Moose.files
Add Autolase.lua
2021-10-01 17:22:58 +02:00
Applevangelist e7fd5db2c2 Create Autolase.lua
Added Class AUTOLASE
2021-10-01 17:20:17 +02:00
Applevangelist b76486ef5f CSAR - added parameters for hovering rescue 2021-10-01 14:52:25 +02:00
Applevangelist 5c06584676 UTILS - correction door check MI-8 2021-10-01 14:52:00 +02:00
Frank 2ae2ee64be OPS
- Lots of updates and improvements
2021-10-01 12:04:15 +02:00
Applevangelist 17ba35e237 INTEL - also set isship and isground attributes on contacts 2021-10-01 10:44:25 +02:00
Applevangelist 53e98e70aa CTLD: added user-friendly function to inject static cargos: CTLD:InjectStaticFromTemplate(Zone, Template, Mass) 2021-10-01 10:43:49 +02:00
Applevangelist 359e18eb58 RADIO - delete frequency check 2021-09-30 08:07:42 +02:00
Applevangelist 3377459df5 Added option for silent mission addition 2021-09-29 16:57:45 +02:00
Frank 3e30d15405 Merge branch 'FF/Ops' into FF/OpsDev 2021-09-29 09:57:52 +02:00
Frank 2b50cf2243 Merge branch 'develop' into FF/Ops 2021-09-29 09:55:20 +02:00
Frank b72e2b6bf9 Merge branch 'master' into develop 2021-09-29 09:34:37 +02:00
Frank c7a2b34f59 Update Warehouse.lua
- Disabled fuel check.
2021-09-29 08:15:22 +02:00
Frank b48958995a OPS
- Fixed oscillation between UpdateRoute and CheckGroupDone in FLIGHTGROUP
- Improvements regarding capturing zones.
- Reduced log output
2021-09-28 23:26:33 +02:00
Applevangelist 58c496eacd GROUP - GROUP:GetAmmunition() - fix to also return bomb count correctly 2021-09-28 16:52:18 +02:00
Applevangelist 7bd867c9dd SEAD - Docu changes for Intellisense 2021-09-28 16:51:35 +02:00
Frank be0558849c OPS
- Added rearming zones
- Improved rearming
2021-09-27 16:11:03 +02:00
Applevangelist a1c7ec2ac9 Fix "local" error 2021-09-27 15:49:03 +02:00
Applevangelist 8bf073c0c6 Small fix for Airbase Parking Spot Finder 2021-09-26 09:51:02 +02:00
Frank 994689f05a OPS
- Improved transport assignments
2021-09-25 20:18:16 +02:00
Applevangelist 3e10f9f451 bugfix 2021-09-24 18:38:58 +02:00
Frank 972fa9f674 OPS
- Fixed bug in WAREHOUSE isShip
- FLIGHTGROUP added damage and ammo checks that trigger FSM events
- Improved engage detected targets
- Improved OPSZONE
2021-09-24 14:47:36 +02:00
Applevangelist 8e64d8e334 CTLD: Added mass and canCargo Flag on spawned statics. Use .enableslingload = true to use 2021-09-24 11:06:54 +02:00
Applevangelist eccd81d40a SPAWNSTATIC - bigfix on canCargo (not:isCargo) and mass (not transported into template) when spawning 2021-09-24 11:05:42 +02:00
Applevangelist 649136f98f small updates, fixes 2021-09-23 12:43:28 +02:00
Frank d7dae1366d OPS
- Improved OPSTRANSPORT for LEGION assets.
2021-09-22 19:27:33 +02:00
Applevangelist 19e2ebcd03 CTLD - Added statics as cargo (#1601)
.... and the ability to load and save them alongside your buildable crates.
2021-09-22 15:55:10 +02:00
Applevangelist 021c907d16 SET - Added SET_CLIENT:CountAlive() 2021-09-21 07:46:21 +02:00
Applevangelist 8ac3e28fdf CTLD - fix SAVE/LOAD when using no path on calls 2021-09-21 07:46:05 +02:00
Frank c5af279730 OPS
- Fixed some stuff for Egress coordinate
- Fixed some bugs in OPSTRANPORT if disembark carriers are specified
2021-09-20 22:47:51 +02:00
Applevangelist e9377e93f1 Postionable - Added Cargo Seats to VAB Mephisto 2021-09-20 14:20:09 +02:00
Applevangelist 6747c69511 CTLD Ability to fire an event on injection 2021-09-20 14:19:26 +02:00
Frank 9b6cae6c49 Update Commander.lua 2021-09-19 22:45:58 +02:00
Frank 7148bd1d42 OPS Escort
- Refined functions to recruit escort assets.
2021-09-19 22:10:52 +02:00
Frank 73940fffc6 Update Legion.lua
- Fix that assets did not get removed
2021-09-18 18:47:06 +02:00
Frank 09015449cd OPS Recruting
- Removed obsolete recruiting functions from code
2021-09-18 18:23:22 +02:00
Frank 4dfdb99731 OPS
- Recruit assets with new function (LEGION, COMMANDER, CHIEF)
2021-09-18 18:09:41 +02:00
Frank bbbca04066 OPS Legion
- Added new global function to recruit assets.
2021-09-18 00:11:04 +02:00
Frank e755bba608 OPS
- Clean up on "CheckMissionType" and "CheckMissionCapability" functions ==> moved to AUFTRAG as global functions.
2021-09-17 16:59:43 +02:00
Frank 9161cec238 OPS
- Simplified asset selection by using just one routine for LEGION, COMMANDER and CHIEF
2021-09-16 21:30:36 +02:00
Applevangelist dc14b60fcc bug fixing 2021-09-16 15:09:51 +02:00
Applevangelist da625d51f6 Merge pull request #1599 from FlightControl-Master/Applevangelist-CTLD-1
CTLD - Added persistance
2021-09-16 13:44:54 +02:00
Applevangelist ce69055758 CTLD - Added persistance
-- ## 6. Save and load back units - persistance
-- 
-- You can save and later load back units dropped or build 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:
-- 
--              my_ctld.enableLoadSave = true -- allow auto-saving and loading of files
--              my_ctld.saveinterval = 600 -- save every 10 minutes
--              my_ctld.filename = "missionsave.csv" -- example filename
--              my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
--  
--  Then use an initial load at the beginning of your mission:
--  
--            my_ctld:__Load(10)
--            
-- **Caveat:**
-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved.
2021-09-16 13:43:39 +02:00
Frank 589ebd5bca CHIEF
- Improved asset selection
2021-09-16 11:02:49 +02:00
Frank 35b50e1a9d OPSZONE and CHIEF 2021-09-14 23:51:03 +02:00
Frank f038564b1b OPSTRANSPORT
- Added Cancel feature. First steps...
- Fixed a couple of other little bugs.
2021-09-13 16:04:16 +02:00
Frank 1d0eb9806d COMMANDER
- Added OPS transport (untested)
2021-09-13 08:31:00 +02:00
Applevangelist c50a9bd318 CTLD - small bug fix on stock removal 2021-09-12 17:37:21 +02:00
Applevangelist cd9006d29b CTLD - added alternative crate spawn by @mousepilot. Add menu item to list stock.Injected troops will not lead to cargo type duplication. 2021-09-11 15:20:36 +02:00
Applevangelist d3b88f9065 bug fixing 2021-09-11 10:03:32 +02:00
Frank 6a6cb1961d OPS LEGION
- Improved asset selection for transports
2021-09-10 11:37:53 +02:00
Frank 884c51a69a OPSTRANSPORT
- Improved assignment to multiple legions.
2021-09-10 00:32:15 +02:00
Frank 6f126e6cd4 OPSCARGO
- fixes for Legion assignment
2021-09-09 13:35:10 +02:00
Frank b0c2e5409a OPS AUFTRAG
- Added Alert 5 mission
- Added Fuel supply mission
- Added Ammo supply mission
2021-09-08 11:49:27 +02:00
Applevangelist 9516ec3cd2 CSAR: Gardening cleanup 2021-09-07 19:53:05 +02:00
Frank aecb92ccd3 OPSGROUP
- Added EPLRS option
- EPLRS is on if group has datalink capability
2021-09-07 11:17:20 +02:00
Frank bdf13f29f7 OPS
- COMMANDER and LEGION clean up of obsolete stuff for recruiting assets.
2021-09-06 23:40:58 +02:00
Frank 81120abfcb OPS
- LEGION and COMMANDER: Recruit assets for mission
2021-09-06 23:24:26 +02:00
Applevangelist 08a8170990 CTLD - improved crate spawning, added beacons / smoke / flare for ships, added documentation 2021-09-05 18:05:08 +02:00
Frank 316ea910c2 Update Cohort.lua
- Fixed some stuff for combat readiness check
2021-09-05 00:23:12 +02:00
Frank 0b5edfc21f OPSGROUP
- Ad infinitum fixes
- Turn into wind fixes
2021-09-04 23:51:17 +02:00
Applevangelist 76c08095ab CTLD - added country variable for crates 2021-09-03 18:38:51 +02:00
Frank 538864519e OPSGROUP
- Fixes that ARMY and NAVYGROUPS only go to first waypoint
- Fixes for PATROL and RECON missions that waypoints are added at the end
- Some other stuff
- Still need to fix adinfinitum
2021-09-03 18:20:28 +02:00
Applevangelist cd6631049b CSAR - Removed message for dead pilots 2021-09-03 14:16:30 +02:00
Applevangelist 079f8030b7 CTLD - added unit parameter to place crates in correct distance. Refactored location calculation for crates. 2021-09-03 14:16:11 +02:00
Frank ad0b32c0ee OPS
- Found and fixed bugs for ARMY and NAVY groups, which caused only one waypoint to be processed
- Added Duration for AUFTRAG
- Fixed bug in auftrag if no legion was assigned and mission was canceled at opsgroup level
- Trying (again) to include the whole route for ARMY and NAVY when UpdateRoute
- Simpler task function for passing waypoint

STILL a lot to do/check!
2021-09-03 00:05:58 +02:00
Applevangelist 8235b51a14 Bug fixing 2021-09-02 18:04:39 +02:00
Frank 1bd5e7472f OPSTRANSPORT v0.4.2
- Some fixed.
- All tested demo missions look nice
2021-09-02 08:30:54 +02:00
Applevangelist 7433a7144c CTLD Added method to inject troops into the field. 2021-09-01 13:33:43 +02:00
Frank 27632ecdd9 OPSTRANSPORT v0.4.1
- Fixed a couple of bugs
- Lots of other fixes and improvements
2021-09-01 00:07:54 +02:00
Frank 0b1e25b073 OPSTRANSPORT v0.4
- Multiple transport zone combos (TZC)
- Other issues solved.
- Demo miz D-day landing is still not working correctly.
2021-08-30 23:45:40 +02:00
Applevangelist 19a93f0026 MANTIS - allow SEAD padding in Mantis 2021-08-28 13:58:06 +02:00
Applevangelist 3256a6d596 SEAD - added time-back-on padding as variable 2021-08-28 13:57:15 +02:00
Frank 28ddfa5243 OPSTRANSPORT
- Fist working steps towards multiple pickup/deploy zone combos.
2021-08-28 00:17:16 +02:00
Applevangelist 05c560be3b CTLD - added delay after unsuccessful build or repair, relocate after done 2021-08-27 18:51:54 +02:00
Applevangelist 8ddd810fe2 SEAD - Refactor and enable delayed switch-off 2021-08-27 18:50:40 +02:00
Applevangelist f20760bae5 CSAR - Remove some noise 2021-08-27 18:48:18 +02:00
Applevangelist cea7320a9b UNIT/GROUP - added function to get the skill of a unit. SEAD - added functionality to calculate time-2-impact of HARMS and adjust behaviour accordingly 2021-08-27 14:56:50 +02:00
Frank fe6826016c Update Chief.lua 2021-08-27 11:59:34 +02:00
Frank f0167b3e88 OPS
- FSM pseudo function cleanup
- Fixed bug when mission is cancelled
2021-08-27 11:31:12 +02:00
Applevangelist 0edbf7f517 Added Engineering (Infantry) Troops 2021-08-27 09:14:54 +02:00
Frank a8a8dcff3f OPS Chief
- Removed mission queue. Now done via COMMANDER
- MissionCancel is buggy
2021-08-26 23:24:11 +02:00
Frank 1b0ad13529 OPS Commander 2021-08-26 21:24:47 +02:00
Frank 418d6c882c Update OpsGroup.lua 2021-08-25 17:46:00 +02:00
Frank 1e6899c40b OPS 2021-08-25 17:20:17 +02:00
Frank 259b201e65 OPS
Good version solving a few bugs and adding some new stuff.
2021-08-24 23:10:38 +02:00
Applevangelist 8fb113a04e CTLD - 1) added ability to limit stock. 2) Stopping Herc Pilots to try and build on the fly (look at you, Gunterlund! ;) ) 2021-08-24 14:16:11 +02:00
Frank c6ebbc6122 OPS
Strange version buggy
2021-08-23 23:34:15 +02:00
Applevangelist 755903a08e Changed priority to show bom target height in ft, in absence of player settings 2021-08-22 11:57:49 +02:00
Applevangelist 34e4825cd0 Merge pull request #1591 from bbirchnz/develop
CTLD: fix FOB units overwriting last instance, Unit Cargo Mass not working on first load
2021-08-22 08:32:18 +02:00
Ben Birch a68eeae9bd Merge remote-tracking branch 'origin/develop' into develop 2021-08-22 12:54:25 +10:00
Ben Birch e4e31b554e CTLD: fix FOB units overwriting last instance, Unit Cargo Mass not working on first load 2021-08-22 12:53:55 +10:00
Frank eba6e3f5f1 OPS Transport+Legion, Navy 2021-08-22 00:31:51 +02:00
Frank d73ebaca76 OPS
- Lots of stuff
2021-08-21 00:58:28 +02:00
Applevangelist 94167152e5 CTLD - added ability to spawn crates on ships 2021-08-20 12:33:10 +02:00
Applevangelist 830f76e909 Cleanup UTF-8 Mess 2021-08-18 18:00:38 +02:00
Applevangelist 8ae04944fe Fix for SAM pattern matching not working 2021-08-18 14:59:58 +02:00
Applevangelist c55b8d29f7 Fix for degree sign extra char 2021-08-18 11:51:41 +02:00
Applevangelist 30623f7d38 AIRBOSS / ATIS deleted extra char before degree char. Small correction for CSAR landings at Airports 2021-08-18 09:28:34 +02:00
Frank 16964520df OPS
- Lots of stuff to assign missions to multiple legions.
2021-08-18 00:25:18 +02:00
Frank 3050fdb3c3 OPS Zone 2021-08-15 00:28:03 +02:00
Frank 77fc2c5cf1 Update OpsGroup.lua 2021-08-12 11:41:30 +02:00
Frank b21c3ed4e9 OPS Cleanup 2021-08-12 11:07:28 +02:00
Frank 93f4b345c5 OPS Generalization 2021-08-12 00:17:55 +02:00
Frank 88e59a2739 OPS
- Ground ops
2021-08-11 10:40:16 +02:00
Frank 86bb256bf1 OPS
- Added Cohort and Legion classes
- Added Platoon and Brigade classes
2021-08-10 00:40:17 +02:00
Applevangelist 9a05f5bc93 Merge pull request #1589 from FlightControl-Master/Applevangelist-patch-5
CSAR fix for #1588
2021-08-09 09:18:51 +02:00
Applevangelist bce28cf389 CSAR fix for #1588
Fixes #1588
2021-08-09 09:06:15 +02:00
Frank 629c5e7739 AUFTRAG
- Recon Mission
2021-08-06 23:16:48 +02:00
Frank ed402e2f5f OPS Chief 2021-08-06 10:01:23 +02:00
Frank 4a56c7523d Wingcommander 2021-08-05 12:44:43 +02:00
Frank 218d9f48b0 Merge branch 'FF/Ops' into FF/OpsDev 2021-08-05 00:24:23 +02:00
Frank f67cf99477 OPS and SCHEDULER 2021-08-05 00:23:25 +02:00
Applevangelist 2201b2adda Update Utils.lua
Corrected self to BASE
2021-08-04 11:07:47 +02:00
Applevangelist 6b36421bce Merge pull request #1585 from FlightControl-Master/Applevangelist-patch-4
Update CSAR.lua
2021-08-04 08:04:21 +02:00
Frank 8c573c65d4 Update FlightControl.lua 2021-08-03 22:37:27 +02:00
Applevangelist 730dabd9cf Update CSAR.lua 2021-08-03 07:16:42 +02:00
Frank 69175e1b19 FC 2021-08-02 23:34:42 +02:00
Frank a5ad58e516 FC 2021-08-02 21:57:53 +02:00
Frank 82b4631341 Flightcontrol 2021-08-02 18:41:33 +02:00
Frank a1c9c1538d Merge branch 'FF/Ops' into FF/OpsDev 2021-08-02 11:59:32 +02:00
Frank 1a53f58540 OPS 2021-08-02 11:57:45 +02:00
Applevangelist 1b414b840e added AP check on landing/rescue 2021-07-31 15:50:50 +02:00
Applevangelist 642cc0e98f Added self.FARPRescueDistance reduced landing check to one call 2021-07-31 13:05:51 +02:00
Applevangelist 63431bb54b Couple of changes to make finding the distance to nearest pilot more stable 2021-07-30 17:30:32 +02:00
Frank d64de26ded OPS
- many fixes and improvements
2021-07-29 13:43:29 +02:00
Applevangelist 8b45067226 Various patches for AI_CARGO 2021-07-29 12:41:27 +02:00
Applevangelist 061032e3d7 Transition cleanup for AI Helicopter, deleted speed test bc in 20% of cases AI lands shit, which stops the FSM 2021-07-28 18:31:39 +02:00
Applevangelist 82432686aa Fixed display messages to all SAR, added option to suppress messaging 2021-07-27 14:10:10 +02:00
Applevangelist b66d2039be Added function to set speed and height parameters for landing 2021-07-27 14:09:10 +02:00
Applevangelist d245a73d7f Make Destroy() silent for scoring 2021-07-26 09:24:52 +02:00
Applevangelist f0037151e6 CTLD - added factor for troops extraction, making Destroy()s silent for scoring, refined autohoverload messaging 2021-07-26 09:24:35 +02:00
Applevangelist 52d494f573 Merge pull request #1582 from bbirchnz/develop
CTLD: fix troops being lost when crates dropped
2021-07-25 09:06:16 +02:00
Ben Birch 36a2d71731 CTLD: fix troops being lost when crates dropped 2021-07-25 13:46:15 +10:00
Frank 615a220acb Update OpsGroup.lua 2021-07-24 23:56:27 +02:00
Frank c718584755 Update OpsGroup.lua 2021-07-24 23:25:26 +02:00
Applevangelist 4db0ea616d Correct opendoor check to include anyone loaded 2021-07-24 15:53:20 +02:00
Applevangelist 5439e60078 Merge pull request #1580 from bbirchnz/ctld-add-mass
CTLD: Enable mass on cargo
2021-07-24 15:04:49 +02:00
Ben Birch a6880d4569 Add SetUnitInternalCargo to UNIT
Add PerCrateMass to CTLD_CARGO (default = 0kg)
Update unit internal cargo after each load/unload operation.
Add mass to cargo reports
2021-07-24 22:39:26 +10:00
Frank 65e852f341 OPS Transport 2021-07-24 01:06:44 +02:00
Frank 4ffdf9e536 Merge branch 'develop' into FF/Ops 2021-07-23 18:49:35 +02:00
Applevangelist 8f698e3e62 Changed Messaging Structure on Approach 2021-07-23 16:29:56 +02:00
Applevangelist 61481e6e9a CSAR updates to messaging. Detect if far approach note exceeds 8km for smoke/flare request 2021-07-23 11:19:53 +02:00
Applevangelist 49397df90b CTLD - added repair capabilties for VEHICLE and FOB types of CTLD_CARGO 2021-07-22 20:17:40 +02:00
Applevangelist 7b254a08fb Merge pull request #1579 from FlightControl-Master/Applevangelist-inteldlink
Update Intelligence.lua
2021-07-22 11:37:51 +02:00
Applevangelist 5bebbcf5eb Update Intelligence.lua
Added FSM functions and documentation for INTEL_DLINK
2021-07-22 11:36:21 +02:00
Applevangelist 83fd48e2f4 Merge pull request #1578 from madmoney99/develop
Airboss update for wind over deck
2021-07-22 08:03:32 +02:00
madmoney99 f5b25370b0 Change to wind measure for Skipper
Previous value 50m produced an incorrect heading and strength based on the actual wind over deck.
2021-07-21 22:51:29 -07:00
Tony Goodale f68d3209ac Merge branch 'FlightControl-Master:develop' into develop 2021-07-21 22:44:11 -07:00
Applevangelist 2ab7f784c9 Merge pull request #1576 from FlightControl-Master/Applevangelist-CTLD
Update CTLD.lua
2021-07-21 18:47:52 +02:00
Applevangelist 1e8d99a591 Merge pull request #1577 from FlightControl-Master/Applevangelist-INTEL
Update Intelligence.lua
2021-07-21 18:47:40 +02:00
Applevangelist d517cba765 Update Intelligence.lua
no dupe docs
2021-07-21 18:45:57 +02:00
Applevangelist 552eb5b9d8 Update CTLD.lua
Add housekeeping for dropped troops
2021-07-21 18:40:43 +02:00
Applevangelist 684c4ea113 Allow MANTIS to use INTEL_DLINK 2021-07-21 18:22:46 +02:00
Applevangelist 4dfaca610f INTEL - bug fixes, added new datalink class 2021-07-21 18:22:31 +02:00
Applevangelist 8a53998251 MANTIS - make start random async, so detection for multiple MANTIS objects works better
INTEL - added function to alter detection types, typos corrected etc
2021-07-20 18:30:11 +02:00
bbirchnz 6690f70b05 fix CTLD:ActivateZone not processing default correctly and using wrong zone table (#1575) 2021-07-19 07:16:21 +02:00
Applevangelist 96d1d3cb66 Light code cleanup, added docu for troop extract 2021-07-18 14:53:00 +02:00
bbirchnz 09785ef451 - ctld: add troop extract (#1574) 2021-07-18 13:01:45 +02:00
Applevangelist 25e118f3dc Added correct Player Mi-8MT unitname
CSAR - logic change to detect dead pilots, if they are not set to immortal. Added FSM event "KIA"
2021-07-17 15:48:18 +02:00
Frank 78b3e3c60b OPS Respawn 2021-07-15 22:25:02 +02:00
Applevangelist efe41a5e21 CSAR and CTLD - use Frequency generation moved to UTILS
CTLD - added option to drop crates anywhere
MANTIS - added state tracker to call Green/Red state change events only once
UTILS - added Marianas NDBs to Frequency generation
2021-07-15 17:16:30 +02:00
Frank 835041e5f6 OPS 2021-07-14 21:00:34 +02:00
Applevangelist 93a8086ff6 Update Mantis.lua (#1569)
Added state tracker so that Red/Green Events only get triggered when a state actually changes.
2021-07-14 15:37:57 +02:00
Applevangelist 1ac40684de Added option to force description on injected pilots for scripting 2021-07-14 08:39:54 +02:00
Applevangelist 433d1bbf57 MANTIS - Change logic to FSM, added functions
CSAR - advanced options to name injected AI downed pilots
CTLD - added Herc speed check
2021-07-13 17:50:44 +02:00
Applevangelist e33de03522 CSAR - honor settings NM vs KM
CTLD - documentation corrections
UTILS - added functions to generate beacon frequency tables FM,VHF,UHF and valid laser codes for JTACs
2021-07-12 19:17:39 +02:00
Applevangelist 86fedbfaae Updated noise level 2021-07-12 18:16:22 +02:00
Applevangelist c0f4eef896 Slightly changed versions of Mantis, Sead and Shorad as the Emissions On/Off stuff ED introduced doesn't really work well. 2021-07-12 18:10:12 +02:00
Frank a2f3bf3a8c Merge branch 'develop' into FF/Ops 2021-07-12 15:27:24 +02:00
Frank 1b717e4683 AUFTRAG
- Added push time
2021-07-12 15:27:08 +02:00
Applevangelist 52e2ac7174 Added documentation 2021-07-11 18:31:17 +02:00
Applevangelist 3d6b053eb4 Added function to check if a loading door on a heli is open 2021-07-11 18:31:09 +02:00
Frank 268eb1d60d OPS 2021-07-11 18:15:25 +02:00
Applevangelist a69865b8c9 -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors
--         self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters
--         self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters
--         self.pilotmustopendoors = false -- switch to true to enable check of open doors
2021-07-10 16:56:35 +02:00
Applevangelist 4a1df3d5cc -- -- (added 0.1.8) - allow to set far/near distance for approach and optionally pilot must open doors
--         self.approachdist_far = 5000 -- switch do 10 sec interval approach mode, meters
--         self.approachdist_near = 3000 -- switch to 5 sec interval approach mode, meters
--         self.pilotmustopendoors = false -- switch to true to enable check of open doors
2021-07-10 16:56:35 +02:00
Frank 6dfd757ea1 OPS 2021-07-08 22:59:29 +02:00
Frank fc1adf3b94 OPS Transport 2021-07-08 12:21:10 +02:00
Frank be2d1d7895 Update OpsTransport.lua 2021-07-07 11:33:51 +02:00
Frank 9e7c360912 Merge branch 'develop' into FF/Ops 2021-07-06 21:57:31 +02:00
Frank 51acd33d19 Merge branch 'master' into develop 2021-07-06 21:57:08 +02:00
Frank af3dbe2f86 Merge branch 'develop' into FF/Ops 2021-07-06 21:56:53 +02:00
Frank 97668e5413 Update FlightGroup.lua
- Fixed flight of airwing is going to tanker even if fuellowrefuel switch is false
2021-07-06 21:56:15 +02:00
Frank 464fde0ed2 OPS
- MSRS
- Callsign
2021-07-06 21:13:50 +02:00
cammel tech 65bd7909e1 Example for CTLD > SCORING added (#1566)
* Example added

Example for the connection to the SCORING Class.

* kleiner Fehler eingeschlichen
2021-07-06 20:26:20 +02:00
Applevangelist 5d3ea57d4d CSAR give scripted spawned pilots a name.
CTLD corrected that generic troops could be spawned only once.
2021-07-06 20:24:35 +02:00
Applevangelist b7bc3cfbcf Spawn crates in a wider radius. Also build, when only one crate is required. 2021-07-05 18:54:45 +02:00
Applevangelist 04ef32420b Merge pull request #1567 from thejuju/develop
Add ATC Ground Mariana Islands support
2021-07-05 14:44:02 +02:00
Julien B a3d8a48b92 Add ATC Ground Mariana Islands support
Added ATC_GROUND_MARIANAISLANDS
Supported optional airbase ZONE_POLYGON boundaries (like initially intented) when there is provided, fallback remain a ZONE from the center of the airfield.
Some airports are bigger than the default radius area and need custom boundaries to work properly on the edge of it.
2021-07-05 13:09:34 +02:00
Applevangelist ef86d1d7cb Merge pull request #1562 from cammeltech/SCORING-reactivate_destroy
Reactivate the :destroy() event on clients with scoring below thresmark
2021-07-04 18:10:45 +02:00
Applevangelist 00b3e9b371 Merge pull request #1564 from justin-lovell/justin-lovell-mark-changed-event
Synchronize Text with Wrapper State
2021-07-04 18:10:35 +02:00
Applevangelist 0d02f56855 Merge pull request #1565 from justin-lovell/updated-debug-msg
Correcting typo for debug message
2021-07-04 18:10:21 +02:00
Applevangelist 48aa841add ATIS - addede rainy presets 1-3
CTLD - avoid "preloading" when pilot leaves/crashes and rejoins
CSAR - added option for autosmokedistance
2021-07-04 18:04:27 +02:00
Applevangelist 4ba52212a9 ATIS - addede rainy presets 1-3
CTLD - avoid "preloading" when pilot leaves/crashes and rejoins
CSAR - added option for autosmokedistance
2021-07-04 18:04:08 +02:00
Justin Lovell a14bca1059 Synchronize Text with Wrapper State
Bug - text is not synchronized with the wrapper state, hence the `GetText()` will be incorrect.

Method `TextChanged` does not exist, resulting `nil` reference errors when the players update markers. Current implementation of `MARKER:OnEventMarkChange(EventData)` is not implemented the same as its siblings of `OnEventMarkRemoved` and `OnEventMarkAdded`. The siblings would move the FSM accordingly -- aligned implementation
2021-07-03 23:30:45 +10:00
Justin Lovell a7e07af24f Correcting typo for debug message
Small PR
2021-07-03 23:24:04 +10:00
Applevangelist e14d655447 Changed default fallback freq to 333.00Khz 2021-07-03 09:30:04 +02:00
Frank 74bdeaf4f7 AIRWING 2021-07-02 23:11:51 +02:00
Applevangelist 9591c62175 corrected autovalue for frequency - thanks to shadowze 2021-07-02 20:22:43 +02:00
Applevangelist 39c46dcab0 Changed frequency logic, some documentation changes 2021-07-02 20:15:12 +02:00
Applevangelist 299e08f53d additional checks to ensure only human players, extra checks in the logic, some logic errors corrected. 2021-07-02 17:52:58 +02:00
wob3155@posteo.de 12555a6ff1 Reactivate the :destroy() event on clients with scoring below thresmark 2021-07-02 08:56:28 +02:00
Frank 1cc012c7d1 Merge branch 'develop' into FF/Ops 2021-07-02 08:53:44 +02:00
Frank 28c6810878 Merge branch 'master' into develop 2021-07-02 08:52:07 +02:00
Frank 1ed36fdff4 Merge branch 'develop' into FF/Ops 2021-07-01 23:56:38 +02:00
Frank 26801cf206 Merge branch 'master' into develop 2021-07-01 23:54:57 +02:00
Frank c80cebb824 WAREHOUSE, AIRWING & SQUADRON
- Added function for hot start to SQUADRON
- Addressed bug #1560
2021-07-01 23:54:24 +02:00
Frank 3c6e8d6d01 OPS
- Transport
- Respawn
2021-07-01 12:29:29 +02:00
Applevangelist 76a53ab154 Added extra checks for Beacon refresh 2021-07-01 09:07:01 +02:00
Frank fa3e387dd1 OPS 2021-06-30 23:23:41 +02:00
Applevangelist dca626bbcb CTLD - added option to suppress messaging, added event for OnAfterTroopsRTB
CSAR - minor bugfix
2021-06-30 17:59:43 +02:00
Applevangelist 3289ad2817 Added basic support for Hercules mod 2021-06-29 17:49:42 +02:00
Applevangelist 5a022a2246 Messages going to SAR flights only 2021-06-29 08:50:46 +02:00
Frank b012c5b2aa OPS Transport
- Improved cargo bay weight calculation.
2021-06-28 13:17:55 +02:00
Frank a861f8d9d4 OPS 2021-06-25 21:13:24 +02:00
Frank 36669c80da OPSTRANSPORT 2021-06-25 13:23:56 +02:00
Applevangelist 2ff128f184 Added Docu for functions 2021-06-25 12:39:02 +02:00
Applevangelist 3b44aba341 Updated frequency test 2021-06-24 08:34:14 +02:00
Frank 978be4e383 OPS Transport 2021-06-23 17:20:51 +02:00
Applevangelist d4cdfcc48c Corrected Mi-8MTV2 Unit Type Name 2021-06-23 14:09:09 +02:00
Applevangelist 5123ab0720 Update CTLD.lua (#1558)
corrected fsm function name headlines in documentation
2021-06-23 09:47:05 +02:00
Applevangelist 5021a1e1f3 Update CTLD.lua 2021-06-22 18:26:44 +02:00
Applevangelist 576281a612 Update CSAR.lua (#1554)
Added options to limit number of downed pilots via Events (mission designers can still "inject" downed pilots):
`self.limitmaxdownedpilots = true
 self.maxdownedpilots = 10`
2021-06-22 18:24:46 +02:00
Applevangelist b84c0aba59 Update Moose.files (#1556) 2021-06-22 18:24:36 +02:00
Applevangelist ee503a378e Update Modules.lua (#1557)
added CTLD
2021-06-22 18:24:24 +02:00
Applevangelist f235037cb9 Create CTLD.lua (#1555)
Player Heli Cargo and Troops Ops :)
2021-06-22 18:24:11 +02:00
Applevangelist 0cd1cd97a6 Update CSAR.lua (#1553) 2021-06-22 13:11:12 +02:00
Frank 74513f1b5c Merge branch 'develop' into FF/Ops 2021-06-22 12:18:45 +02:00
Frank 231c5bfea7 Ops 2021-06-22 12:18:25 +02:00
Frank cfd0e4ba85 Merge branch 'master' into develop 2021-06-19 22:28:30 +02:00
Applevangelist bf33e4ed4f Update CSAR.lua (#1552) 2021-06-18 12:12:54 +02:00
Frank 6290db035d Merge branch 'develop' into FF/Ops 2021-06-15 22:00:14 +02:00
Applevangelist cd1935be1d Update CSAR.lua (#1551) 2021-06-15 15:49:58 +02:00
Applevangelist 64262d6ecc Update CSAR.lua (#1550) 2021-06-15 10:32:52 +02:00
Applevangelist 270c69344f Update CSAR.lua
Updated documentation / clarification. Added feature docu on using SRS
2021-06-15 07:42:45 +02:00
Applevangelist 10a0793af7 Update CSAR.lua
Small updates to reflect correct measurements. Options to SRS TTS.
2021-06-14 19:15:58 +02:00
Applevangelist 1b37af321f Update CSAR.lua
Small updates
2021-06-14 16:21:47 +02:00
Applevangelist 7cde279be1 Update Modules.lua
added CSAR module
2021-06-14 13:20:00 +02:00
Applevangelist 0396741f3d Create CSAR.lua
Initial Release
2021-06-14 13:18:27 +02:00
Applevangelist 117cf8888a Update Globals.lua 2021-06-13 10:39:07 +02:00
Frank 83db7b32e8 Merge branch 'master' into FF/Ops 2021-06-12 22:59:20 +02:00
Frank d496d3d16e Merge branch 'master' into develop 2021-06-12 22:23:10 +02:00
Frank 6b801247ab Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-11 23:41:52 +02:00
Frank 4c6cd31bfc Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-10 23:33:27 +02:00
Frank a8ef096f24 Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-10 23:23:30 +02:00
Applevangelist c8d2a7e833 Added commands for immortal and invisible to Wrapper.Group#GROUP (#1547)
* Added function for message duration (#1542)

... and correct flash status setting

* Update Spawn.lua (#1544)

* Update Spawn.lua

* Update Group.lua (#1546)

Added invisible and immortal commands on GROUP level.
2021-06-10 11:59:50 +02:00
Frank 783391838b Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-10 00:19:02 +02:00
Frank 7c37d8b204 Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-09 13:19:14 +02:00
Applevangelist 65ed8825b0 Update Intelligence.lua
Added functionality to calculate the position of a cluster after x seconds, based on trajectory (average speed and heading) of a cluster
*  INTEL:CalcClusterFuturePosition(cluster,seconds)
Will also draw arrows on the map if `self.clustermarkers` is true and `self.verbose > 1`
Change cluster coordinate updates to better suite cluster movement
2021-06-08 15:59:36 +02:00
Applevangelist af9324dd5f master updates to develop for InitCleanup() and CC Messages (#1545)
* Added function for message duration (#1542)

... and correct flash status setting

* Update Spawn.lua (#1544)

* Update Spawn.lua
2021-06-07 18:26:43 +02:00
Frank 064f727676 Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-05 23:45:15 +02:00
Frank 8025862fb9 Merge branch 'FF/MasterDevel' into FF/Ops 2021-06-01 23:20:59 +02:00
Frank 2544ec5587 Update SRS.lua 2021-05-30 01:44:36 +02:00
Frank 6c823b0137 Merge branch 'FF/MasterDevel' into FF/Ops 2021-05-30 01:36:43 +02:00
Frank 88d44fa654 Merge branch 'develop' into FF/Ops 2021-05-28 23:01:47 +02:00
Frank 8b6f9f9de7 Merge branch 'master' into develop 2021-05-28 23:01:15 +02:00
Applevangelist cb14961dcd Update Zone.lua (#1541)
added function `ZONE_POLYGON_BASE:Boundary()`
2021-05-26 15:54:20 +02:00
Applevangelist dab486ec99 Update Controllable.lua
Typo in `OptionDisperseOnAttack()`
2021-05-26 09:16:26 +02:00
Shadowze 75849fbfb5 Update Positionable.lua
function added to work out if a unit is a submarine
2021-05-25 22:30:16 +01:00
Frank f984ff044b Merge branch 'master' into develop 2021-05-25 21:13:36 +02:00
Frank aaf5f295da Merge branch 'develop' into FF/Ops 2021-05-20 23:27:05 +02:00
Frank 8f612f2017 Merge branch 'master' into develop 2021-05-20 23:25:29 +02:00
Frank a2808163a7 Boom and Probe 2021-05-20 23:23:12 +02:00
Frank e4aa23ce3d Update Point.lua 2021-05-15 00:29:20 +02:00
Frank 1b3cac9424 Merge branch 'FF/MasterDevel' into FF/Ops 2021-05-13 23:38:17 +02:00
Applevangelist e49020b905 Update README.md 2021-05-12 09:19:07 +02:00
Frank d68dcaf69a Merge branch 'develop' into FF/Ops 2021-05-11 14:52:31 +02:00
Frank 856c3578f6 Merge branch 'master' into develop 2021-05-11 11:59:48 +02:00
Tony Goodale 18c6ea39ce Merge pull request #7 from FlightControl-Master/develop
Develop
2021-05-10 15:47:14 -07:00
Frank 1ce46c016c Cargo 2021-05-10 00:14:07 +02:00
Frank 296944c23c Merge branch 'develop' into FF/Ops 2021-05-07 22:57:34 +02:00
Frank 4f772fecdc Merge branch 'master' into develop 2021-05-07 21:56:17 +02:00
Frank cdd22444b9 Merge branch 'master' into develop 2021-05-07 21:49:24 +02:00
Frank 15848bc8c4 Merge branch 'master' into FF/Ops 2021-05-07 17:47:41 +02:00
Applevangelist 9eba6d607c Updated doc images 2021-05-06 09:30:06 +02:00
Applevangelist e9d3c7c2e7 Update MarkerOps_Base.lua 2021-05-05 19:02:46 +02:00
Applevangelist ad16c6d44f Include MarkerOps_Base (#1533)
Include MarkerOps_Base
2021-05-05 11:56:00 +02:00
Applevangelist 2046958bd6 Added MarkerOps_Base.lua 2021-05-05 11:44:35 +02:00
Applevangelist 0046533d53 Added base class for action based on marker additions, changes, deletions and text keywords 2021-05-05 11:44:17 +02:00
Applevangelist ce7e152e13 Updated Shorad and Set 2021-05-04 12:59:21 +02:00
Tony Goodale edcdc057e6 Merge pull request #6 from FlightControl-Master/develop
Develop
2021-05-01 15:40:23 -07:00
Frank cfa851beea Merge branch 'master' into FF/Ops 2021-05-02 00:25:07 +02:00
Frank f5d8efe86e Merge branch 'master' into develop 2021-05-01 22:45:48 +02:00
Applevangelist 7c8e5033ec Merge pull request #1527 from FlightControl-Master/AA-1
Update Sead.lua
2021-05-01 17:29:43 +02:00
Applevangelist 22ec94b374 Merge pull request #1528 from FlightControl-Master/AA-2
Update Shorad.lua
2021-05-01 17:27:22 +02:00
Applevangelist 5cd3a172f1 Merge pull request #1529 from FlightControl-Master/AA-3
Update Mantis.lua
2021-05-01 17:24:33 +02:00
Applevangelist a89a3b38ce Update Mantis.lua
Adjustments for 2.7
2021-05-01 17:21:53 +02:00
Applevangelist 3202702612 Update Shorad.lua
Adjustments for 2.7
2021-05-01 17:20:35 +02:00
Applevangelist 725e32f439 Update Sead.lua
Adjust to Event changes
2021-05-01 17:19:10 +02:00
Applevangelist 7da1905ad3 Update Event.lua (#1525)
Updates for Events 33 and 6 - makes scoring also work again for STATIC targets
2021-05-01 14:22:01 +02:00
Frank 421005a3b6 event backfixed 2021-04-30 11:53:57 +02:00
Applevangelist d5bf28f799 Update AirWing.lua (#1521)
Markpoint honored for direct calls from #OPSGROUP
2021-04-29 18:20:03 +02:00
Frank 990f748e42 Update Database.lua 2021-04-27 09:19:33 +02:00
Applevangelist a466365949 Update Controllable.lua (#1519)
Error line 1463
2021-04-25 20:31:02 +02:00
Frank b12f71c430 Merge branch 'develop' into FF/Ops 2021-04-23 13:38:28 +02:00
Frank 236a69e5dc Merge branch 'master' into develop 2021-04-23 13:29:37 +02:00
Applevangelist 9458243926 Merge pull request #1516 from FlightControl-Master/AA
Update Controllable.lua
2021-04-23 11:00:50 +02:00
Applevangelist 41274e7801 Update Controllable.lua 2021-04-23 10:59:22 +02:00
Applevangelist 0259e41ce8 Create .luacheckrc 2021-04-22 10:10:59 +02:00
Applevangelist e78f9cbd46 Merge pull request #1512 from FlightControl-Master/aa
Update Airboss.lua
2021-04-22 08:53:52 +02:00
Applevangelist 9d6851adec Update Airboss.lua
Escape error 17933
2021-04-22 08:30:19 +02:00
Applevangelist 4668f072af Merge pull request #1511 from FlightControl-Master/AA-1
Update Airboss.lua
2021-04-22 08:21:00 +02:00
Applevangelist a1a414925f Update Airboss.lua 2021-04-22 08:19:56 +02:00
Applevangelist 19eb9dca30 Update Utils.lua 2021-04-20 18:49:54 +02:00
Applevangelist 8464a2ba43 Merge pull request #1507 from FlightControl-Master/Applevangelist-patch-1
Update Airbase.lua
2021-04-20 16:52:28 +02:00
Applevangelist a8549e2b7d Update Airbase.lua 2021-04-20 16:50:00 +02:00
Applevangelist 2531a25c9e Update Group.lua
Add EnableEmissions
2021-04-20 11:36:59 +02:00
Frank 2f7b7eeddd Merge branch 'master' into develop 2021-04-18 14:49:37 +02:00
Frank ab1ec54c03 Merge branch 'master' into develop 2021-04-18 14:34:46 +02:00
Frank a1ca605206 Merge branch 'master' into develop 2021-04-17 23:19:42 +02:00
Frank a7b444dfea Update OpsGroup.lua 2021-04-17 09:29:55 +02:00
Frank 64b35b9cb3 Merge branch 'develop' into FF/Ops 2021-04-16 23:15:30 +02:00
Frank 4a8d52b328 Merge branch 'master' into develop 2021-04-16 23:06:04 +02:00
Pikes 3cbf6fff90 Merge pull request #1490 from Applevangelist/patch-82
Update Event.lua workaround for 2.7
2021-04-16 18:01:25 +01:00
Frank 2693e76483 Update Airboss.lua 2021-04-15 21:13:35 +02:00
Applevangelist b232a5da37 Update Event.lua workaround for 2.7
Same as for Event 31
2021-04-15 13:02:59 +02:00
Frank e50fc00583 Merge branch 'develop' into FF/Ops 2021-04-14 23:44:43 +02:00
Frank 4b8802af37 Merge branch 'master' into develop 2021-04-14 23:42:48 +02:00
Frank ebd69fd10d Merge pull request #1482 from Applevangelist/patch-74
Update FlightGroup.lua
2021-04-12 15:55:50 +02:00
Applevangelist c1a8c08880 Update FlightGroup.lua 2021-04-12 13:02:16 +02:00
Frank 647fb6acbf Merge branch 'develop' into FF/Ops 2021-04-07 20:54:19 +02:00
Frank ede692ea2a Merge branch 'master' into develop 2021-04-07 20:53:27 +02:00
Frank ca44e2762b Update OpsGroup.lua 2021-04-07 20:53:15 +02:00
Frank eda70742eb Merge pull request #1486 from Applevangelist/patch-78
Update AirWing.lua
2021-04-04 22:03:49 +02:00
Frank b81e3e9e6d Merge pull request #1485 from Applevangelist/patch-77
Update Intelligence.lua
2021-04-04 22:03:40 +02:00
Applevangelist e766eaeccc Update AirWing.lua
Avoid loop if mission is governed by an Airwing. Mission cancel will ask airwing to cancel will ask flightgroup to cancel, which doesn't work if the latter is dead. Rare but happens.
2021-04-04 13:11:25 +02:00
Applevangelist bab61271f2 Update Intelligence.lua
Make Markers Coalition specific
2021-04-04 13:07:54 +02:00
Applevangelist b1465d89a6 Update FlightGroup.lua
Added check for velocity for the RTB loop (if group is taxiing). Added OutOfAAMissiles.
2021-04-04 12:49:40 +02:00
Frank 05b1ddf013 Merge pull request #1483 from Applevangelist/patch-75
Update AirWing.lua
2021-04-03 22:25:51 +02:00
Applevangelist 903e8711f7 Update AirWing.lua
Make Patrolpoint Markes Switchable
2021-04-01 13:28:59 +02:00
Applevangelist 31b93fb42c Update FlightGroup.lua
Avoid endless when plane/heli is not airborne by needs to RTB
2021-04-01 13:21:40 +02:00
Frank e16bbfc2b9 Merge branch 'develop' into FF/Ops 2021-03-29 19:55:35 +02:00
Frank 220e004d73 Merge branch 'master' into develop 2021-03-29 19:55:21 +02:00
Frank 3136861b24 Merge branch 'develop' into FF/Ops 2021-03-29 18:02:23 +02:00
Frank dc2693bead Merge branch 'master' into develop 2021-03-29 18:02:00 +02:00
Frank d7da8c4253 Merge branch 'develop' into FF/Ops 2021-03-29 09:55:13 +02:00
Frank c1ff5831ad Merge branch 'master' into develop 2021-03-29 09:53:51 +02:00
Frank ac5646075b Merge pull request #1481 from madmoney99/develop
Airboss CASE III fix for OS/AA
2021-03-29 00:31:46 +02:00
madmoney99 5a1faba796 CASE III fix for OS/AA 2021-03-28 15:25:59 -07:00
Tony Goodale 4ab16cb276 Merge pull request #5 from FlightControl-Master/develop
Merge pull request #1478 from madmoney99/develop
2021-03-28 15:12:27 -07:00
Frank eb1e885c0a Merge branch 'develop' into FF/Ops 2021-03-28 20:33:08 +02:00
Frank 00ff9a11b8 Merge branch 'master' into develop 2021-03-28 20:30:19 +02:00
Frank 8eabc024bf Merge pull request #1479 from madmoney99/develop
Update Airboss.lua
2021-03-23 08:44:14 +01:00
madmoney99 a848af0753 Update Airboss.lua 2021-03-22 19:01:04 -07:00
Frank f4e7a3a314 Merge pull request #1478 from madmoney99/develop
Airboss formating fixes
2021-03-22 22:19:29 +01:00
madmoney99 d83283c709 Update Airboss.lua
formatting fixes
2021-03-22 10:10:33 -07:00
Tony Goodale a35112a6f3 Merge pull request #4 from FlightControl-Master/develop
Merge pull request #1476 from madmoney99/develop
2021-03-22 10:01:53 -07:00
Frank 66c012a30d Merge pull request #1476 from madmoney99/develop
Develop Airboss grading additions
2021-03-22 12:03:58 +01:00
madmoney99 7b92b7853d Grading Additions
Addition of:
- AA - Angled Approach call (advisory)
- Drift Right/Left - DR/DL (advisory)
- Overshoot - OS - graded for start

Updated default SetMPWireCorrection(Dcorr) to 12 from 8.7 based on feedback.

Minor clean up and updates for intro section to include new additions and other changes previously made.

TonyG
2021-03-21 17:40:44 -07:00
madmoney99 6c9f10e6a8 Revert "Grading additions"
This reverts commit 6db2c4465d.
2021-03-21 17:38:06 -07:00
madmoney99 6db2c4465d Grading additions
Addition of:
 - AA - Angled Approach call (advisory)
 - Drift Right/Left - DR/DL (advisory)
 - Overshoot - OS - graded for start

Updated default SetMPWireCorrection(Dcorr) to 12 from 8.7 based on feedback.

Minor clean up and updates for intro section to include new additions and other changes previously made.

TonyG
2021-03-21 17:35:16 -07:00
Tony Goodale c0cc960df5 Merge pull request #3 from FlightControl-Master/develop
Develop
2021-03-21 14:53:53 -07:00
Frank 968240ad44 Merge pull request #1473 from Applevangelist/patch-72
Update Sead.lua
2021-03-14 22:46:16 +01:00
Applevangelist 50f3016711 Update Sead.lua
latest
2021-03-10 11:36:10 +01:00
Frank 56e2b06e9d OPS Cargo 2021-03-07 23:47:29 +01:00
Frank c02f69720a Merge pull request #1471 from Applevangelist/patch-71
Update Controllable.lua
2021-03-04 16:33:56 +01:00
Frank 96df7d1e42 Merge pull request #1470 from Applevangelist/patch-70
Update Intelligence.lua
2021-03-04 16:33:46 +01:00
Applevangelist 1d4caf7588 Update Controllable.lua
2 functions missing though they are in the doc header
2021-03-04 16:15:57 +01:00
Applevangelist b0e3201d3c Update Intelligence.lua
Added Docu
2021-03-04 11:13:30 +01:00
Frank bab097958f Merge branch 'develop' into FF/Ops 2021-03-03 00:03:52 +01:00
Frank f4a3f6d433 OPS 2021-03-03 00:03:39 +01:00
Frank e73ab89a18 Merge pull request #1469 from madmoney99/develop
Develop
2021-03-02 22:46:31 +01:00
madmoney99 187643f6ae Groove time additions
Changes on screen description of groove times to match NATOPs grading
NESA
LIG
Changes unicorn groove time from 16-18secs to 15-18.99 seconds.

No change to grading.  Reference GitHub issues 1445 &1446.
2021-03-02 13:36:54 -08:00
Tony Goodale 3c9bdb2915 Merge pull request #1 from FlightControl-Master/develop
Develop
2021-03-02 13:28:58 -08:00
Frank 5241642447 Merge branch 'master' into develop 2021-03-02 21:19:41 +01:00
Frank 3b2cbea1c4 Update OpsGroup.lua 2021-03-02 21:12:51 +01:00
Frank 80e3da49cb Merge pull request #1468 from Applevangelist/patch-69
Update Intelligence.lua
2021-03-02 15:34:27 +01:00
Applevangelist 1df7a12587 Update Intelligence.lua
Finalized Cluster functions, added bits and bobs
2021-03-02 14:28:37 +01:00
Frank 663a59bd71 Merge branch 'master' into develop 2021-02-28 23:26:48 +01:00
Frank ced859424b Merge branch 'master' into develop 2021-02-27 21:18:11 +01:00
Frank 4a6377c204 Ops Cargo 2021-02-27 21:17:58 +01:00
Frank fd22558207 Merge pull request #1467 from madmoney99/develop
T-45C Goshawk Parameters
2021-02-26 08:00:35 +01:00
madmoney99 a6faadea48 T-45C Goshawk Parameters
Addition of parameters for the VNAO T-45C Goshawk.
2021-02-25 12:52:50 -08:00
Frank 42a26100b7 Merge pull request #1465 from Applevangelist/patch-67
Update Intelligence.lua
2021-02-24 18:02:19 +01:00
Frank e8f4770abe Merge pull request #1464 from Applevangelist/patch-66
Update AirWing.lua
2021-02-24 18:01:44 +01:00
Applevangelist 7751d4ee88 Update Intelligence.lua
Added function to set verbosity
2021-02-24 16:22:13 +01:00
Applevangelist cdd829e7be Update AirWing.lua
Ensure correct type of tanker is going on mission
2021-02-24 16:16:28 +01:00
Frank 16bf3a89b5 Merge pull request #1461 from Applevangelist/patch-63
Update Intelligence.lua
2021-02-21 23:59:37 +01:00
Applevangelist 11cdae098b Update Intelligence.lua
Honor settings for clustermarkers
2021-02-21 19:55:48 +01:00
Frank a63ee3fa8b Merge branch 'develop' into FF/Ops 2021-02-21 11:54:06 +01:00
Frank 5cc023d1fe OPS Cargo 2021-02-21 11:53:52 +01:00
Frank c4084156ac OPS Cargo 2021-02-21 00:19:32 +01:00
Frank 302018feff OPS Cargo 2021-02-20 00:03:45 +01:00
Frank c50f6b72e7 OPS Cargo 2021-02-19 00:11:55 +01:00
Frank c0401447dd OPS Cargo 2021-02-18 00:10:17 +01:00
Frank 43680c2fc3 Merge branch 'master' into develop 2021-02-17 13:16:46 +01:00
Frank 2c92bb9d61 OPS Cargo 2021-02-17 00:29:59 +01:00
Frank dcf1a56756 Update OpsGroup.lua 2021-02-16 00:15:22 +01:00
Frank 5990ab1cc9 OPSTRANSPORT
- New class.
2021-02-15 23:19:35 +01:00
Frank 084499fa0e OPS Wait 2021-02-14 00:19:03 +01:00
Frank 74e84a73dd OPS Cargo 2021-02-13 00:09:25 +01:00
Frank 96b0393f91 OPS Cargo 2021-02-11 23:58:56 +01:00
Frank 0656c33e05 Update OpsGroup.lua 2021-02-10 23:50:37 +01:00
Frank ec1ab49b54 Merge pull request #1447 from Applevangelist/patch-51
Update Auftrag.lua
2021-02-10 21:34:12 +01:00
Applevangelist 1daa2857b2 Update Auftrag.lua
Corrected docu in NewTanker, 1 is actually boom, and 0 probe
2021-02-10 13:19:53 +01:00
Frank 8c55541d0e Update OpsGroup.lua 2021-02-08 16:27:58 +01:00
Frank c5a4776b3a OPS Cargo 2021-02-07 01:24:13 +01:00
Frank 8bb9f0d7c0 OPS Cargo 2021-02-06 09:41:24 +01:00
Frank 7d83c251a8 OPS Cargo 2021-02-03 22:51:18 +01:00
Frank ee3ead9aac OPS Cargo 2021-02-02 23:20:55 +01:00
Frank 54896e0e31 Merge pull request #1444 from rollnthndr/develop
Added a note pointing out that callsigns need to be chosen depending …
2021-02-01 08:06:11 +01:00
Frank 016198e7f2 OPS Cargo 2021-01-31 23:28:04 +01:00
Frank 0933eef4c3 Merge branch 'develop' into FF/Ops 2021-01-31 00:50:14 +01:00
Frank fe55555c67 OPS
- Fixed inAir check for spawned groups
- Reduced SCHEDULER min delay to 0.001 sec (was 0.1 sec)
2021-01-30 23:28:07 +01:00
Frank bcf8973eed OPS 2021-01-30 21:29:29 +01:00
rollnthndr 50d2e42cc3 Added VSCode settings folder and workspace file to the ignore list. 2021-01-29 18:31:19 -07:00
rollnthndr 261d1cbeba Added a note pointing out that callsigns need to be chosen depending on the type of unit that this task is being assigned to. 2021-01-29 18:15:20 -07:00
Frank 242462b9ba OPS Cargo 2021-01-29 00:32:38 +01:00
Frank fd4e478dbf Merge branch 'master' into develop 2021-01-26 12:40:15 +01:00
Frank 6b91e68668 OPS Cargo 2021-01-26 12:39:49 +01:00
Frank 7516d2af56 Merge branch 'master' into develop 2021-01-23 16:47:14 +01:00
Frank c247a98402 OPS cargo 2021-01-23 16:46:44 +01:00
Frank 83bcd55a7a Merge pull request #1439 from FlightControl-Master/wingthor_fixes
Added a table shuffler using Fisher Yeates algorithm in Utilities/Uti…
2021-01-22 23:28:42 +01:00
Wingthor 5a43936e35 Added a table shuffler using Fisher Yeates algorithm in Utilities/Utils.lua 2021-01-22 23:10:22 +01:00
Frank 35b776179e Merge branch 'master' into develop 2021-01-19 16:56:40 +01:00
Frank f4cb6df8d4 Ops
respawn
2021-01-19 16:53:22 +01:00
Frank a259b88f1d Merge branch 'develop' into FF/Ops 2021-01-16 22:57:10 +01:00
Frank 5b4f3d1c98 Merge branch 'master' into develop 2021-01-16 22:56:43 +01:00
Frank c62c21c386 FLIGHTGROUP v0.6.1
- Improved engage targets
2021-01-16 22:56:28 +01:00
Frank 17c56a11bd Merge pull request #1432 from OttoWerkr/develop
Update Scenery.lua
2021-01-15 23:53:37 +01:00
Frank 796561b8b6 Merge pull request #1433 from FlightControl-Master/FF/Ops
OPS
2021-01-15 23:44:46 +01:00
Frank 209b7a64ed Update OpsGroup.lua 2021-01-15 23:42:30 +01:00
Frank 8f56299380 OPS
**AUFTRAG**
- Added PATROLZONE type. Works for all OPSGROUPs (air, ground and sea).

**FLIGHTGROUP**
- Improved EngageTarget
- Added option to automatically engage detected targets.

**TARGET**
- Improved Zone type
2021-01-15 23:29:16 +01:00
OttoWerkr 73b1394ca7 Update Scenery.lua
Added FindByName() to SCENERY Class.
2021-01-15 14:27:43 -05:00
Frank f3a2cf7284 Patrol
- Added `FLIGHTGROUP:SetEngageDetectedOn()`
- AUFTRAG patrol zone.
2021-01-15 00:59:52 +01:00
Frank df79c765f8 Merge branch 'master' into develop 2021-01-12 17:40:40 +01:00
Frank d09737c218 Merge branch 'master' into develop 2021-01-12 13:51:26 +01:00
Frank 6b0c42ae50 Merge branch 'master' into develop 2021-01-12 10:45:11 +01:00
Frank 7363da1b54 Merge branch 'master' into develop 2021-01-09 23:48:42 +01:00
Frank 684f19c060 Merge branch 'master' into develop 2021-01-05 22:59:57 +01:00
Frank d08e4db0eb Merge branch 'master' into develop 2021-01-03 00:24:42 +01:00
Frank f1e7d8bd06 Merge branch 'master' into develop 2021-01-01 10:16:58 +01:00
Frank 1328ac061e Merge branch 'master' into develop 2020-12-31 21:22:59 +01:00
Frank 391f0fc9af Merge branch 'master' into develop 2020-12-29 23:38:44 +01:00
Frank 38cf5be738 Update Airbase.lua 2020-12-29 22:51:10 +01:00
Frank 1bb70b8697 Update Warehouse.lua
- added airbase coalition check when spawning aircraft #1413
2020-12-29 22:28:36 +01:00
Frank 6e35b80daf Update Warehouse.lua
- requests should no longer be processed if warehouse has no airbase
2020-12-29 21:41:15 +01:00
Frank a171c22ce4 Merge branch 'develop' into FF/Ops 2020-12-29 11:23:04 +01:00
Frank f56457bc28 Update Intelligence.lua
- added reject zones
2020-12-29 11:22:47 +01:00
Frank 9708de70c7 Merge branch 'master' into develop 2020-12-27 17:59:57 +01:00
Frank d68589bb70 Merge branch 'master' into develop 2020-12-23 16:10:07 +01:00
Frank 16eb38782a Merge branch 'master' into develop 2020-12-23 10:54:57 +01:00
Frank 2175e9c0d6 Merge branch 'master' into develop 2020-12-22 09:49:08 +01:00
Frank 9abd71f681 Merge branch 'develop' into FF/Ops 2020-12-21 00:13:09 +01:00
Frank d1dc829136 Merge branch 'master' into develop 2020-12-20 23:48:28 +01:00
Frank eda672bdc6 Merge branch 'develop' into FF/Ops 2020-12-19 12:48:12 +01:00
Frank 0e5dbbea79 Merge branch 'master' into develop 2020-12-19 01:18:17 +01:00
Frank f5809ef302 Merge branch 'develop' into FF/Ops 2020-12-18 23:50:59 +01:00
Frank 457ce1ece1 Merge branch 'master' into develop 2020-12-18 13:52:23 +01:00
Frank 9c9841332f Merge branch 'master' into develop 2020-12-17 13:55:38 +01:00
Frank 9470d3a2ef Merge branch 'master' into develop 2020-12-17 12:34:59 +01:00
Frank 8356271f8d Merge branch 'master' into develop 2020-12-17 12:34:04 +01:00
Frank 42a6f3272d Merge branch 'master' into develop 2020-12-16 00:12:38 +01:00
Frank 362b242c41 Merge pull request #1398 from Applevangelist/patch-20
Error in FlightGroup.lua in line 2390 as detected by HRP Zero
2020-12-15 00:36:57 +01:00
Frank 7ace7b49ca Merge branch 'develop' into FF/Ops 2020-12-15 00:14:15 +01:00
Frank 7cd902e924 Merge branch 'master' into develop 2020-12-15 00:11:34 +01:00
Applevangelist e3c14678e2 Error in FlightGroup.lua in line 2390 as detected by HRP Zero
Not searching in Paris, but in pairs :)
2020-12-14 13:37:17 +01:00
Frank bfaca75a7d Merge branch 'master' into develop 2020-12-14 12:37:45 +01:00
Frank 5aecd8c255 Ops
- Performance
2020-12-10 18:53:49 +01:00
Frank cfc0f1a1f7 Merge branch 'master' into develop 2020-12-03 23:18:32 +01:00
Frank edf657c65c Merge branch 'master' into develop 2020-11-30 11:40:34 +01:00
Frank 801d00fa26 Stuff 2020-11-30 11:38:36 +01:00
Frank eeffa31282 Update ChiefOfStaff.lua 2020-11-26 11:47:05 +01:00
Frank 206528a530 Merge branch 'FF/Ops' into FF/OpsDev 2020-11-26 10:56:17 +01:00
Frank 41690fdf7b Merge pull request #1385 from FlightControl-Master/FF/Ops
OPS
2020-11-25 11:34:41 +01:00
Frank 5b83f8e45b OPS 2020-11-25 11:27:07 +01:00
Frank 94863ed8ae Update OpsGroup.lua 2020-11-24 22:00:36 +01:00
Frank e63b99c890 Merge pull request #1379 from Applevangelist/patch-7
Update Controllable.lua
2020-11-22 15:54:03 +01:00
Frank e8542743b4 Merge pull request #1380 from Applevangelist/patch-8
Update Sead.lua
2020-11-22 15:53:14 +01:00
Applevangelist 309e1208c2 Update Sead.lua
Feature Request #1355
2020-11-22 14:17:42 +01:00
Applevangelist 3e0db05664 Update Controllable.lua
Added control to keep within limits of the options
2020-11-22 13:01:26 +01:00
Applevangelist 9573d92cc0 Update Controllable.lua
Add option to control a2a attack ranges for AIR fighter units. See https://wiki.hoggitworld.com/view/DCS_option_missileAttack
2020-11-22 12:07:49 +01:00
Frank 1abc0dcb6a Merge pull request #1378 from FlightControl-Master/FF/Ops
OPS
2020-11-21 21:55:20 +01:00
Frank 770fb3228f OPS
AUFTRAG
- Changed ESCORT engage distance input to NM (not meters)

TARGET
- Fixed bug in count targets
- Improved dead/destroyed management

FLIGHTGROUP
- Only register UNIT/STATIC kills (not SCENERY)

INTEL
- Improved agent set
2020-11-21 21:49:47 +01:00
Frank d6aa251c5e Merge pull request #1377 from Applevangelist/patch-6
Update AI_A2G_Dispatcher.lua
2020-11-20 18:17:25 +01:00
Frank 9305b870d2 Merge pull request #1376 from Applevangelist/patch-5
Update AI_A2A_Dispatcher.lua
2020-11-20 18:17:00 +01:00
Applevangelist 824a98d7a8 Update AI_A2G_Dispatcher.lua
Wrapper for player messages and function to set this on/off
2020-11-20 10:43:39 +01:00
Applevangelist 7a55ba105c Update AI_A2A_Dispatcher.lua
Wrap player messages, added function to switch on/off
2020-11-20 10:34:53 +01:00
Frank 122425338e Merge branch 'master' into develop 2020-11-20 10:18:05 +01:00
Frank 1d7f874a78 Merge pull request #1375 from FlightControl-Master/FF/Ops
OPS
2020-11-19 21:49:02 +01:00
Frank afb63a7331 Update Target.lua
- Fixed escort problem
2020-11-19 19:51:58 +01:00
Frank 0c07a660ee WAREHOUSE
- Added runway destroyed check.
2020-11-17 00:25:37 +01:00
Frank 8ffda53e3d Chief and Wing Commander
- Added back in
2020-11-16 18:11:15 +01:00
Frank 5a30c08ff6 Merge pull request #1369 from FlightControl-Master/FF/Ops
TARGET
2020-11-16 13:33:14 +01:00
Frank 9cac2674c4 Update Target.lua
- Alive checks improved
2020-11-16 13:22:53 +01:00
Frank ec3f43c69f Merge pull request #1367 from FlightControl-Master/FF/Ops
OPS
2020-11-16 00:02:20 +01:00
Frank 270dfab6a2 OPS
less output again
2020-11-15 23:49:25 +01:00
Frank 624b1f5647 Merge branch 'develop' into FF/Ops 2020-11-15 23:34:34 +01:00
Frank 84332a3ebf Update FlightGroup.lua
Fixes bug when flight searches a tanker when fuel is low.
2020-11-15 23:33:43 +01:00
Frank 63ff79ef0b Merge branch 'master' into FF/Ops 2020-11-15 23:00:50 +01:00
Frank 09452444d7 Merge branch 'master' into develop 2020-11-15 23:00:23 +01:00
Frank 75be2fc0ba Update Target.lua 2020-11-15 21:44:08 +01:00
Frank 1e0a0f8dc5 Ops 2020-11-13 23:26:56 +01:00
Frank f3d9b9c5be Ops 2020-11-06 00:29:42 +01:00
Frank cef0e444c9 Ops 2020-11-04 22:48:14 +01:00
Frank bad8dc2220 Merge pull request #1362 from FlightControl-Master/FF/Ops
Ops Enhancements
2020-10-31 22:05:58 +01:00
Frank e05d57d908 Merge branch 'develop' into FF/Ops 2020-10-31 21:22:17 +01:00
Frank 45c153f7a0 Merge branch 'master' into develop 2020-10-31 21:21:47 +01:00
Frank 0948f418cd Ops
- Fixed payload not reduced
2020-10-31 11:57:23 +01:00
Frank d027113b0b Merge branch 'develop' into FF/Ops 2020-10-28 09:48:36 +01:00
Frank ff00c2fb15 Merge branch 'master' into develop 2020-10-28 09:46:34 +01:00
Frank cd5b4ee1be Update AI_Formation.lua
- fixed bug
- set default mode to following
2020-10-28 09:44:33 +01:00
Frank ac337b8459 Ops
- Laser
2020-10-28 09:06:22 +01:00
Frank 567f7cbc29 Update OpsGroup.lua
- Laser
2020-10-27 00:12:59 +01:00
Frank d81ffcf119 Merge branch 'develop' into FF/Ops 2020-10-26 16:16:56 +01:00
Frank 6ed66a70a2 Merge branch 'master' into develop 2020-10-26 16:16:28 +01:00
Frank 9ed3b045f7 Merge branch 'master' into develop 2020-10-26 09:17:41 +01:00
Frank 2d6653c14a Ops
- Laser
2020-10-26 08:01:56 +01:00
Frank ab0e7e735a Merge branch 'develop' into FF/Ops 2020-10-20 23:40:52 +02:00
Frank 3243555d3f Merge branch 'master' into develop 2020-10-20 23:31:30 +02:00
Frank 8696ce0cc1 Ops 2020-10-20 23:04:15 +02:00
Frank 459ff26868 Ops 2020-10-19 23:33:39 +02:00
Frank 277eb2d09f Ops
- Laser
2020-10-18 00:02:48 +02:00
Frank 187093fa5a NavyGroup Update 2020-10-14 00:24:12 +02:00
Frank 6c9ce55759 Navygroup
- Fixed Astar bug
- Turn into wind stop
2020-10-13 00:29:31 +02:00
Frank f9721e501a Merge branch 'FF/Ops' into develop 2020-10-12 00:22:47 +02:00
Frank 23c22651be Update Airboss.lua
- Added USS Harry S. Truman (CVN-75) as SC type.
2020-10-12 00:21:47 +02:00
Frank 2ad81ca499 Ops 2020-10-12 00:09:02 +02:00
Frank 7792381c36 OPS 2020-10-09 17:10:24 +02:00
Frank 591dd128ce Navygroup 2020-10-09 00:15:19 +02:00
Frank ce10390406 ARMYGROUP 2020-10-07 23:26:46 +02:00
Frank bf299e5f8d Merge pull request #1353 from FlightControl-Master/FF/Ops
OPS fixes
2020-10-03 23:29:35 +02:00
Frank 7f98e703f1 Update Airbase.lua
- Fixed bug in the `AIRBASE:GetRunwaydata()` function.
2020-10-03 23:12:17 +02:00
Frank 421d27f640 Update Airboss.lua
- Fix if unit skill is not set in env.mission (DCS bug that should be fixed by ED meanwhile).
2020-10-03 21:43:09 +02:00
Frank fb1e95ab87 Pathfinding 2020-10-03 18:22:23 +02:00
Frank 3737592734 Ops
Fixes
2020-10-01 23:55:39 +02:00
Frank 6445cf01a2 Merge pull request #1350 from FlightControl-Master/FF/Ops
OPS Update
2020-09-20 00:26:10 +02:00
Frank 8e07b93eac Merge branch 'develop' into FF/Ops 2020-09-19 23:57:25 +02:00
Frank 23f6752362 Ops
- rm staff
2020-09-19 23:55:45 +02:00
Frank 19b870c038 Ops
- Less traceing.
2020-09-19 23:52:17 +02:00
Frank ca7f15e622 Ops 2020-09-19 01:15:34 +02:00
Frank d497bb25f3 Ops 2020-09-18 18:07:10 +02:00
Frank 29d694722b Ops
- Quite a few fixes while going through the example missions.
2020-09-16 23:35:13 +02:00
Frank 40ffef035b Ops 2020-09-15 18:11:19 +02:00
Frank a00c198bcc Update Warehouse.lua
- Added born check. Helps with delayed spawning on carriers.
2020-09-15 01:13:11 +02:00
Frank 717842b276 Ops 2020-09-10 00:34:17 +02:00
Frank d5c4c34759 Ops 2020-09-09 00:02:46 +02:00
Frank edb0032118 ops 2020-09-08 18:04:03 +02:00
Frank 123144d06e Ops temp 2020-09-08 00:15:29 +02:00
Frank 38f5d45d5b Merge branch 'master' into FF/Ops 2020-09-07 22:25:28 +02:00
Frank bcaf808b88 Ops little fixes 2020-09-07 22:17:33 +02:00
Frank 7b8db597ef Ops 2020-09-07 00:42:29 +02:00
Frank 18e192b235 Ops 2020-09-06 00:18:20 +02:00
Frank 5ad30277ba Ops 2020-09-05 00:56:44 +02:00
Frank 2e998dc315 Ops 2020-09-04 17:20:40 +02:00
Frank cda1658541 Ops 2020-09-04 00:58:37 +02:00
Frank 8b640912c3 Ops 2020-09-02 22:54:54 +02:00
Frank 81a773b861 Merge branch 'master' into develop 2020-09-02 00:09:15 +02:00
Frank 444cc43971 Ops 2020-09-02 00:05:05 +02:00
Frank eb86d59203 Ops 2020-09-01 11:42:45 +02:00
Frank 5ee35f3fbb Less output 2020-08-31 12:50:50 +02:00
Frank 9a91145f89 Small 2020-08-30 23:44:15 +02:00
Frank 637081ebdd Ops 2020-08-30 22:52:18 +02:00
Frank 7d2746e7be Merge branch 'develop' into FF/Ops 2020-08-30 17:24:57 +02:00
Frank c84a92c787 Merge branch 'master' into develop 2020-08-30 17:15:31 +02:00
Frank b775aaa274 Merge branch 'master' into FF/Ops 2020-08-30 17:05:09 +02:00
Frank a97d7abc24 Range Ops 2020-08-30 17:03:16 +02:00
Frank 7e73db505b Decreased Tracing 2020-08-30 01:38:18 +02:00
Frank a126906776 up 2020-08-29 21:24:15 +02:00
Frank 2d6092c114 TIMER
- Added new TIMER class.
2020-08-29 20:26:20 +02:00
Frank caef309547 Ops
- Airboss optimizations WIP
2020-08-28 13:24:29 +02:00
Frank f9ec21a2b5 Ops 2020-08-26 00:01:52 +02:00
Frank 06600b2a94 Ops 2020-08-25 17:55:53 +02:00
Frank 3f39ec0ae0 Stuff 2020-08-25 01:03:18 +02:00
Frank cfc45cf068 Warehouse marker and spawning update 2020-08-23 01:08:02 +02:00
Frank 45d809a641 Update Profiler.lua 2020-08-22 22:39:54 +02:00
Frank fcfbc5e17c Profiler
- Added lua Profiler.
2020-08-22 21:24:35 +02:00
Frank f30c66424c Radioqueue Profiler 2020-08-22 21:18:12 +02:00
Frank f823fc6ee0 Update AI_Formation.lua
Performance update
2020-08-22 16:31:56 +02:00
Frank aac291c0c6 Performance Optimizations 2020-08-22 00:09:36 +02:00
Frank 61adeeeda3 Delete Astar - Copy.lua 2020-08-20 01:24:28 +02:00
Frank f94a5eed61 Merge branch 'develop' into FF/OpsDev 2020-08-19 23:21:06 +02:00
Frank 0695c48e7a Merge branch 'master' into develop 2020-08-19 23:12:05 +02:00
Frank f4bfcf58fd Ops 2020-08-19 22:32:16 +02:00
Frank dee7307adc Ops 2020-08-19 01:06:28 +02:00
Frank 114032a743 Ops 2020-08-18 18:15:22 +02:00
Frank e9e6a63e6a Ops 2020-08-18 01:35:24 +02:00
Frank 2a4f6020c2 Ops 2020-08-17 01:24:51 +02:00
Frank 3ea8b3737f Update Profiler.lua 2020-08-16 01:18:18 +02:00
Frank ed47b4b3db Ops 2020-08-16 00:35:42 +02:00
Frank adc89feda9 Ops 2020-08-15 00:54:36 +02:00
Frank 32c9a59ff0 Ops 2020-08-14 18:09:13 +02:00
Frank 200c1dac85 Ops 2020-08-14 01:01:45 +02:00
Frank cf65c2af15 Ops 2020-08-13 01:21:02 +02:00
Frank fb6ebc42a5 Ops 2020-08-10 23:45:52 +02:00
Frank c7696c375e Ops 2020-08-10 01:46:03 +02:00
Frank 337e1a837f Ops 2020-08-08 01:10:03 +02:00
Frank 495a9fd3a0 Ops Target
New Class.
2020-08-06 01:43:42 +02:00
Frank e6f7493ddd Ops Fixes 2020-08-05 00:39:48 +02:00
Frank 76e75505e5 Ops 2020-08-04 16:56:45 +02:00
Frank dafbd3f368 Ops & Auftrag 2020-08-04 01:10:53 +02:00
Frank 11ec70441a Ops group 2020-08-03 00:31:10 +02:00
Frank d1a18913f3 Ops 2020-08-02 01:31:47 +02:00
Frank 775b9e9fde Fixed bugs with new option table. 2020-08-01 00:25:12 +02:00
Frank 286e34e057 Profiler 2020-07-31 17:32:24 +02:00
Frank da2aa00442 A*
Further improvements by switching to keys.
2020-07-31 00:09:21 +02:00
Frank ba90176bb1 A*
Further optimized by using DCS API land.isVisible instead of Moose IsLoS function.
2020-07-30 21:40:27 +02:00
Frank cf9248ecc8 A*
- Improved performance by caching cost and valid/invalid neighbours.
2020-07-30 17:41:26 +02:00
Frank 0d42b12658 Enhanced Groups 2020-07-30 01:08:25 +02:00
Frank 2f6d9b640f NAVY 2020-07-29 01:10:13 +02:00
Frank 04923b65b2 Stuff 2020-07-24 01:34:38 +02:00
Frank 2328f9a36b CHIEF 2020-07-23 19:15:26 +02:00
Frank 98971736f8 Astar 2020-07-23 00:24:59 +02:00
Frank ec809085b4 Waypoints 2020-07-22 01:05:24 +02:00
Frank af023c1994 Waypoints 2020-07-21 17:53:59 +02:00
Frank d9b7cc18f3 A* 2020-07-21 01:19:03 +02:00
Frank ce1121a4c2 INTEL
Clusters
2020-07-20 01:20:28 +02:00
Frank 5fa75bf6b9 INTEL
Improved clusters
2020-07-19 18:31:09 +02:00
Frank 5cb1036618 CHIEF
- Added Chief of Staff class.
2020-07-19 01:22:49 +02:00
Frank 9473dc2069 Update Intelligence.lua 2020-07-16 01:04:43 +02:00
Frank 81f5184b43 Added WINCOM;MANDER 2020-07-15 21:48:11 +02:00
Frank b6bc22f991 Squadron Grouping 2020-07-15 00:51:27 +02:00
Frank 56fad29df9 ops 2020-07-14 17:56:18 +02:00
Frank dfeea53cf6 Merge branch 'develop' into FF/Ops 2020-07-14 00:33:29 +02:00
Frank 71e6369960 INTEL class 2020-07-13 22:31:33 +02:00
Frank 0611db78b3 Intel 2020-07-13 18:31:01 +02:00
Frank 8c768cc517 Update Intelligence.lua 2020-07-12 22:20:20 +02:00
Frank b67750d808 Intel 2020-07-12 20:57:37 +02:00
Frank 96f2ad06e2 Update FlightGroup.lua
Fixed TACAN
2020-07-12 19:43:27 +02:00
Frank 5cf4833455 OpsGroup 2020-07-10 14:16:41 +02:00
Frank f578fcfdc7 NAVYGROUP
Improved dead/stop
2020-07-10 11:14:18 +02:00
Frank 9838e1af19 Ops 2020-07-09 00:06:02 +02:00
Frank cee443e28d Ops 2020-07-08 00:18:42 +02:00
Frank 1fe57cab67 bla 2020-07-08 00:12:57 +02:00
Frank 80b22a8444 Ops 2020-07-08 00:07:30 +02:00
Frank 71733babf8 NavyGroup 2020-07-07 22:28:54 +02:00
Frank 9f793d6cf7 OpsGroup 2020-07-07 00:47:42 +02:00
Frank 46e73a108f Ops 2020-07-04 23:16:57 +02:00
Frank b37a8f3217 Merge branch 'FF/Ops' into FF/Fox2 2020-07-02 10:58:55 +02:00
Frank 1202187e2d Update Warehouse.lua 2020-07-02 10:48:49 +02:00
Frank b13ddc958b Update Warehouse.lua 2020-07-02 10:48:08 +02:00
Frank ba3954ad3f Update Airboss.lua 2020-07-01 23:56:05 +02:00
Frank f2e417b21f Ops 2020-07-01 23:52:27 +02:00
Frank d93d0afdf3 Ops 2020-06-30 00:33:26 +02:00
Frank 66d913e4bf Merge branch 'FF/Ops' into FF/Fox2 2020-06-29 21:10:46 +02:00
Frank 207c9ed62e blanks 2020-06-29 21:08:57 +02:00
Frank e3f4aa3d64 Update FlightGroup.lua 2020-06-29 21:05:26 +02:00
Frank 02d30fa2b4 Ops 2020-06-29 21:04:42 +02:00
Frank c287a96c7f Merge branch 'FF/Ops' into FF/Fox2 2020-06-29 20:58:15 +02:00
Frank 2392c9dc8e Ops 2020-06-29 20:55:32 +02:00
Frank 682c1f5ef2 Ops 2020-06-27 22:44:50 +02:00
Frank 50305f3d6f Update Utils.lua 2020-06-15 20:33:27 +02:00
Frank 1129e34e01 Merge branch 'master' into FF/Fox2 2020-06-11 23:48:34 +02:00
Frank 76dfcf751c Update SpawnStatic.lua 2020-06-10 22:35:43 +02:00
Frank 4227186383 FG 2020-06-10 21:17:03 +02:00
Frank 9d3341ab9f Merge branch 'master' into FF/Fox2 2020-06-05 00:04:01 +02:00
Frank fd432dc0d1 Update ATIS.lua 2020-06-04 23:28:39 +02:00
Frank ee12dc0a7d The Channel Update
**AIRBASE**
- Added "The Channel" map airport enumerators.

**ATIS v0.8.0**
- Added sunset and sunrise time.

**UTILS**
- Corrected NTTR map local time diff to GMT-8 (was -7) hours.
- Corrected Normandy map local time diff to GMT+0 (was +1) hours.
- Added "The Channel" map local time diff as GMT+2 (should be 0).
- Fixed bug in UTILS.GetSunRiseAndSet() function.
2020-06-04 23:26:56 +02:00
Frank 6f940fbf46 Docs 2020-06-03 17:33:14 +02:00
Frank 5fbdeb7a78 Update FlightControl.lua 2020-05-31 15:34:58 +02:00
Frank 64f278e2e7 Merge branch 'master' into FF/Fox2 2020-05-27 22:54:17 +02:00
Frank 6fcc3ef1e2 Updates 2020-05-27 22:00:03 +02:00
Frank 0e073f3f8b Update SpawnStatic.lua 2020-05-27 16:00:55 +02:00
Frank b3b021550c Spawn Static 2020-05-27 11:03:28 +02:00
Frank 42e05aa28a SpawnStatic 2020-05-26 18:08:26 +02:00
Frank fd726f1da8 SPAWNSTATIC 2020-05-26 00:59:41 +02:00
Frank 44786c3ea4 Update SpawnStatic.lua 2020-05-25 22:06:57 +02:00
Frank 12d69c057a Update SpawnStatic.lua 2020-05-25 20:55:59 +02:00
Frank 4841a06c9f Merge branch 'master' into FF/Fox2 2020-05-25 00:25:52 +02:00
Frank 1ee03dfe8c Update Utils.lua 2020-05-24 23:16:05 +02:00
Frank c1ae6be799 Update Point.lua 2020-05-24 23:09:59 +02:00
Frank e689fe0366 Sunrise and Sunset 2020-05-24 01:58:10 +02:00
Frank 94fced4af0 Sunrise and set 2020-05-22 23:58:42 +02:00
Frank 5223a4beba Ops 2020-05-22 11:21:09 +02:00
Frank 3c7a90e2a2 Update NavyGroup.lua 2020-05-22 00:38:52 +02:00
Frank da2d56752d Refactoring 2020-05-21 18:01:58 +02:00
Frank b26fb5ac19 Refactoring 2020-05-21 00:16:19 +02:00
Frank 3bb8f35f63 Merge branch 'master' into FF/Fox2 2020-05-20 23:09:42 +02:00
Frank 3ca27c4852 Refactoring 2020-05-20 21:34:57 +02:00
Frank 32421bdb6a Update Auftrag.lua 2020-05-18 22:26:44 +02:00
Frank 06076f4154 Refactoring 2020-05-18 12:43:53 +02:00
Frank 322e079486 Refactoring 2020-05-18 10:26:48 +02:00
Frank f2af1b1921 Merge branch 'develop' into FF/Fox2 2020-05-17 18:49:00 +02:00
Frank c5d3db0bed Refactoring 2020-05-16 18:48:33 +02:00
Frank 66c6b8c2e4 Merge branch 'develop' into FF/Fox2 2020-05-16 13:17:14 +02:00
Frank 9b72254ccc Refactoring 2020-05-16 12:58:59 +02:00
Frank 5afd5cae27 Update NavyGroup.lua 2020-05-15 23:12:33 +02:00
Frank 4537784ad9 Refactoring 2020-05-15 22:44:15 +02:00
Frank c2f6940c5e Refactoring Ops 2020-05-14 22:50:27 +02:00
Frank a7011e33fa Refactoring 2020-05-13 16:35:32 +02:00
Frank 5686403f7f OPS 2020-05-13 11:31:50 +02:00
Frank 33859c2b31 Update NavyGroup.lua 2020-05-12 17:30:36 +02:00
Frank 8472a37cd8 NAVYGROUP 2020-05-11 18:52:12 +02:00
Frank ed49348155 Ops 2020-05-08 00:36:53 +02:00
Frank ae36353aab Ops FC 2020-05-07 18:05:41 +02:00
Frank c30d9f3472 Ops 2020-05-07 00:00:35 +02:00
Frank 853989bfc9 Ops 2020-05-06 16:05:12 +02:00
Frank df2dbed441 Ops 2020-05-06 01:13:39 +02:00
Frank 7f0f7cee0c Ops 2020-05-05 17:49:32 +02:00
Frank 14cf4edc39 Ops 2020-05-05 00:40:05 +02:00
Frank c1144b063b Merge branch 'FF/Develop' into FF/Fox2 2020-05-04 11:25:34 +02:00
Frank cd81911bfb Update WingCommander.lua 2020-05-01 23:50:59 +02:00
Frank d2962005ee Merge branch 'develop' into FF/Fox2 2020-05-01 23:06:28 +02:00
Frank c2d4f179ae Update Utils.lua
Big smoke and fire
2020-05-01 22:58:47 +02:00
Frank a51aeab811 Ops 2020-05-01 22:52:18 +02:00
Frank dbc8e8be67 Ops 2020-04-30 17:28:06 +02:00
Frank 1c93f06b9b Update Auftrag.lua 2020-04-30 12:39:25 +02:00
Frank 422e111857 Ops 2020-04-30 01:42:38 +02:00
Frank c8d2104c39 Ops 2020-04-29 16:09:11 +02:00
Frank b2a903e077 Update AirWing.lua 2020-04-29 00:14:44 +02:00
Frank 74bd2c1cfc Ops 2020-04-28 22:50:03 +02:00
Frank 4ea9c8508d Ops 2020-04-28 17:31:59 +02:00
Frank f9e5785f52 Ops 2020-04-28 00:42:13 +02:00
Frank 25e58393ea Ops 2020-04-27 01:48:16 +02:00
Frank 3653199bfc Update Auftrag.lua 2020-04-25 22:22:58 +02:00
Frank 443a37a9c5 Ops 2020-04-25 21:46:05 +02:00
Frank 78ef53fbea Merge branch 'FF/Develop' into FF/Fox2 2020-04-25 12:17:43 +02:00
Frank 5e63deb717 Merge branch 'FF/Develop' into FF/Fox2 2020-04-25 01:04:28 +02:00
Frank 5d8b0d150a Update Controllable.lua 2020-04-25 00:43:57 +02:00
Frank 0440982c83 Ops 2020-04-25 00:43:02 +02:00
Frank b683dd5352 Merge branch 'FF/Develop' into FF/Fox2 2020-04-24 22:57:08 +02:00
Frank ac975e7e86 Restored old CleanUp and Scoring 2020-04-24 16:55:15 +02:00
Frank 2745c6c1ca Merge branch 'FF/Develop' into FF/Fox2 2020-04-24 16:54:37 +02:00
Frank 918337f202 Merge branch 'FF/Develop' into FF/Fox2 2020-04-24 16:47:30 +02:00
Frank 5a2779c698 Update ATIS.lua 2020-04-24 16:42:07 +02:00
Frank 7429982bef Merge branch 'FF/Develop' into FF/Fox2 2020-04-24 16:06:59 +02:00
Frank 4222c4d945 Fixes 2020-04-23 23:19:11 +02:00
Frank 62bfdcff8f Merge branch 'FF/Develop' into FF/Fox2 2020-04-23 16:48:19 +02:00
Frank 06213ab583 Airboss v1.1.3 2020-04-23 16:38:33 +02:00
Frank 1a8f9b4aa2 Ops 2020-04-23 01:02:51 +02:00
Frank 7c07df19bf Ops 2020-04-22 18:06:18 +02:00
Frank d2629263c3 Ops 2020-04-22 00:33:52 +02:00
Frank 11fb5500f3 Ops 2020-04-21 01:04:24 +02:00
Frank 31697030ed Ops 2020-04-19 23:49:08 +02:00
Frank 54e799d3f6 Ops 2020-04-18 12:58:29 +02:00
Frank ca2ae9657c Update Warehouse.lua 2020-04-17 15:58:28 +02:00
Frank efaf7e856c Ops 2020-04-17 01:40:33 +02:00
Frank eeb5555400 Ops 2020-04-16 18:07:50 +02:00
Frank d72b33551e Ops Auftrag 2020-04-16 01:14:19 +02:00
Frank 5a6a9d1aed Ops 2020-04-15 00:45:28 +02:00
Frank 2810acc1f4 Ops 2020-04-13 22:41:05 +02:00
Frank 08e4b7baeb Ops 2020-04-13 01:44:39 +02:00
Frank 8d722c2ccc Ops & MARKER 2020-04-12 01:41:02 +02:00
Frank afe5df922a Ops 2020-04-11 17:14:36 +02:00
Frank 93a0b0e19e Ops 2020-04-10 01:26:37 +02:00
Frank d52867c541 Ops 2020-04-09 18:07:41 +02:00
Frank f554642505 Ops 2020-04-09 01:30:32 +02:00
Frank a0ecf58c7f Merge branch 'develop' into FF/Fox2 2020-04-08 16:09:04 +02:00
Frank 0ab54268e9 Update FlightGroup.lua 2020-04-08 01:38:42 +02:00
Frank fdf2605367 Merge branch 'FF/Develop' into FF/Fox2 2020-04-08 01:11:04 +02:00
Frank 3548cea999 Update Auftrag.lua 2020-04-07 21:36:48 +02:00
Frank 7f3ebe4f48 Ops 2020-04-07 01:22:06 +02:00
Frank f22837103b Ops 2020-04-06 00:19:50 +02:00
Frank 7465fd065f Ops 2020-04-05 01:37:36 +02:00
Frank 61d3035c76 Merge branch 'develop' into FF/Fox2 2020-04-04 23:52:36 +02:00
Frank 92402fc0ca Ops 2020-04-04 22:53:20 +02:00
Frank 6f1861f057 Update ATIS.lua 2020-04-04 16:21:39 +02:00
Frank 94259ab75a ATIS 2020-04-04 01:29:46 +02:00
Frank 2dbfe1d4ee Update Warehouse.lua 2020-04-03 17:48:39 +02:00
Frank d06d23cba2 Warehouse 2020-04-03 01:39:17 +02:00
Frank 8b9e49cfc6 Ops 2020-04-02 18:00:14 +02:00
Frank 618b4d7ded Ops 2020-04-02 01:03:32 +02:00
Frank b409b8dfc1 Update FlightGroup.lua 2020-04-01 17:46:53 +02:00
Frank efb514e97f Update FlightGroup.lua 2020-04-01 16:53:51 +02:00
Frank 81977aa04d Ops 2020-04-01 01:46:01 +02:00
Frank 3e5b87411e Ops 2020-03-31 01:15:05 +02:00
Frank 717ef02c44 Ops 2020-03-29 00:25:24 +01:00
Frank c4b8e4a36c Merge branch 'develop' into FF/Fox2 2020-03-28 20:48:19 +01:00
Frank 4ae4f76ce0 Ops 2020-03-28 20:47:49 +01:00
Frank c0a9d51f98 Merge branch 'develop' into FF/Fox2 2020-03-28 16:51:21 +01:00
Frank d9f7be4f25 Ops 2020-03-28 16:51:06 +01:00
Frank 891235032a Ops Auftrag 2020-03-27 22:19:20 +01:00
Frank dda74c471f Update Auftrag.lua 2020-03-27 19:03:52 +01:00
Frank f5c419919c Ops 2020-03-27 17:20:53 +01:00
Frank a95f3f2fa1 Ops 2020-03-26 23:59:30 +01:00
Frank 0e78a11fd6 Ops FC 2020-03-25 22:44:23 +01:00
Frank 130e089d1c Ops 2020-03-25 16:43:34 +01:00
Frank e675c991c4 Ops 2020-03-24 23:17:07 +01:00
Frank fc79188fe2 Ops 2020-03-24 21:34:05 +01:00
Frank 56ec7f122f Ops 2020-03-23 23:56:10 +01:00
Frank e1d09bdd8b Ops 2020-03-23 00:27:16 +01:00
Frank 1ae2171885 Ops 2020-03-22 00:33:15 +01:00
Frank c2257729d5 Ops 2020-03-20 23:11:29 +01:00
Frank 4dd8e53c1c Merge branch 'develop' into FF/Fox2 2020-03-20 16:47:41 +01:00
Frank 4991fc9cb4 Ops 2020-03-20 01:00:45 +01:00
Frank 1371445e94 Ops 2020-03-17 23:44:06 +01:00
Frank 8823b96b5f ops 2020-03-17 16:53:47 +01:00
Frank 9cb551e6fa Ops 2020-03-16 22:56:45 +01:00
Frank b9fc1df2e4 Ops 2020-03-16 16:44:23 +01:00
Frank 3bc387c1c7 Ops 2020-03-16 00:32:26 +01:00
Frank fb85b94d2d Ops 2020-03-14 23:26:58 +01:00
Frank 3409030e1a Merge branch 'develop' into FF/Fox2 2020-03-14 20:41:35 +01:00
Frank c0b816fd40 Merge branch 'FF/Develop' into FF/Fox2 2020-03-14 20:18:26 +01:00
Frank 9f14ed7a5c Ops 2020-03-14 18:38:52 +01:00
Frank e147a4c604 Ops 2020-03-14 02:03:20 +01:00
Frank fd7827c264 Ops Squadron 2020-03-12 14:29:41 +01:00
Frank 6c0b6b3b95 Ops
- First CAP version
2020-03-12 12:37:09 +01:00
Frank 04fb7f7684 Ops
Improved mission handling on cancel, done and repeat.
2020-03-11 19:35:42 +01:00
Frank fa9adaf046 Ops 2020-03-10 23:18:38 +01:00
Frank 6305459056 Ops 2020-03-10 17:03:24 +01:00
Frank a477fc78bc Ops 2020-03-10 00:44:50 +01:00
Frank 781ebc7a42 Ops 2020-03-09 00:23:56 +01:00
Frank 59603e3027 Ops 2020-03-08 00:35:06 +01:00
Frank dc7c50b97b Ops 2020-03-07 00:23:54 +01:00
Frank 2eb4e87d32 Ops 2020-03-06 17:29:43 +01:00
Frank fd730206a6 Merge branch 'FF/Develop' into FF/Fox2 2020-03-05 23:41:53 +01:00
Frank 7d9e721506 Ops 2020-03-05 23:40:18 +01:00
Frank 5f416b3271 Ops 2020-03-05 17:59:26 +01:00
Frank 589335e420 Ops 2020-03-05 00:13:49 +01:00
Frank 2214584e59 Ops 2020-03-04 17:22:30 +01:00
Frank 090d0e79f9 Ops 2020-03-03 22:57:11 +01:00
Frank ae3d1f6b2a Ops 2020-03-03 16:48:05 +01:00
Frank 9ea52fce28 Ops 2020-03-03 00:20:08 +01:00
Frank dc3350a1c8 FG
- SetTask --> PushTask to keep enroute tasks alive.
2020-03-01 23:55:15 +01:00
Frank 905095add9 Ops 2020-03-01 22:48:09 +01:00
Frank ce4353d701 Ops 2020-02-29 00:53:32 +01:00
Frank 929f99d527 Ops 2020-02-28 16:47:24 +01:00
Frank 541db97a74 Ops 2020-02-28 01:05:30 +01:00
Frank cc567cf342 Ops 2020-02-27 16:56:27 +01:00
Frank 8ed6f8b193 Update FlightGroup.lua 2020-02-26 23:06:36 +01:00
Frank 1dec76d96d Merge branch 'develop' into FF/Fox2 2020-02-26 21:12:33 +01:00
Frank 75216793f9 Ops 2020-02-26 17:03:23 +01:00
Frank 173e68676c Ops 2020-02-25 23:57:12 +01:00
Frank e67d5a6c9f Merge branch 'develop' into FF/Fox2 2020-02-25 22:47:37 +01:00
Frank 47e4777df0 Ops 2020-02-25 16:49:54 +01:00
Frank e1c602583c FG 2020-02-24 20:22:52 +01:00
Frank 8925bc11ca FG CONTROLLABLE
FG v0.3.2
CONTROLLABLE removed self dependencies for some tasks.
2020-02-22 11:03:15 +01:00
Frank 6e97433ec4 Ops 2020-02-21 16:59:51 +01:00
Frank bddc40ddb8 Update FlightGroup.lua 2020-02-20 23:16:32 +01:00
Frank 7d697dc584 Ops 2020-02-20 12:31:42 +01:00
Frank 02c63850a1 Ops 2020-02-19 23:30:11 +01:00
Frank 2d3ef543b3 Ops 2020-02-18 16:45:07 +01:00
Frank 78ccbd733b Merge branch 'FF/Develop' into FF/Fox2 2020-02-18 00:00:38 +01:00
Frank 99ab12ed99 Updates Base, Event, Point, Contollable, FG 2020-02-17 21:40:20 +01:00
Frank fd7469b5f0 FG 2020-02-16 17:38:37 +01:00
Frank 014b07ffc3 Merge branch 'FF/Develop' into FF/Fox2 2020-02-15 23:53:05 +01:00
Frank 165c569a99 Ops 2020-02-15 00:49:29 +01:00
Frank 6cb59a8f39 Merge branch 'FF/Develop' into FF/Fox2 2020-02-14 19:00:26 +01:00
Frank 2e49b72ab4 Ops 2020-02-14 16:51:49 +01:00
Frank 0da53c794e Update FlightGroup.lua 2020-02-14 00:44:33 +01:00
Frank c54a0968b9 Ops 2020-02-13 16:49:05 +01:00
Frank 5ba5dd5fd8 Ops 2020-02-12 23:44:31 +01:00
Frank 004591f8ad Ops 2020-02-12 16:44:01 +01:00
Frank b7543f24e8 Ops 2020-02-11 16:43:59 +01:00
Frank eb8853ddf4 FG 2020-02-10 23:22:36 +01:00
Frank 3a0e154daa FG/TG 2020-02-09 23:13:34 +01:00
Frank e004da9f00 Update FlightGroup.lua 2020-02-09 19:17:25 +01:00
Frank ecd76dec7d FG/TG 2020-02-08 23:42:52 +01:00
Frank 29995e1584 FG/TG 2020-02-06 12:40:23 +01:00
Frank b97b197219 FG 2020-02-04 23:15:48 +01:00
Frank 87b75b4269 FG 2020-02-03 22:25:02 +01:00
Frank 2b96071884 FG/TG 2020-02-02 23:46:59 +01:00
Frank 4ee3d8fb5c FG/TG 2020-02-02 01:01:04 +01:00
Frank 30c21086ba Intermediate 2020-01-31 23:34:07 +01:00
Frank 5fda8c1c1b FG TG 2020-01-31 12:02:13 +01:00
Frank 4bb96a7730 FG/TANKER 2020-01-30 16:50:03 +01:00
Frank 4ec757d87a FGFC 2020-01-29 22:52:06 +01:00
Frank 745da36e78 FG 2020-01-29 16:50:08 +01:00
Frank 3a2effb2dc Update FlightGroup.lua 2020-01-29 00:14:53 +01:00
Frank f45eef2dd2 FGFC 2020-01-28 16:48:42 +01:00
Frank d3825ccda1 Update FlightGroup.lua 2020-01-27 22:42:10 +01:00
Frank aaabf5220e FG/FC v0.2.4 2020-01-27 00:09:35 +01:00
Frank 43034ed0ad Merge branch 'develop' into FF/Fox2 2020-01-26 00:00:17 +01:00
Frank 50ee8865ed FG & FC 2020-01-25 23:32:41 +01:00
Frank 74a4b84b78 Update FlightControl.lua 2020-01-24 18:36:25 +01:00
Frank 56feca1451 FC 2020-01-24 16:54:46 +01:00
Frank d5cdb9ec34 FCFG 2020-01-23 16:57:15 +01:00
Frank 37d2602f2d FCFG 2020-01-22 22:53:22 +01:00
Frank fe7df16ac0 FCFG 2020-01-22 16:40:34 +01:00
Frank 1c1f74e1ad FG/FC 2020-01-22 00:23:18 +01:00
Frank 33d86bd081 FC 2020-01-21 16:40:07 +01:00
Frank e509eb3d35 FG/FC/ATIS v0.6.3 2020-01-20 23:25:43 +01:00
Frank ebc080b452 Merge branch 'FF/Develop' into FF/Fox2 2020-01-20 15:41:53 +01:00
Frank 25ddf06ad7 FCFG 2020-01-17 16:51:35 +01:00
Frank 5c23b102df FG 2020-01-16 23:36:54 +01:00
Frank 3e9ab6834e FCFG 2020-01-16 16:47:51 +01:00
Frank d9bfc65862 FC&FG 2020-01-15 22:58:25 +01:00
Frank c661491772 FC 2020-01-15 16:56:50 +01:00
Frank 0b2f6b4f3d Update FlightGroup.lua 2020-01-15 00:14:29 +01:00
Frank 75114effc1 Update FlightGroup.lua 2020-01-14 23:52:35 +01:00
Frank b513b2a049 Rat2 2020-01-14 16:52:32 +01:00
Frank 82c606edc1 Merge branch 'develop' into FF/Fox2 2020-01-08 22:26:05 +01:00
Frank 0550ed1566 Update FlightGroup.lua 2020-01-08 00:33:07 +01:00
Frank 9dad6fb9b9 Merge branch 'FF/Develop' into FF/Fox2 2020-01-07 23:36:10 +01:00
Frank 5d4fb7c1ba Merge branch 'develop' into FF/Fox2 2020-01-06 23:20:55 +01:00
Frank 2e15341f6b Merge branch 'FF/Develop' into FF/Fox2 2020-01-04 00:21:45 +01:00
Frank 8be023a5ee Merge branch 'develop' into FF/Fox2 2020-01-02 23:04:48 +01:00
Frank 6b66b0d567 Merge branch 'FF/Develop' into FF/Fox2 2019-12-31 00:01:11 +01:00
Frank 53bde229c3 Update FlightGroup.lua 2019-12-30 19:44:26 +01:00
Frank 4eaff505df Merge branch 'develop' into FF/Fox2 2019-12-29 23:49:14 +01:00
Frank bff55e29ea Merge branch 'FF/Develop' into FF/Fox2 2019-12-29 00:03:51 +01:00
Frank 3950008d05 AI_AIR 2019-12-28 19:51:32 +01:00
Frank bbc1da3c48 Merge branch 'FF/Develop' into FF/Fox2 2019-12-20 15:14:44 +01:00
Frank a4c6a67d33 Merge branch 'FF/Develop' into FF/Fox2 2019-12-18 23:01:10 +01:00
Frank dbb1fa05d7 Merge branch 'develop' into FF/Fox2 2019-12-17 23:39:04 +01:00
Frank 859b33fa08 FG & FC 2019-12-14 22:37:59 +01:00
Frank abf8601f67 Merge branch 'develop' into FF/Fox2 2019-12-12 18:22:22 +01:00
Frank cd33677de4 ATIS v0.6.0
- Added altimeter QNH
2019-12-11 20:50:25 +01:00
Frank 68fff65088 Merge branch 'develop' into FF/Fox2 2019-12-10 21:07:00 +01:00
Frank 99827bce47 RAT 2019-12-10 19:35:10 +01:00
Frank 2583c5c175 Merge branch 'develop' into FF/Fox2 2019-12-08 13:13:23 +01:00
Frank 61dafa7aa7 Merge branch 'FF/Develop' into FF/Fox2 2019-12-08 00:50:48 +01:00
Frank abbe53c7fa RAT 2019-12-06 23:31:44 +01:00
Frank 5b57873dee RAT 2019-12-06 16:55:21 +01:00
Frank 1d9b114c95 RAT2 2019-12-05 22:42:24 +01:00
Frank 88e7302838 RAT2 2019-12-05 17:06:58 +01:00
Frank b9cb19e8e0 Merge branch 'develop' into FF/Fox2 2019-12-04 22:18:09 +01:00
Frank 3e69143cfd Update Range.lua 2019-12-04 12:12:21 +01:00
Frank 7a53439408 Airbase and TaskBombingRunway 2019-12-02 21:58:32 +01:00
Frank 6a6c7a3236 Merge branch 'develop' into FF/Fox2 2019-12-02 17:06:39 +01:00
Frank f9a49ab7e7 f 2019-12-02 16:26:53 +01:00
Frank 5ad927d20c RAT 2019-11-27 16:46:03 +01:00
Frank 75a7f05761 Update FlightGroup.lua 2019-11-25 23:42:01 +01:00
Frank 70d8fa23fb Merge branch 'develop' into FF/Fox2 2019-11-25 12:34:13 +01:00
Frank a1e6ee7a90 Merge branch 'FF/Develop' into FF/Fox2 2019-11-25 12:24:15 +01:00
Frank b3fe6fe363 FC FG 2019-11-25 09:44:55 +01:00
Frank e978ff69bb Merge branch 'develop' into FF/Fox2 2019-11-23 21:03:13 +01:00
Frank 5fe9cb24ac Merge branch 'develop' into FF/Fox2 2019-11-23 20:45:57 +01:00
Frank cc5db0ef6b Update FlightControl.lua 2019-11-23 19:17:01 +01:00
Frank 3d1f0cc376 FC FG 2019-11-22 23:45:04 +01:00
Frank 751cd8c75c FC 2019-11-21 21:39:31 +01:00
Frank 26bb6471dc FC 2019-11-21 16:38:36 +01:00
Frank 1822df778d Parking 2019-11-20 23:17:31 +01:00
Frank 57af94ef7b Parking 2019-11-20 16:45:31 +01:00
Frank f79ec32c2b FG & FC
parking
2019-11-19 23:08:02 +01:00
Frank 8c91c753ee RAT 2019-11-19 16:48:56 +01:00
Frank f975fa8eae Update FlightControl.lua 2019-11-18 22:48:24 +01:00
Frank d0d58b3f24 Update FlightControl.lua 2019-11-17 22:17:49 +01:00
Frank 986459151f FC 2019-11-15 16:33:58 +01:00
Frank 5cf97ac802 FC FG 2019-11-14 21:50:55 +01:00
Frank a33c8219e1 Merge branch 'FF/Fox2' of https://github.com/FlightControl-Master/MOOSE into FF/Fox2 2019-11-14 16:08:27 +01:00
Frank f5ec6c1cb4 FC FG 2019-11-14 16:08:16 +01:00
Frank 6f1afa2de9 Update Warehouse.lua 2019-11-13 21:10:26 +01:00
Frank 7e9042074d FG 2019-11-13 15:59:34 +01:00
Frank 164bcc5557 Update FlightGroup.lua 2019-11-12 21:43:56 +01:00
Frank 50b61ea094 Ops 2019-11-12 20:10:29 +01:00
Frank b737da9c51 Arty v1.1.6 2019-11-12 16:13:07 +01:00
Frank b41e539b6e Ops FG & FC 2019-11-11 15:14:47 +01:00
Frank a2f35d816c FC 2019-11-10 00:37:31 +01:00
Frank b12d9e9a5d Merge branch 'develop' into FF/Fox2 2019-11-09 22:28:42 +01:00
Frank 5ba8979d94 Ops FG & FC 2019-11-09 20:40:40 +01:00
Frank 880fae604d Ops FlightGroup & FlightControl 2019-11-09 15:19:11 +01:00
Frank 0db24006e1 Merge branch 'develop' into FF/Fox2 2019-11-04 22:44:49 +01:00
Frank 05b87659f4 FG 2019-11-03 15:38:22 +01:00
Frank 3b58e1f127 Merge branch 'develop' into FF/Fox2 2019-10-31 19:31:44 +01:00
Frank c5705e8b1a ARTY v1.1.5 2019-10-31 18:52:35 +01:00
Frank 91dc8a9453 Update FlightGroup.lua 2019-10-30 11:23:37 +01:00
Frank 16ed06a10b Update FlightGroup.lua 2019-10-29 15:36:27 +01:00
Frank 4fb28b1bf5 Update FlightGroup.lua 2019-10-24 23:35:59 +02:00
Frank 7c2ad7e8d0 Merge branch 'FF/Develop' into FF/Fox2 2019-10-22 23:38:02 +02:00
Frank a79b4ba3a9 Merge branch 'FF/Develop' into FF/Fox2 2019-10-22 23:19:47 +02:00
Frank 7b92053277 ATIS v0.5.0 2019-10-21 22:14:10 +02:00
Frank 9e742b1620 ATIS UTILS 2019-10-21 21:58:27 +02:00
Frank b3be71c75b Merge branch 'FF/Develop' into FF/Fox2 2019-10-21 00:30:39 +02:00
Frank 010ca59281 Update Airboss.lua 2019-10-19 19:22:23 +02:00
Frank 1293679401 Merge branch 'develop' into FF/Fox2 2019-10-18 23:29:25 +02:00
Frank f1f3d69584 ATIS v0.4.1 2019-10-18 21:12:24 +02:00
Frank 8a00a31d5b Update Settings.lua 2019-10-14 19:29:13 +02:00
Frank f96a554a84 Update Settings.lua 2019-10-14 18:10:54 +02:00
Frank 70f49a9600 Merge branch 'FF/Develop' into FF/Fox2 2019-10-14 14:44:05 +02:00
Frank f968472c00 Ops
Airboss v1.0.9
Range v2.2.0
2019-10-14 14:32:47 +02:00
Frank c9f9ac7b0e Merge branch 'FF/Develop' into FF/Fox2 2019-10-11 01:55:42 +02:00
Frank 6a3c0b00a1 Update Settings.lua 2019-10-10 20:51:38 +02:00
Frank f194db0309 Merge branch 'FF/Develop' into FF/Fox2 2019-10-09 21:14:58 +02:00
Frank b0342c048d Merge branch 'FF/Develop' into FF/Fox2 2019-10-03 22:46:20 +02:00
Frank 6ae074cd10 Merge branch 'develop' into FF/Fox2 2019-10-02 23:41:34 +02:00
Frank d37a16f3ef ATIS v0.3.0 2019-10-02 18:10:42 +02:00
Frank 7efd2a3053 Merge branch 'FF/Develop' into FF/Fox2 2019-09-30 13:50:56 +02:00
Frank 5c8a3f695e ATIS v0.2.0 2019-09-30 00:33:19 +02:00
Frank fff30c21cd Ops 2019-09-28 00:51:01 +02:00
Frank 6eafba3032 Ops 2019-09-25 23:07:15 +02:00
Frank b96cd1909a ATIS v0.1.0 2019-09-23 12:15:44 +02:00
Frank aa294cd9c6 ATIS 2019-09-22 00:27:03 +02:00
Frank 8880bf8587 ATIS 2019-09-21 00:02:10 +02:00
Frank b0b8d81e5c OPS ATIS 2019-09-19 00:07:28 +02:00
Frank de2f7148ee FG/FC 2019-09-17 22:09:56 +02:00
Frank 69c8d52f52 Ops 2019-09-16 23:23:00 +02:00
Frank aae67268df Ops 2019-09-15 22:18:56 +02:00
Frank 417e88cc6b FlightControl/Group 2019-09-14 22:06:34 +02:00
Frank 6411b77284 Merge branch 'FF/Develop' into FF/Fox2 2019-09-13 19:43:17 +02:00
Frank 367c67f269 ops 2019-09-13 10:24:55 +02:00
Frank a3067740a3 Ops 2019-09-11 20:09:55 +02:00
Frank af3bf7dece OPS 2019-09-06 22:23:52 +02:00
Frank 553f14d2ea Ops 2019-09-05 09:49:27 +02:00
Frank 36e923cac7 Ops 2019-09-04 23:05:03 +02:00
Frank eb14fdc84b Ops 2019-09-04 16:57:01 +02:00
Frank 361854390f Ops 2019-09-03 23:15:20 +02:00
Frank c6fc6ab017 Update Range.lua 2019-09-03 18:06:15 +02:00
Frank 254dd92cd8 Ops 2019-09-03 16:59:20 +02:00
Frank 93af911891 up 2019-09-02 22:38:09 +02:00
Frank 1ecbf22f0a misc 2019-08-30 23:48:41 +02:00
Frank 3531b6a3e6 WH Ops 2019-08-30 10:53:20 -04:00
Frank 91035064fb FG v0.0.7 2019-08-28 10:55:50 -04:00
Frank 11944dab2f FG v0.0.6 2019-08-26 22:26:11 +02:00
Frank 15cbdabbeb FG v0.0.5 2019-08-26 17:54:50 +02:00
Frank 677a733767 Update FlightGroup.lua 2019-08-25 23:49:26 +02:00
Frank 657e3578e0 FG 2019-08-25 22:00:42 +02:00
Frank f9389f4aed FG merge 2019-08-23 10:51:47 -04:00
Frank c263221236 FG v0.0.4 2019-08-23 10:48:38 -04:00
Frank c0a1df4682 Update FlightGroup.lua 2019-08-23 00:41:43 +02:00
Frank aae8ef8053 Update Airboss.lua 2019-08-22 00:02:15 +02:00
Frank d9d246e194 Update FlightGroup.lua 2019-08-21 21:16:06 +02:00
Frank 2d4520995a FG 2019-08-21 10:52:24 -04:00
Frank 786e1ea784 FG v0.0.3 2019-08-21 00:14:05 +02:00
Frank 9e2f312126 FC 2019-08-20 10:42:51 -04:00
Frank 9797d47fbf FG 2019-08-19 23:45:48 +02:00
Frank 594afc5b8e FLIGHTGROUP v0.0.2
- Proper flight status.
2019-08-19 14:25:15 +02:00
Frank d530ba5c3e Merge branch 'FF/Develop' into FF/Fox2 2019-08-18 22:38:14 +02:00
Frank 94bbb8a9a4 RH, GROUP 2019-08-18 21:02:23 +02:00
Frank aade1eebf1 AB, RH 1.0.9 2019-08-18 12:46:22 +02:00
Frank f16db1d4e5 Update Airboss.lua 2019-08-18 11:55:06 +02:00
Frank 17372a16c6 AB, FG 2019-08-16 10:49:19 -04:00
Frank 059bc05527 Merge 2019-08-16 03:46:22 -04:00
Frank 296a7a808a FG 2019-08-16 03:17:03 -04:00
Frank db3ab439d0 FG 2019-08-14 10:36:09 -04:00
Frank a26c9f423c Update FlightGroup.lua 2019-08-12 15:18:42 +02:00
Frank d1e4295178 Merge branch 'FF/Develop' into FF/Fox2 2019-08-11 20:41:45 +02:00
Frank 043e1dc74b Merge branch 'FF/Develop' into FF/Fox2 2019-08-11 20:39:15 +02:00
Frank 10154a6e4e Added SWAPR 2019-08-11 20:34:04 +02:00
Frank 1e1e0758f8 Update FlightGroup.lua 2019-08-11 18:28:57 +02:00
Frank f069523ba6 Flight Group 2019-08-10 22:55:01 +02:00
Frank 6c846380c0 WH
- WAREHOUSE
- FlIGHTGROUP
2019-08-09 21:45:50 +02:00
Frank c172c6d6d6 Update Warehouse.lua 2019-08-09 00:16:43 +02:00
Frank acd0ec187f Update Warehouse.lua 2019-08-08 18:59:14 +02:00
Frank 965ae51223 Update Airboss.lua 2019-08-08 17:00:49 +02:00
funkyfranky 65557db444 WH 2019-08-08 16:28:53 +02:00
funkyfranky 414196afc6 CVW 2019-08-07 16:49:26 +02:00
Frank eb37f9b18c CVW 0.0.5 2019-08-04 11:05:05 +02:00
Frank 6f7f20f7a8 Merge branch 'FF/Develop' into FF/Fox2 2019-08-02 20:46:59 +02:00
funkyfranky 2533651215 CVW 0.0.4 2019-08-02 16:51:00 +02:00
Frank aa4e5d0e7b Detection fix issue #1173
fix for #1173
2019-06-26 09:05:31 +02:00
funkyfranky aef5f0c11a SET 2019-06-25 16:56:15 +02:00
Frank a43769981d Merge branch 'FF/Develop' into FF/Fox2 2019-06-24 13:59:43 +02:00
funkyfranky b0a67686e3 CVW 2019-06-21 16:24:08 +02:00
funkyfranky 4622affe70 CVW 2019-06-21 08:39:44 +02:00
Frank 6b55f0e5dc CVW 0.0.5 2019-06-20 00:42:04 +02:00
Frank 92a75d488c Update CarrierAirWing.lua 2019-06-19 21:00:30 +02:00
funkyfranky c76703207a CVW 2019-06-19 16:22:04 +02:00
funkyfranky 0418dfaa32 CVW 2019-06-18 16:27:18 +02:00
Frank 15d7c36d8f CVW 2019-06-17 23:50:03 +02:00
Frank 305a738926 Skipper v0.0.5 2019-06-16 22:20:26 +02:00
funkyfranky ba1b42f209 SK 2019-06-14 16:23:55 +02:00
Frank b45b3c9804 Update Skipper.lua 2019-06-13 22:32:55 +02:00
funkyfranky fa048311ca Skipper 2019-06-13 15:55:11 +02:00
Frank 23bffd8c1d SKIPPER v0.0.3 2019-06-12 22:43:13 +02:00
funkyfranky 731517dcf2 AB 1.0.3 2019-06-12 16:30:37 +02:00
Frank 6ac1194746 Skipper v0.0.1
- Added skipper class.
- Other minor improvements.
2019-06-11 22:57:38 +02:00
Frank 9b2ed229b2 Merge branch 'FF/Develop' into FF/Fox2 2019-06-08 23:10:05 +02:00
funkyfranky 8ad430347c Score 2019-06-07 15:46:40 +02:00
funkyfranky 9c344eff6e Score 2019-06-06 15:57:05 +02:00
Frank 557a652f73 Merge branch 'FF/Develop' into FF/Fox2 2019-05-26 12:18:05 +02:00
Frank 1ce7ecf64e Fox file 2019-05-26 12:16:42 +02:00
Frank 9c84e995ac FOX 2019-05-26 11:54:45 +02:00
Frank 93936a07d0 Merge branch 'develop' into FF/Fox2 2019-05-26 11:43:35 +02:00
Frank 7a000b02ce WH 0.9.0
WH: Added AssedLowFuel event.
2019-05-26 00:01:15 +02:00
Frank fbff95ed29 FOX update 2019-05-24 23:08:48 +02:00
Frank 7a79d49659 Update Fox2.lua 2019-05-24 09:24:59 +02:00
Frank 614a1cc906 Fixed zone find 2019-05-23 21:07:47 +02:00
Frank 1e1335e2ed Update Scoring.lua 2019-05-16 21:09:18 +02:00
funkyfranky 70bca0d142 SC 2019-05-15 16:47:38 +02:00
funkyfranky eb3037c913 Sc 2019-05-14 16:42:49 +02:00
Frank 8e92b8045b MISC
FMD: misc
FOX v0.3.0 improvements
RANGE v2.1.1 fixed bugs
SCORING added option to reload csv file
WAREHOUSE v0.7.1 added AssedSpawned FMS event.
2019-05-13 21:57:09 +02:00
funkyfranky b795d2fd09 FMD 2019-05-09 16:27:54 +02:00
Frank a73faca149 Update FlightModelData.lua 2019-05-08 23:13:32 +02:00
Frank 96a7a963e6 Update Radio.lua 2019-05-07 21:25:24 +02:00
funkyfranky e206fcd5d5 RQ 2019-05-07 15:37:20 +02:00
Frank bbc353a861 RANGE 2.1.0
RADIOQUEUE class added.
WAREHOUS fixed bug with speed unit conversion.
2019-05-07 01:10:48 +02:00
funkyfranky ba1857950e RAT2 2019-05-03 16:28:40 +02:00
Frank 43193cf7f6 RANGE 2.0 2019-05-01 17:27:30 +02:00
Frank 43c95b6ab5 RANGE 2.0.0
RANGE
- Converted to FSM.
2019-04-29 20:17:09 +02:00
Frank da44f6ef22 Merge branch 'FF/Develop' into FF/Fox2 2019-04-23 19:21:56 +02:00
Frank 6113455520 Rescuehelo 1.0.6 2019-04-23 19:05:54 +02:00
Frank 1150c81ab9 AIRBOSS v0.9.9
FOX2 fixes
2019-04-19 19:44:06 +02:00
funkyfranky 5aabdb7593 Fox, AB 2019-04-18 16:29:00 +02:00
Frank 0a8f83b0fc Update Fox2.lua 2019-04-17 23:02:02 +02:00
funkyfranky b0adfc6bb5 Fox2 v0.1.1 2019-04-17 16:32:29 +02:00
funkyfranky e959b40805 Fox2 2019-04-16 16:36:33 +02:00
Frank 274771b212 Fox 2019-04-15 23:59:20 +02:00
113 changed files with 269659 additions and 11732 deletions
+7
View File
@@ -28,6 +28,13 @@ local.properties
.buildpath
#####################
## Visual Studio Code
#####################
*.code-workspace
.vscode/
#################
## Visual Studio
#################
@@ -0,0 +1,24 @@
env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' )
local base = _G
__Moose = {}
__Moose.Include = function( IncludeFile )
if not __Moose.Includes[ IncludeFile ] then
__Moose.Includes[IncludeFile] = IncludeFile
local f = assert( base.loadfile( IncludeFile ) )
if f == nil then
error ("Moose: Could not load Moose file " .. IncludeFile )
else
env.info( "Moose: " .. IncludeFile .. " dynamically loaded." )
return f()
end
end
end
__Moose.Includes = {}
__Moose.Include( 'Scripts/Moose/Modules.lua' )
BASE:TraceOnOff( true )
env.info( '*** MOOSE INCLUDE END *** ' )
File diff suppressed because it is too large Load Diff
@@ -884,8 +884,9 @@ do -- AI_A2A_DISPATCHER
--- Enumerator for spawns at airbases
-- @type AI_A2A_DISPATCHER.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff
--- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
---
-- @field #AI_A2A_DISPATCHER.Takeoff Takeoff
AI_A2A_DISPATCHER.Takeoff = GROUP.Takeoff
--- Defnes Landing location.
@@ -931,6 +932,8 @@ do -- AI_A2A_DISPATCHER
self.DefenderTasks = {} -- The Defenders Tasks.
self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons.
self.SetSendPlayerMessages = false --#boolean Flash messages to player
-- TODO: Check detection through radar.
self.Detection:FilterCategories( { Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } )
--self.Detection:InitDetectRadar( true )
@@ -951,7 +954,6 @@ do -- AI_A2A_DISPATCHER
self:SetDefaultCapTimeInterval( 180, 600 ) -- Between 180 and 600 seconds.
self:SetDefaultCapLimit( 1 ) -- Maximum one CAP per squadron.
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
@@ -1709,7 +1711,7 @@ do -- AI_A2A_DISPATCHER
end
--- Set the Squadron visible before startup of the dispatcher.
--- [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.
-- They will lock the parking spot.
-- @param #AI_A2A_DISPATCHER self
@@ -2358,6 +2360,12 @@ do -- AI_A2A_DISPATCHER
return self
end
--- Set flashing player messages on or off
-- @param #AI_A2A_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function AI_A2A_DISPATCHER:SetSendMessages( onoff )
self.SetSendPlayerMessages = onoff
end
--- Sets flights to take-off in the air, as part of the defense system.
-- @param #AI_A2A_DISPATCHER self
@@ -3041,7 +3049,20 @@ do -- AI_A2A_DISPATCHER
for FriendlyDistance, AIFriendly in UTILS.spairs( DefenderFriendlies or {} ) do
-- We only allow to ENGAGE targets as long as the Units on both sides are balanced.
if AttackerCount > DefenderCount then
local Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP
--self:I("***** AI_A2A_DISPATCHER:CountDefendersToBeEngaged() *****\nThis is supposed to be a UNIT:")
if AIFriendly then
local classname = AIFriendly.ClassName or "No Class Name"
local unitname = AIFriendly.IdentifiableName or "No Unit Name"
--self:I("Class Name: " .. classname)
--self:I("Unit Name: " .. unitname)
--self:I({AIFriendly})
end
local Friendly = nil
if AIFriendly and AIFriendly:IsAlive() then
--self:I("AIFriendly alive, getting GROUP")
Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP
end
if Friendly and Friendly:IsAlive() then
-- Ok, so we have a friendly near the potential target.
-- Now we need to check if the AIGroup has a Task.
@@ -3246,7 +3267,9 @@ do -- AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup )
end
AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling
end
end
@@ -3258,10 +3281,10 @@ do -- AI_A2A_DISPATCHER
self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To )
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3277,7 +3300,7 @@ do -- AI_A2A_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3465,10 +3488,10 @@ do -- AI_A2A_DISPATCHER
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup )
if DefenderTarget then
if Squadron.Language == "EN" then
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колёса вверх.", DefenderGroup )
end
--Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
@@ -3486,11 +3509,11 @@ do -- AI_A2A_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
if Squadron.Language == "EN" then
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "DE" then
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехватывая боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "DE" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
end
end
@@ -3508,10 +3531,10 @@ do -- AI_A2A_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
if Squadron.Language == "EN" then
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", задействуя боги в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup )
end
end
self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit )
@@ -3526,10 +3549,10 @@ do -- AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
if Squadron.Language == "EN" then
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращение на базу.", DefenderGroup )
end
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3557,10 +3580,10 @@ do -- AI_A2A_DISPATCHER
local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron.Language == "EN" then
if Squadron.Language == "EN" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup )
elseif Squadron.Language == "RU" then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup )
elseif Squadron.Language == "RU" and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", посадка на базу.", DefenderGroup )
end
if Action and Action == "Destroy" then
@@ -3953,6 +3976,30 @@ do
function AI_A2A_DISPATCHER:SchedulerCAP( SquadronName )
self:CAP( SquadronName )
end
--- Add resources to a Squadron
-- @param #AI_A2A_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to add.
function AI_A2A_DISPATCHER:AddToSquadron(Squadron,Amount)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount + Amount
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
--- Remove resources from a Squadron
-- @param #AI_A2A_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to remove.
function AI_A2A_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount - Amount
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
end
@@ -4529,5 +4576,5 @@ do
return self
end
end
@@ -999,7 +999,9 @@ do -- AI_A2G_DISPATCHER
-- self.Detection:InitDetectRadar( false )
-- self.Detection:InitDetectVisual( true )
-- self.Detection:SetRefreshTimeInterval( 30 )
self.SetSendPlayerMessages = false --flash messages to players
self:SetDefenseRadius()
self:SetDefenseLimit( nil )
self:SetDefenseApproach( AI_A2G_DISPATCHER.DefenseApproach.Random )
@@ -3699,7 +3701,9 @@ do -- AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
end
AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit
end
end
@@ -3711,7 +3715,7 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
if Squadron and self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup )
end
@@ -3730,8 +3734,9 @@ do -- AI_A2G_DISPATCHER
if Squadron and AttackSetUnit:Count() > 0 then
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3745,8 +3750,9 @@ do -- AI_A2G_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3757,8 +3763,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
end
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end
@@ -3770,7 +3777,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." )
end
if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3785,8 +3794,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
end
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3842,7 +3852,9 @@ do -- AI_A2G_DISPATCHER
self:F( { DefenderTarget = DefenderTarget } )
if DefenderTarget then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup )
end
AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
end
end
@@ -3857,8 +3869,9 @@ do -- AI_A2G_DISPATCHER
if Squadron then
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
end
@@ -3873,8 +3886,9 @@ do -- AI_A2G_DISPATCHER
local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup )
end
end
end
@@ -3884,8 +3898,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup )
end
self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
@@ -3899,8 +3914,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
--Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
end
if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3915,8 +3931,9 @@ do -- AI_A2G_DISPATCHER
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
if self.SetSendPlayerMessages then
Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup )
end
if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
DefenderGroup:Destroy()
@@ -3956,7 +3973,7 @@ do -- AI_A2G_DISPATCHER
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
-- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates.
-- (y1 y2)x + (x2 x1)y + (x1y2 x2y1) = 0
-- (y1 - y2)x + (x2 - x1)y + (x1y2 - x2y1) = 0
local c1 = DefenseCoordinate
local c2 = AttackCoordinate
@@ -4017,7 +4034,7 @@ do -- AI_A2G_DISPATCHER
for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do
-- Here we check if the defenders have a defense line to the attackers.
-- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage.
-- If the attackers are behind enemy lines or too close to an other defense line; then don't engage.
local DefenseCoordinate = DefenderGroup:GetCoordinate()
local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem )
@@ -4320,7 +4337,7 @@ do -- AI_A2G_DISPATCHER
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
@@ -4537,7 +4554,7 @@ do -- AI_A2G_DISPATCHER
if self.TacticalDisplay then
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
@@ -4706,6 +4723,35 @@ do
local PatrolTaskType = PatrolTaskTypes[math.random(1,3)]
self:Patrol( SquadronName, PatrolTaskType )
end
--- Set flashing player messages on or off
-- @param #AI_A2G_DISPATCHER self
-- @param #boolean onoff Set messages on (true) or off (false)
function AI_A2G_DISPATCHER:SetSendMessages( onoff )
self.SetSendPlayerMessages = onoff
end
end
--- Add resources to a Squadron
-- @param #AI_A2G_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to add.
function AI_A2G_DISPATCHER:AddToSquadron(Squadron,Amount)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount + Amount
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
--- Remove resources from a Squadron
-- @param #AI_A2G_DISPATCHER self
-- @param #string Squadron The squadron name.
-- @param #number Amount Number of resources to remove.
function AI_A2G_DISPATCHER:RemoveFromSquadron(Squadron,Amount)
local Squadron = self:GetSquadron(Squadron)
if Squadron.ResourceCount then
Squadron.ResourceCount = Squadron.ResourceCount - Amount
end
self:T({Squadron = Squadron.Name,SquadronResourceCount = Squadron.ResourceCount})
end
+4 -2
View File
@@ -585,11 +585,13 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
if AIGroup and AIGroup:IsAlive() then
self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:T( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
--AIGroup:ClearTasks()
AIGroup:OptionProhibitAfterburner(true)
local EngageRoute = {}
--- Calculate the target route point.
@@ -533,6 +533,10 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3()
if not TargetCoord then
self:Return()
return
end
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
+2 -2
View File
@@ -515,8 +515,8 @@ function AI_BAI_ZONE:onafterEngage( Controllable, From, Event, To,
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
+4 -2
View File
@@ -428,8 +428,10 @@ function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
if not CurrentVec2 then return self end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
+2 -2
View File
@@ -459,8 +459,8 @@ function AI_CAS_ZONE:onafterEngage( Controllable, From, Event, To,
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToEngageZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -174,8 +174,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZone
self:SetPickupSpeed( 350, 150 )
self:SetDeploySpeed( 350, 150 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:SetPickupRadius( 40, 12 )
self:SetDeployRadius( 40, 12 )
self:SetPickupHeight( 500, 200 )
self:SetDeployHeight( 500, 200 )
@@ -186,6 +186,9 @@ end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet )
return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
local dispatcher = AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
dispatcher:SetLandingSpeedAndHeight(27, 6)
return dispatcher
end
@@ -21,7 +21,7 @@
-- ===
--
-- @module AI.AI_Cargo_Dispatcher_Ship
-- @image AI_Cargo_Dispatching_For_Ship.JPG
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_DISPATCHER_SHIP
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
+1 -1
View File
@@ -7,7 +7,7 @@
-- ===
--
-- @module AI.AI_Cargo_Ship
-- @image AI_Cargo_Dispatching_For_Ship.JPG
-- @image AI_Cargo_Dispatcher.JPG
--- @type AI_CARGO_SHIP
-- @extends AI.AI_Cargo#AI_CARGO
+2 -2
View File
@@ -1140,8 +1140,8 @@ end
-- @param DCS#Vec3 CV2 Vec3
function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2)
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then
if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation and not self:Is("Stopped") then
self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )})
FollowGroup:OptionROTEvadeFire()
+12 -9
View File
@@ -725,8 +725,8 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
return
end
if self.Controllable:IsAlive() then
local life = self.Controllable:GetLife() or 0
if self.Controllable:IsAlive() and life > 1 then
-- Determine if the AIControllable is within the PatrolZone.
-- If not, make a waypoint within the to that the AIControllable will fly at maximum speed to that point.
@@ -743,8 +743,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
if self.Controllable:InAir() == false then
self:T( "Not in the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
if not CurrentVec2 then return end
--Done: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -758,8 +759,9 @@ function AI_PATROL_ZONE:onafterRoute( Controllable, From, Event, To )
else
self:T( "In the air, finding route path within PatrolZone" )
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
if not CurrentVec2 then return end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
@@ -870,9 +872,10 @@ function AI_PATROL_ZONE:onafterRTB()
--- Calculate the current route point.
local CurrentVec2 = self.Controllable:GetVec2()
--TODO: Create GetAltitude function for GROUP, and delete GetUnit(1).
local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
if not CurrentVec2 then return end
--DONE: Create GetAltitude function for GROUP, and delete GetUnit(1).
--local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude()
local CurrentAltitude = self.Controllable:GetAltitude()
local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y )
local ToPatrolZoneSpeed = self.PatrolMaxSpeed
local CurrentRoutePoint = CurrentPointVec3:WaypointAir(
+943
View File
@@ -0,0 +1,943 @@
--- **Core** - A* Pathfinding.
--
-- **Main Features:**
--
-- * Find path from A to B.
-- * Pre-defined as well as custom valid neighbour functions.
-- * Pre-defined as well as custom cost functions.
-- * Easy rectangular grid setup.
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Astar
-- @image CORE_Astar.png
--- ASTAR class.
-- @type ASTAR
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table nodes Table of nodes.
-- @field #number counter Node counter.
-- @field #number Nnodes Number of nodes.
-- @field #number nvalid Number of nvalid calls.
-- @field #number nvalidcache Number of cached valid evals.
-- @field #number ncost Number of cost evaluations.
-- @field #number ncostcache Number of cached cost evals.
-- @field #ASTAR.Node startNode Start node.
-- @field #ASTAR.Node endNode End node.
-- @field Core.Point#COORDINATE startCoord Start coordinate.
-- @field Core.Point#COORDINATE endCoord End coordinate.
-- @field #function ValidNeighbourFunc Function to check if a node is valid.
-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function.
-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another.
-- @field #table CostArg Optional arguments passed to the cost function.
-- @extends Core.Base#BASE
--- *When nothing goes right... Go left!*
--
-- ===
--
-- # The ASTAR Concept
--
-- Pathfinding algorithm.
--
--
-- # Start and Goal
--
-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually.
--
-- ## Start
--
-- The start
--
-- ## Goal
--
--
-- # Nodes
--
-- ## Rectangular Grid
--
-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where
--
-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid.
-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km).
-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km).
-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km).
-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*.
-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created.
--
-- ## Valid Surfaces
--
-- Certain unit types can only travel on certain surfaces types, for example
--
-- * Naval units can only travel on water (that also excludes shallow water in DCS currently),
-- * Ground units can only traval on land.
--
-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient.
--
-- ## Box Width (BoxHY)
--
-- The box width needs to be large enough to capture all paths you want to consider.
--
-- ## Space in X
--
-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node.
--
-- ## Grid Spacing
--
-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible.
-- However, if the value is too large, the algorithm might fail to get a valid path.
--
-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path.
--
-- # Valid Neighbours
--
-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible.
--
-- ## Line of Sight
--
-- For naval
--
--
-- # Heuristic Cost
--
-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another.
-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes.
--
--
-- # Calculate the Path
--
-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which
-- describe the optimal path from the start node to the end node.
--
-- By default, the start and end node are include in the table that is returned.
--
-- Note that a valid path must not always exist. So you should check if the function returns *nil*.
--
-- Common reasons that a path cannot be found are:
--
-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid.
-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY*
-- * There simply is no valid path ==> you are screwed :(
--
--
-- # Examples
--
-- ## Strait of Hormuz
--
-- Carrier Group finds its way through the Stait of Hormuz.
--
-- ##
--
--
--
-- @field #ASTAR
ASTAR = {
ClassName = "ASTAR",
Debug = nil,
lid = nil,
nodes = {},
counter = 1,
Nnodes = 0,
ncost = 0,
ncostcache = 0,
nvalid = 0,
nvalidcache = 0,
}
--- Node data.
-- @type ASTAR.Node
-- @field #number id Node id.
-- @field Core.Point#COORDINATE coordinate Coordinate of the node.
-- @field #number surfacetype Surface type.
-- @field #table valid Cached valid/invalid nodes.
-- @field #table cost Cached cost.
--- ASTAR infinity.
-- @field #number INF
ASTAR.INF=1/0
--- ASTAR class version.
-- @field #string version
ASTAR.version="0.4.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add more valid neighbour functions.
-- TODO: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ASTAR object.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:New()
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, BASE:New()) --#ASTAR
self.lid="ASTAR | "
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set coordinate from where to start.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate Start coordinate.
-- @return #ASTAR self
function ASTAR:SetStartCoordinate(Coordinate)
self.startCoord=Coordinate
return self
end
--- Set coordinate where you want to go.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate end coordinate.
-- @return #ASTAR self
function ASTAR:SetEndCoordinate(Coordinate)
self.endCoord=Coordinate
return self
end
--- Create a node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node.
-- @return #ASTAR.Node The node.
function ASTAR:GetNodeFromCoordinate(Coordinate)
local node={} --#ASTAR.Node
node.coordinate=Coordinate
node.surfacetype=Coordinate:GetSurfaceType()
node.id=self.counter
node.valid={}
node.cost={}
self.counter=self.counter+1
return node
end
--- Add a node to the table of grid nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @return #ASTAR self
function ASTAR:AddNode(Node)
self.nodes[Node.id]=Node
self.Nnodes=self.Nnodes+1
return self
end
--- Add a node to the table of grid nodes specifying its coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created.
-- @return #ASTAR.Node The node.
function ASTAR:AddNodeFromCoordinate(Coordinate)
local node=self:GetNodeFromCoordinate(Coordinate)
self:AddNode(node)
return node
end
--- Check if the coordinate of a node has is at a valid surface type.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added.
-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid.
-- @return #boolean If true, surface type of node is valid.
function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes)
if SurfaceTypes then
if type(SurfaceTypes)~="table" then
SurfaceTypes={SurfaceTypes}
end
for _,surface in pairs(SurfaceTypes) do
if surface==Node.surfacetype then
return true
end
end
return false
else
return true
end
end
--- Add a function to determine if a neighbour of a node is valid.
-- @param #ASTAR self
-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid.
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...)
self.ValidNeighbourFunc=NeighbourFunction
self.ValidNeighbourArg={}
if arg then
self.ValidNeighbourArg=arg
end
return self
end
--- Set valid neighbours to require line of sight between two nodes.
-- @param #ASTAR self
-- @param #number CorridorWidth Width of LoS corridor in meters.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourLoS(CorridorWidth)
self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourDistance(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance)
return self
end
--- Set valid neighbours to be in a certain distance.
-- @param #ASTAR self
-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m.
-- @return #ASTAR self
function ASTAR:SetValidNeighbourRoad(MaxDistance)
self:SetValidNeighbourFunction(ASTAR.Road, MaxDistance)
return self
end
--- Set the function which calculates the "cost" to go from one to another node.
-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments.
-- Very often the distance between nodes is a good measure for the cost.
-- @param #ASTAR self
-- @param #function CostFunction Function that returns the "cost".
-- @param ... Condition function arguments if any.
-- @return #ASTAR self
function ASTAR:SetCostFunction(CostFunction, ...)
self.CostFunc=CostFunction
self.CostArg={}
if arg then
self.CostArg=arg
end
return self
end
--- Set heuristic cost to go from one node to another to be their 2D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist2D()
self:SetCostFunction(ASTAR.Dist2D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostDist3D()
self:SetCostFunction(ASTAR.Dist3D)
return self
end
--- Set heuristic cost to go from one node to another to be their 3D distance.
-- @param #ASTAR self
-- @return #ASTAR self
function ASTAR:SetCostRoad()
self:SetCostFunction(ASTAR)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Grid functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a rectangular grid of nodes between star and end coordinate.
-- The coordinate system is oriented along the line between start and end point.
-- @param #ASTAR self
-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed.
-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km).
-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km).
-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters.
-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX.
-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes.
-- @return #ASTAR self
function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid)
-- Note that internally
-- x coordinate is z: x-->z Line from start to end
-- y coordinate is x: y-->x Perpendicular
-- Grid length and width.
local Dz=SpaceX or 10000
local Dx=BoxHY and BoxHY/2 or 20000
-- Increments.
local dz=deltaX or 2000
local dx=deltaY or dz
-- Heading from start to end coordinate.
local angle=self.startCoord:HeadingTo(self.endCoord)
--Distance between start and end.
local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz
-- Origin of map. Needed to translate back to wanted position.
local co=COORDINATE:New(0, 0, 0)
local do1=co:Get2DDistance(self.startCoord)
local ho1=co:HeadingTo(self.startCoord)
-- Start of grid.
local xmin=-Dx
local zmin=-Dz
-- Number of grid points.
local nz=dist/dz+1
local nx=2*Dx/dx+1
-- Debug info.
local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz)
self:T(self.lid..text)
-- Loop over x and z coordinate to create a 2D grid.
for i=1,nx do
-- x coordinate perpendicular to z.
local x=xmin+dx*(i-1)
for j=1,nz do
-- z coordinate connecting start and end.
local z=zmin+dz*(j-1)
-- Rotate 2D.
local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle)
-- Coordinate of the node.
local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true)
-- Create a node at this coordinate.
local node=self:GetNodeFromCoordinate(c)
-- Check if node has valid surface type.
if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then
if MarkGrid then
c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype))
end
-- Add node to grid.
self:AddNode(node)
end
end
end
-- Debug info.
local text=string.format("Done building grid!")
self:T2(self.lid..text)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Valid neighbour functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function to check if two nodes have line of sight (LoS).
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number corridor (Optional) Width of corridor in meters.
-- @return #boolean If true, two nodes have LoS.
function ASTAR.LoS(nodeA, nodeB, corridor)
local offset=1
local dx=corridor and corridor/2 or nil
local dy=dx
local cA=nodeA.coordinate:GetVec3()
local cB=nodeB.coordinate:GetVec3()
cA.y=offset
cB.y=offset
local los=land.isVisible(cA, cB)
if los and corridor then
-- Heading from A to B.
local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate)
local Ap=UTILS.VecTranslate(cA, dx, heading+90)
local Bp=UTILS.VecTranslate(cB, dx, heading+90)
los=land.isVisible(Ap, Bp)
if los then
local Am=UTILS.VecTranslate(cA, dx, heading-90)
local Bm=UTILS.VecTranslate(cB, dx, heading-90)
los=land.isVisible(Am, Bm)
end
end
return los
end
--- Function to check if two nodes have a road connection.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #boolean If true, two nodes are connected via a road.
function ASTAR.Road(nodeA, nodeB)
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
return true
else
return false
end
end
--- Function to check if distance between two nodes is less than a threshold distance.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @param #number distmax Max distance in meters. Default is 2000 m.
-- @return #boolean If true, distance between the two nodes is below threshold.
function ASTAR.DistMax(nodeA, nodeB, distmax)
distmax=distmax or 2000
local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate)
return dist<=distmax
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Heuristic cost functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic cost is given by the 2D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist2D(nodeA, nodeB)
local dist=nodeA.coordinate:Get2DDistance(nodeB)
return dist
end
--- Heuristic cost is given by the 3D distance between the nodes.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.Dist3D(nodeA, nodeB)
local dist=nodeA.coordinate:Get3DDistance(nodeB.coordinate)
return dist
end
--- Heuristic cost is given by the distance between the nodes on road.
-- @param #ASTAR.Node nodeA First node.
-- @param #ASTAR.Node nodeB Other node.
-- @return #number Distance between the two nodes.
function ASTAR.DistRoad(nodeA, nodeB)
-- Get the path.
local path=land.findPathOnRoads("roads", nodeA.coordinate.x, nodeA.coordinate.z, nodeB.coordinate.x, nodeB.coordinate.z)
if path then
local dist=0
for i=2,#path do
local b=path[i] --DCS#Vec2
local a=path[i-1] --DCS#Vec2
dist=dist+UTILS.VecDist2D(a,b)
end
return dist
end
return math.huge
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Find the closest node from a given coordinate.
-- @param #ASTAR self
-- @param Core.Point#COORDINATE Coordinate.
-- @return #ASTAR.Node Cloest node to the coordinate.
-- @return #number Distance to closest node in meters.
function ASTAR:FindClosestNode(Coordinate)
local distMin=math.huge
local closeNode=nil
for _,_node in pairs(self.nodes) do
local node=_node --#ASTAR.Node
local dist=node.coordinate:Get2DDistance(Coordinate)
if dist<distMin then
distMin=dist
closeNode=node
end
end
return closeNode, distMin
end
--- Find the start node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindStartNode()
local node, dist=self:FindClosestNode(self.startCoord)
self.startNode=node
if dist>1000 then
self:T(self.lid.."Adding start node to node grid!")
self:AddNode(node)
end
return self
end
--- Add a node.
-- @param #ASTAR self
-- @param #ASTAR.Node Node The node to be added to the nodes table.
-- @return #ASTAR self
function ASTAR:FindEndNode()
local node, dist=self:FindClosestNode(self.endCoord)
self.endNode=node
if dist>1000 then
self:T(self.lid.."Adding end node to node grid!")
self:AddNode(node)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Main A* pathfinding function
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates.
-- @param #ASTAR self
-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it.
-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it.
-- @return #table Table of nodes from start to finish.
function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode)
self:FindStartNode()
self:FindEndNode()
local nodes=self.nodes
local start=self.startNode
local goal=self.endNode
-- Sets.
local openset = {}
local closedset = {}
local came_from = {}
local g_score = {}
local f_score = {}
openset[start.id]=true
local Nopen=1
-- Initial scores.
g_score[start.id]=0
f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal)
-- Set start time.
local T0=timer.getAbsTime()
-- Debug message.
local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes)
self:T(self.lid..text)
local Tstart=UTILS.GetOSTime()
-- Loop while we still have an open set.
while Nopen > 0 do
-- Get current node.
local current=self:_LowestFscore(openset, f_score)
-- Check if we are at the end node.
if current.id==goal.id then
local path=self:_UnwindPath({}, came_from, goal)
if not ExcludeEndNode then
table.insert(path, goal)
end
if ExcludeStartNode then
table.remove(path, 1)
end
local Tstop=UTILS.GetOSTime()
local dT=nil
if Tstart and Tstop then
dT=Tstop-Tstart
end
-- Debug message.
local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes)
if dT then
text=text..string.format(", OS Time %.6f sec", dT)
end
text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache)
text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache)
self:T(self.lid..text)
return path
end
-- Move Node from open to closed set.
openset[current.id]=nil
Nopen=Nopen-1
closedset[current.id]=true
-- Get neighbour nodes.
local neighbors=self:_NeighbourNodes(current, nodes)
-- Loop over neighbours.
for _,neighbor in pairs(neighbors) do
if self:_NotIn(closedset, neighbor.id) then
local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor)
if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then
came_from[neighbor]=current
g_score[neighbor.id]=tentative_g_score
f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal)
if self:_NotIn(openset, neighbor.id) then
-- Add to open set.
openset[neighbor.id]=true
Nopen=Nopen+1
end
end
end
end
end
-- Debug message.
local text=string.format("WARNING: Could NOT find valid path!")
self:E(self.lid..text)
MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug)
return nil -- no valid path
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- A* pathfinding helper functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number "Cost" to go from node A to node B.
function ASTAR:_HeuristicCost(nodeA, nodeB)
-- Counter.
self.ncost=self.ncost+1
-- Get chached cost if available.
local cost=nodeA.cost[nodeB.id]
if cost~=nil then
self.ncostcache=self.ncostcache+1
return cost
end
local cost=nil
if self.CostFunc then
cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg))
else
cost=self:_DistNodes(nodeA, nodeB)
end
nodeA.cost[nodeB.id]=cost
nodeB.cost[nodeA.id]=cost -- Symmetric problem.
return cost
end
--- Check if going from a node to a neighbour is possible.
-- @param #ASTAR self
-- @param #ASTAR.Node node A node.
-- @param #ASTAR.Node neighbor Neighbour node.
-- @return #boolean If true, transition between nodes is possible.
function ASTAR:_IsValidNeighbour(node, neighbor)
-- Counter.
self.nvalid=self.nvalid+1
local valid=node.valid[neighbor.id]
if valid~=nil then
--env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id))
self.nvalidcache=self.nvalidcache+1
return valid
end
local valid=nil
if self.ValidNeighbourFunc then
valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg))
else
valid=true
end
node.valid[neighbor.id]=valid
neighbor.valid[node.id]=valid -- Symmetric problem.
return valid
end
--- Calculate 2D distance between two nodes.
-- @param #ASTAR self
-- @param #ASTAR.Node nodeA Node A.
-- @param #ASTAR.Node nodeB Node B.
-- @return #number Distance between nodes in meters.
function ASTAR:_DistNodes(nodeA, nodeB)
return nodeA.coordinate:Get2DDistance(nodeB.coordinate)
end
--- Function that calculates the lowest F score.
-- @param #ASTAR self
-- @param #table set The set of nodes IDs.
-- @param #number f_score F score.
-- @return #ASTAR.Node Best node.
function ASTAR:_LowestFscore(set, f_score)
local lowest, bestNode = ASTAR.INF, nil
for nid,node in pairs(set) do
local score=f_score[nid]
if score<lowest then
lowest, bestNode = score, nid
end
end
return self.nodes[bestNode]
end
--- Function to get valid neighbours of a node.
-- @param #ASTAR self
-- @param #ASTAR.Node theNode The node.
-- @param #table nodes Possible neighbours.
-- @param #table Valid neighbour nodes.
function ASTAR:_NeighbourNodes(theNode, nodes)
local neighbors = {}
for _,node in pairs(nodes) do
if theNode.id~=node.id then
local isvalid=self:_IsValidNeighbour(theNode, node)
if isvalid then
table.insert(neighbors, node)
end
end
end
return neighbors
end
--- Function to check if a node is not in a set.
-- @param #ASTAR self
-- @param #table set Set of nodes.
-- @param #ASTAR.Node theNode The node to check.
-- @return #boolean If true, the node is not in the set.
function ASTAR:_NotIn(set, theNode)
return set[theNode]==nil
end
--- Unwind path function.
-- @param #ASTAR self
-- @param #table flat_path Flat path.
-- @param #table map Map.
-- @param #ASTAR.Node current_node The current node.
-- @return #table Unwinded path.
function ASTAR:_UnwindPath( flat_path, map, current_node )
if map [current_node] then
table.insert (flat_path, 1, map[current_node])
return self:_UnwindPath(flat_path, map, map[current_node])
else
return flat_path
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+85 -24
View File
@@ -234,7 +234,8 @@ FORMATION = {
-- @param #BASE self
-- @return #BASE
function BASE:New()
local self = routines.utils.deepCopy( self ) -- Create a new self instance
--local self = routines.utils.deepCopy( self ) -- Create a new self instance
local self = UTILS.DeepCopy(self)
_ClassID = _ClassID + 1
self.ClassID = _ClassID
@@ -472,14 +473,16 @@ do -- Event Handling
return self
end
-- Event handling function prototypes
-- Event handling function prototypes - Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--- Occurs whenever any unit in a mission fires a weapon. But not any machine gun or autocannon based weapon, those are handled by EVENT.ShootingStart.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventShot
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs whenever an object is hit by a weapon.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit object the fired the weapon
-- weapon: Weapon object that hit the target
-- target: The Object that was hit.
@@ -488,6 +491,7 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft takes off from an airbase, farp, or ship.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that tookoff
-- place: Object from where the AI took-off from. Can be an Airbase Object, FARP, or Ships
-- @function [parent=#BASE] OnEventTakeoff
@@ -495,6 +499,7 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft lands at an airbase, farp or ship
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has landed
-- place: Object that the unit landed on. Can be an Airbase Object, FARP, or Ships
-- @function [parent=#BASE] OnEventLand
@@ -502,101 +507,118 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft crashes into the ground and is completely destroyed.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has crashed
-- @function [parent=#BASE] OnEventCrash
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a pilot ejects from an aircraft
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that has ejected
-- @function [parent=#BASE] OnEventEjection
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft connects with a tanker and begins taking on fuel.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is receiving fuel.
-- @function [parent=#BASE] OnEventRefueling
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an object is dead.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is dead.
-- @function [parent=#BASE] OnEventDead
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an object is completely destroyed.
-- initiator : The unit that is was destroyed.
--- Occurs when an Event for an object is triggered.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that triggered the event.
-- @function [parent=#BASE] OnEvent
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when the pilot of an aircraft is killed. Can occur either if the player is alive and crashes or if a weapon kills the pilot without completely destroying the plane.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that the pilot has died in.
-- @function [parent=#BASE] OnEventPilotDead
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a ground unit captures either an airbase or a farp.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that captured the base
-- place: The airbase that was captured, can be a FARP or Airbase. When calling place:getCoalition() the faction will already be the new owning faction.
-- @function [parent=#BASE] OnEventBaseCaptured
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mission starts
--- Occurs when a mission starts
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventMissionStart
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mission ends
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventMissionEnd
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when an aircraft is finished taking fuel.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was receiving fuel.
-- @function [parent=#BASE] OnEventRefuelingStop
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any object is spawned into the mission.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was spawned
-- @function [parent=#BASE] OnEventBirth
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any system fails on a human controlled aircraft.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that had the failure
-- @function [parent=#BASE] OnEventHumanFailure
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft starts its engines.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is starting its engines.
-- @function [parent=#BASE] OnEventEngineStartup
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any aircraft shuts down its engines.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is stopping its engines.
-- @function [parent=#BASE] OnEventEngineShutdown
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any player assumes direct control of a unit.
--- Occurs when any player assumes direct control of a unit. Note - not Mulitplayer safe. Use PlayerEnterAircraft.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is being taken control of.
-- @function [parent=#BASE] OnEventPlayerEnterUnit
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any player relieves control of a unit to the AI.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that the player left.
-- @function [parent=#BASE] OnEventPlayerLeaveUnit
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit begins firing a weapon that has a high rate of fire. Most common with aircraft cannons (GAU-8), autocannons, and machine guns.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that is doing the shooting.
-- target: The unit that is being targeted.
-- @function [parent=#BASE] OnEventShootingStart
@@ -604,24 +626,28 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when any unit stops firing its weapon. Event will always correspond with a shooting start event.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- initiator : The unit that was doing the shooting.
-- @function [parent=#BASE] OnEventShootingEnd
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a new mark was added.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkAdded
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark was removed.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkRemoved
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a mark text was changed.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- MarkID: ID of the mark.
-- @function [parent=#BASE] OnEventMarkChange
-- @param #BASE self
@@ -638,11 +664,13 @@ do -- Event Handling
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs on the death of a unit. Contains more and different information. Similar to unit_lost it will occur for aircraft before the aircraft crash event occurs.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: The unit that killed the target
-- * target: Target Object
@@ -654,11 +682,13 @@ do -- Event Handling
--- Occurs when any modification to the "Score" as seen on the debrief menu would occur.
-- There is no information on what values the score was changed to. Event is likely similar to player_comment in this regard.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventScore
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when the game thinks an object is destroyed.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: The unit that is was destroyed.
--
@@ -667,6 +697,7 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs shortly after the landing animation of an ejected pilot touching the ground and standing up. Event does not occur if the pilot lands in the water and sub combs to Davey Jones Locker.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
--
-- * initiator: Static object representing the ejected pilot. Place : Aircraft that the pilot ejected from.
-- * place: may not return as a valid object if the aircraft has crashed into the ground and no longer exists.
@@ -677,37 +708,44 @@ do -- Event Handling
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Paratrooper landing.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventParatrooperLanding
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Discard chair after ejection.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventDiscardChairAfterEjection
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Weapon add. Fires when entering a mission per pylon with the name of the weapon (double pylons not counted, infinite wep reload not counted.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventParatrooperLanding
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Trigger zone.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventTriggerZone
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Landing quality mark.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventLandingQualityMark
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- BDA.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- @function [parent=#BASE] OnEventBDA
-- @param #BASE self
-- @param Core.Event#EVENTDATA EventData The EventData structure.
--- Occurs when a player enters a slot and takes control of an aircraft.
-- Have a look at the class @{Core.EVENT#EVENT} as these are just the prototypes.
-- **NOTE**: This is a workaround of a long standing DCS bug with the PLAYER_ENTER_UNIT event.
-- initiator : The unit that is being taken control of.
-- @function [parent=#BASE] OnEventPlayerEnterAircraft
@@ -755,17 +793,34 @@ function BASE:CreateEventCrash( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a Crash Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventUnitLost(EventTime, Initiator)
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_UNIT_LOST,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
--- Creation of a Dead Event.
-- @param #BASE self
-- @param DCS#Time EventTime The time stamp of the event.
-- @param DCS#Object Initiator The initiating object of the event.
function BASE:CreateEventDead( EventTime, Initiator )
self:F( { EventTime, Initiator } )
function BASE:CreateEventDead( EventTime, Initiator, IniObjectCategory )
self:F( { EventTime, Initiator, IniObjectCategory } )
local Event = {
id = world.event.S_EVENT_DEAD,
time = EventTime,
initiator = Initiator,
IniObjectCategory = IniObjectCategory,
}
world.onEvent( Event )
@@ -817,8 +872,7 @@ end
world.onEvent(Event)
end
-- TODO: Complete DCS#Event structure.
--- The main event handling function... This function captures all events generated for the class.
-- @param #BASE self
-- @param DCS#Event event
@@ -857,20 +911,22 @@ do -- Scheduling
-- @param #number Start Specifies the amount of seconds that will be waited before the scheduling is started, and the event function is called.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function BASE:ScheduleOnce( Start, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
-- Object name.
local ObjectName = "-"
ObjectName = self.ClassName .. self.ClassID
-- Debug info.
self:F3( { "ScheduleOnce: ", ObjectName, Start } )
if not self.Scheduler then
self.Scheduler = SCHEDULER:New( self )
end
-- FF this was wrong!
--[[
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule(
self,
SchedulerFunction,
@@ -880,6 +936,10 @@ do -- Scheduling
nil,
nil
)
]]
-- NOTE: MasterObject (first parameter) needs to be nil or it will be the first argument passed to the SchedulerFunction!
local ScheduleID = self.Scheduler:Schedule(nil, SchedulerFunction, {...}, Start)
self._.Schedules[#self._.Schedules+1] = ScheduleID
@@ -894,7 +954,7 @@ do -- Scheduling
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
-- @param #table ... Optional arguments that can be given as part of scheduler. The arguments need to be given as a table { param1, param 2, ... }.
-- @return #number The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function BASE:ScheduleRepeat( Start, Repeat, RandomizeFactor, Stop, SchedulerFunction, ... )
self:F2( { Start } )
self:T3( { ... } )
@@ -908,8 +968,9 @@ do -- Scheduling
self.Scheduler = SCHEDULER:New( self )
end
local ScheduleID = self.Scheduler:Schedule(
self,
-- NOTE: MasterObject (first parameter) should(!) be nil as it will be the first argument passed to the SchedulerFunction!
local ScheduleID = self.Scheduler:Schedule(
nil,
SchedulerFunction,
{ ... },
Start,
@@ -926,13 +987,13 @@ do -- Scheduling
--- Stops the Schedule.
-- @param #BASE self
-- @param #function SchedulerFunction The event function to be called when a timer event occurs. The event function needs to accept the parameters specified in SchedulerArguments.
function BASE:ScheduleStop( SchedulerFunction )
-- @param #string SchedulerID (Optional) Scheduler ID to be stopped. If nil, all pending schedules are stopped.
function BASE:ScheduleStop( SchedulerID )
self:F3( { "ScheduleStop:" } )
if self.Scheduler then
_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
--_SCHEDULEDISPATCHER:Stop( self.Scheduler, self._.Schedules[SchedulerFunction] )
_SCHEDULEDISPATCHER:Stop(self.Scheduler, SchedulerID)
end
end
@@ -1076,7 +1137,7 @@ end
--- Set tracing for a class
-- @param #BASE self
-- @param #string Class
-- @param #string Class Class name.
function BASE:TraceClass( Class )
_TraceClass[Class] = true
_TraceClassMethod[Class] = {}
@@ -1085,8 +1146,8 @@ end
--- Set tracing for a specific method of class
-- @param #BASE self
-- @param #string Class
-- @param #string Method
-- @param #string Class Class name.
-- @param #string Method Method.
function BASE:TraceClassMethod( Class, Method )
if not _TraceClassMethod[Class] then
_TraceClassMethod[Class] = {}
+16 -7
View File
@@ -170,6 +170,8 @@ end
function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration})
Mode=Mode or "Y"
-- Get frequency.
local Frequency=UTILS.TACANToFrequency(Channel, Mode)
@@ -187,11 +189,16 @@ function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration)
-- Check if unit is an aircraft and set system accordingly.
local AA=self.Positionable:IsAir()
if AA then
System=5 --NOTE: 5 is how you cat the correct tanker behaviour! --BEACON.System.TACAN_TANKER
-- Check if "Y" mode is selected for aircraft.
if Mode~="Y" then
self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y !The BEACON is not emitting.", self.Positionable})
if Mode=="X" then
--self:E({"WARNING: The POSITIONABLE you want to attach the AA Tacan Beacon is an aircraft: Mode should Y!", self.Positionable})
System=BEACON.System.TACAN_TANKER_X
else
System=BEACON.System.TACAN_TANKER_Y
end
end
@@ -267,13 +274,12 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
IsValid = false
end
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing
-- or 14 (TACAN_AA_MODE_Y) if it does not
-- I'm using the beacon type 4 (BEACON_TYPE_TACAN). For System, I'm using 5 (TACAN_TANKER_MODE_Y) if the bearing shows its bearing or 14 (TACAN_AA_MODE_Y) if it does not
local System
if Bearing then
System = 5
System = BEACON.System.TACAN_TANKER_Y
else
System = 14
System = BEACON.System.TACAN_AA_MODE_Y
end
if IsValid then -- Starts the BEACON
@@ -281,10 +287,13 @@ function BEACON:AATACAN(TACANChannel, Message, Bearing, BeaconDuration)
self.Positionable:SetCommand({
id = "ActivateBeacon",
params = {
type = 4,
type = BEACON.Type.TACAN,
system = System,
callsign = Message,
AA = true,
frequency = Frequency,
bearing = Bearing,
modeChannel = "Y",
}
})
+168 -114
View File
@@ -124,6 +124,7 @@ function DATABASE:New()
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
--self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM
self:HandleEvent( EVENTS.Hit, self.AccountHits )
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
@@ -136,8 +137,8 @@ function DATABASE:New()
self:_RegisterGroupsAndUnits()
self:_RegisterClients()
self:_RegisterStatics()
--self:_RegisterAirbases()
--self:_RegisterPlayers()
--self:_RegisterAirbases()
self.UNITS_Position = 0
@@ -162,17 +163,11 @@ end
function DATABASE:AddUnit( DCSUnitName )
if not self.UNITS[DCSUnitName] then
-- Debug info.
self:T( { "Add UNIT:", DCSUnitName } )
--local UnitRegister = UNIT:Register( DCSUnitName )
-- Register unit
self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName)
-- This is not used anywhere in MOOSE as far as I can see so I remove it until there comes an error somewhere.
--table.insert(self.UNITS_Index, DCSUnitName )
end
return self.UNITS[DCSUnitName]
@@ -182,7 +177,6 @@ end
--- Deletes a Unit from the DATABASE based on the Unit Name.
-- @param #DATABASE self
function DATABASE:DeleteUnit( DCSUnitName )
self.UNITS[DCSUnitName] = nil
end
@@ -234,7 +228,15 @@ end
function DATABASE:AddAirbase( AirbaseName )
if not self.AIRBASES[AirbaseName] then
self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName )
-- Register airbase.
local airbase = AIRBASE:Register( AirbaseName ) --Wrapper.Airbase#AIRBASE
-- Add to airbase.
self.AIRBASES[AirbaseName]=airbase
-- Debug info
self:I(string.format("Adding airbase %s (ID=%d): %s with %d parking spots", tostring(airbase.AirbaseName), airbase.AirbaseID, airbase.AirbaseCategoryName, tonumber(#airbase.parking)))
end
return self.AIRBASES[AirbaseName]
@@ -300,23 +302,23 @@ do -- Zones
for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do
local ZoneName = ZoneData.name
-- Color
local color=ZoneData.color or {1, 0, 0, 0.15}
-- Create new Zone
local Zone=nil --Core.Zone#ZONE_BASE
if ZoneData.type==0 then
---
-- Circular zone
---
self:I(string.format("Register ZONE: %s (Circular)", ZoneName))
Zone=ZONE:New(ZoneName)
else
---
@@ -324,51 +326,54 @@ do -- Zones
---
self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName))
Zone=ZONE_POLYGON_BASE:New(ZoneName, ZoneData.verticies)
--for i,vec2 in pairs(ZoneData.verticies) do
-- local coord=COORDINATE:NewFromVec2(vec2)
-- coord:MarkToAll(string.format("%s Point %d", ZoneName, i))
--end
end
if Zone then
-- Store color of zone.
-- Store color of zone.
Zone.Color=color
-- Store zone ID.
Zone.ZoneID=ZoneData.zoneId
-- Store in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone.
self:AddZone(ZoneName, Zone)
end
end
-- Polygon zones defined by late activated groups.
for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do
if ZoneGroupName:match("#ZONE_POLYGON") then
local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON")
local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)")
local ZoneName = ZoneName1 .. ( ZoneName2 or "" )
-- Debug output
self:I(string.format("Register ZONE: %s (Polygon)", ZoneName))
-- Create a new polygon zone.
local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup )
-- Set color.
Zone_Polygon:SetColor({1, 0, 0}, 0.15)
-- Store name in DB.
self.ZONENAMES[ZoneName] = ZoneName
-- Add zone to DB.
self:AddZone( ZoneName, Zone_Polygon )
end
@@ -778,7 +783,9 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
local StaticTemplate = UTILS.DeepCopy( StaticTemplate )
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
local StaticTemplateGroupName = env.getValueDictByKey(StaticTemplate.name)
local StaticTemplateName=StaticTemplate.units[1].name
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
@@ -786,7 +793,7 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
StaticTemplate.CoalitionID = CoalitionID
StaticTemplate.CountryID = CountryID
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName
self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateGroupName
self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate
self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1]
self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID
@@ -830,7 +837,7 @@ function DATABASE:GetStaticUnitTemplate( StaticName )
return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
else
self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName))
return nil
return nil
end
end
@@ -856,10 +863,24 @@ function DATABASE:GetGroupTemplateFromUnitName( UnitName )
return self.Templates.Units[UnitName].GroupTemplate
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
return nil
end
end
--- Get group template from unit name.
-- @param #DATABASE self
-- @param #string UnitName Name of the unit.
-- @return #table Group template.
function DATABASE:GetUnitTemplateFromUnitName( UnitName )
if self.Templates.Units[UnitName] then
return self.Templates.Units[UnitName]
else
self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName))
return nil
end
end
--- Get coalition ID from client name.
-- @param #DATABASE self
-- @param #string ClientName Name of the Client.
@@ -934,13 +955,13 @@ end
function DATABASE:_RegisterGroupsAndUnits()
local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) }
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
for DCSGroupId, DCSGroup in pairs( CoalitionData ) do
if DCSGroup:isExist() then
-- Group name.
local DCSGroupName = DCSGroup:getName()
@@ -953,11 +974,11 @@ function DATABASE:_RegisterGroupsAndUnits()
-- Get unit name.
local DCSUnitName = DCSUnit:getName()
-- Add unit.
self:I(string.format("Register Unit: %s", tostring(DCSUnitName)))
self:AddUnit( DCSUnitName )
end
else
self:E({"Group does not exist: ", DCSGroup})
@@ -1010,7 +1031,7 @@ end
function DATABASE:_RegisterAirbases()
for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do
-- Get the airbase name.
local DCSAirbaseName = DCSAirbase:getName()
@@ -1019,9 +1040,9 @@ function DATABASE:_RegisterAirbases()
-- Add and register airbase.
local airbase=self:AddAirbase( DCSAirbaseName )
-- Unique ID.
local airbaseUID=airbase:GetID(true)
local airbaseUID=airbase:GetID(true)
-- Debug output.
local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal)
@@ -1030,14 +1051,14 @@ function DATABASE:_RegisterAirbases()
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])
end
end
text=text.."]"
text=text.."]"
self:I(text)
-- Check for DCS bug IDs.
if airbaseID~=airbase:GetID() then
--self:E("WARNING: :getID does NOT match :GetID!")
end
end
end
return self
@@ -1053,74 +1074,74 @@ function DATABASE:_EventOnBirth( Event )
self:F( { Event } )
if Event.IniDCSUnit then
if Event.IniObjectCategory == 3 then
self:AddStatic( Event.IniDCSUnitName )
else
if Event.IniObjectCategory == 1 then
self:AddUnit( Event.IniDCSUnitName )
self:AddGroup( Event.IniDCSGroupName )
-- Add airbase if it was spawned later in the mission.
local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName)
if DCSAirbase then
self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName)))
self:AddAirbase(Event.IniDCSUnitName)
end
end
end
if Event.IniObjectCategory == 1 then
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
-- Client
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
if client then
-- TODO: create event ClientAlive
end
-- Get player name.
end
-- Get player name.
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName then
-- Debug info.
self:I(string.format("Player '%s' joint unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName)))
-- Add client in case it does not exist already.
if not client then
client=self:AddClient(Event.IniDCSUnitName)
end
-- Add player.
client:AddPlayer(PlayerName)
-- Add player.
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniUnitName, PlayerName )
end
-- Player settings.
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu(Event.IniUnit)
-- Create an event.
self:CreateEventPlayerEnterAircraft(Event.IniUnit)
end
end
end
end
@@ -1130,48 +1151,64 @@ end
function DATABASE:_EventOnDeadOrCrash( Event )
if Event.IniDCSUnit then
local name=Event.IniDCSUnitName
if Event.IniObjectCategory == 3 then
---
-- STATICS
---
if self.STATICS[Event.IniDCSUnitName] then
self:DeleteStatic( Event.IniDCSUnitName )
end
---
-- Maybe a UNIT?
---
-- Delete unit.
if self.UNITS[Event.IniDCSUnitName] then
self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName))
local DCSUnit = _DATABASE:FindUnit( Event.IniDCSUnitName )
self:T({DCSUnit})
if DCSUnit then
--self:I("Creating DEAD Event for UNIT "..tostring(Event.IniDCSUnitName))
--DCSUnit:Destroy(true)
return
end
end
else
if Event.IniObjectCategory == 1 then
---
-- UNITS
---
-- Delete unit.
if self.UNITS[Event.IniDCSUnitName] then
self:DeleteUnit(Event.IniDCSUnitName)
end
-- Remove client players.
local client=self.CLIENTS[name] --Wrapper.Client#CLIENT
if client then
client:RemovePlayers()
end
end
end
-- Add airbase if it was spawned later in the mission.
local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE
if airbase and (airbase:IsHelipad() or airbase:IsShip()) then
self:DeleteAirbase(Event.IniDCSUnitName)
end
end
-- Account destroys.
@@ -1187,28 +1224,28 @@ function DATABASE:_EventOnPlayerEnterUnit( Event )
if Event.IniDCSUnit then
if Event.IniObjectCategory == 1 then
-- Add unit.
self:AddUnit( Event.IniDCSUnitName )
-- Ini unit.
Event.IniUnit = self:FindUnit( Event.IniDCSUnitName )
-- Add group.
self:AddGroup( Event.IniDCSGroupName )
-- Get player unit.
local PlayerName = Event.IniDCSUnit:getPlayerName()
if PlayerName then
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniDCSUnitName, PlayerName )
end
local Settings = SETTINGS:Set( PlayerName )
Settings:SetPlayerMenu( Event.IniUnit )
else
self:E("ERROR: getPlayerName() returned nil for event PlayerEnterUnit")
end
@@ -1224,30 +1261,30 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
self:F2( { Event } )
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
-- Try to get the player name. This can be buggy for multicrew aircraft!
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName then --and self.PLAYERS[PlayerName] then
-- Debug info.
self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName)))
-- Remove player menu.
local Settings = SETTINGS:Set( PlayerName )
Settings:RemovePlayerMenu(Event.IniUnit)
-- Delete player.
self:DeletePlayer(Event.IniUnit, PlayerName)
-- Client stuff.
local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT
if client then
client:RemovePlayer(PlayerName)
end
end
end
end
@@ -1474,19 +1511,19 @@ function DATABASE:SetPlayerSettings( PlayerName, Settings )
self.PLAYERSETTINGS[PlayerName] = Settings
end
--- Add a flight group to the data base.
--- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base.
-- @param #DATABASE self
-- @param Ops.FlightGroup#FLIGHTGROUP flightgroup
function DATABASE:AddFlightGroup(flightgroup)
self:I({NewFlightGroup=flightgroup.groupname})
self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup
-- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB.
function DATABASE:AddOpsGroup(opsgroup)
--env.info("Adding OPSGROUP "..tostring(opsgroup.groupname))
self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup
end
--- Get a flight group from the data base.
--- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base.
-- @param #DATABASE self
-- @param #string groupname Group name of the flight group. Can also be passed as GROUP object.
-- @return Ops.FlightGroup#FLIGHTGROUP Flight group object.
function DATABASE:GetFlightGroup(groupname)
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:GetOpsGroup(groupname)
-- Get group and group name.
if type(groupname)=="string" then
@@ -1494,6 +1531,23 @@ function DATABASE:GetFlightGroup(groupname)
groupname=groupname:GetName()
end
--env.info("Getting OPSGROUP "..tostring(groupname))
return self.FLIGHTGROUPS[groupname]
end
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base.
-- @param #DATABASE self
-- @param #string groupname Group name of the group. Can also be passed as GROUP object.
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:FindOpsGroup(groupname)
-- Get group and group name.
if type(groupname)=="string" then
else
groupname=groupname:GetName()
end
--env.info("Getting OPSGROUP "..tostring(groupname))
return self.FLIGHTGROUPS[groupname]
end
@@ -1580,13 +1634,13 @@ function DATABASE:_RegisterTemplates()
for group_num, Template in pairs(obj_type_data.group) do
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)
else
self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID)
end --if GroupTemplate and GroupTemplate.units then
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
+76 -69
View File
@@ -983,13 +983,12 @@ end
-- @param #EVENTDATA Event Event data table.
function EVENT:onEvent( Event )
--- Function to handle errors.
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
if BASE.Debug ~= nil then
env.info( debug.traceback() )
end
return errmsg
end
@@ -1002,6 +1001,7 @@ function EVENT:onEvent( Event )
if self and self.Events and self.Events[Event.id] and self.MissionEnd==false and (Event.initiator~=nil or (Event.initiator==nil and Event.id~=EVENTS.PlayerLeaveUnit)) then
-- Check if mission has ended.
if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true
end
@@ -1009,35 +1009,12 @@ function EVENT:onEvent( Event )
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
if Event.IniObjectCategory == Object.Category.UNIT then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
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 )
end
Event.IniDCSGroupName = ""
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
--if Event.IniGroup then
Event.IniGroupName = Event.IniDCSGroupName
--end
end
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
end
if Event.IniObjectCategory == Object.Category.STATIC then
if Event.IniObjectCategory == Object.Category.STATIC then
---
-- Static
---
if Event.id==31 then
-- Event.initiator is a Static object representing the pilot. But getName() errors due to DCS bug.
Event.IniDCSUnit = Event.initiator
local ID=Event.initiator.id_
@@ -1063,9 +1040,47 @@ function EVENT:onEvent( Event )
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
-- Dead events of units can be delayed and the initiator changed to a static.
-- Take care of that.
local Unit=UNIT:FindByName(Event.IniDCSUnitName)
if Unit then
Event.IniObjectCategory = Object.Category.UNIT
end
end
if Event.IniObjectCategory == Object.Category.UNIT then
---
-- Unit
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
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 )
end
Event.IniDCSGroupName = Event.IniUnit and Event.IniUnit.GroupName or ""
if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then
Event.IniDCSGroupName = Event.IniDCSGroup:getName()
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.IniGroupName = Event.IniDCSGroupName
end
Event.IniPlayerName = Event.IniDCSUnit:getPlayerName()
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
end
if Event.IniObjectCategory == Object.Category.CARGO then
---
-- Cargo
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
@@ -1076,15 +1091,22 @@ function EVENT:onEvent( Event )
end
if Event.IniObjectCategory == Object.Category.SCENERY then
---
-- Scenery
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = SCENERY:Register( Event.IniDCSUnitName, Event.initiator )
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1!
Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY"
end
if Event.IniObjectCategory == Object.Category.BASE then
---
-- Base Object
---
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
@@ -1096,7 +1118,12 @@ function EVENT:onEvent( Event )
end
if Event.target then
---
-- TARGET
---
-- Target category.
Event.TgtObjectCategory = Event.target:getCategory()
if Event.TgtObjectCategory == Object.Category.UNIT then
@@ -1109,9 +1136,7 @@ function EVENT:onEvent( Event )
if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then
Event.TgtDCSGroupName = Event.TgtDCSGroup:getName()
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
--if Event.TgtGroup then
Event.TgtGroupName = Event.TgtDCSGroupName
--end
Event.TgtGroupName = Event.TgtDCSGroupName
end
Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName()
Event.TgtCoalition = Event.TgtDCSUnit:getCoalition()
@@ -1159,6 +1184,7 @@ function EVENT:onEvent( Event )
end
end
-- Weapon.
if Event.weapon then
Event.Weapon = Event.weapon
Event.WeaponName = Event.Weapon:getTypeName()
@@ -1193,23 +1219,22 @@ function EVENT:onEvent( Event )
Event.MarkGroupID = Event.groupID
end
-- Cargo object.
if Event.cargo then
Event.Cargo = Event.cargo
Event.CargoName = Event.cargo.Name
end
-- Zone object.
if Event.zone then
Event.Zone = Event.zone
Event.ZoneName = Event.zone.ZoneName
end
-- Priority order.
local PriorityOrder = EventMeta.Order
local PriorityBegin = PriorityOrder == -1 and 5 or 1
local PriorityEnd = PriorityOrder == -1 and 1 or 5
if Event.IniObjectCategory ~= Object.Category.STATIC then
self:F( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } )
end
local PriorityEnd = PriorityOrder == -1 and 1 or 5
for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do
@@ -1222,8 +1247,8 @@ function EVENT:onEvent( Event )
-- self:E( { "Evaluating: ", EventClass:GetClassNameAndID() } )
--end
Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName )
Event.IniGroup = Event.IniGroup or GROUP:FindByName( Event.IniDCSGroupName )
Event.TgtGroup = Event.TgtGroup or GROUP:FindByName( Event.TgtDCSGroupName )
-- If the EventData is for a UNIT, the call directly the EventClass EventFunction for that UNIT.
if EventData.EventUnit then
@@ -1233,20 +1258,17 @@ function EVENT:onEvent( Event )
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
Event.id == EVENTS.RemoveUnit or
Event.id == EVENTS.UnitLost then
local UnitName = EventClass:GetName()
if ( EventMeta.Side == "I" and UnitName == Event.IniDCSUnitName ) or
( EventMeta.Side == "T" and UnitName == Event.TgtDCSUnitName ) then
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for UNIT ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
@@ -1259,15 +1281,12 @@ function EVENT:onEvent( Event )
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event )
end, ErrorHandler )
end
end
end
else
@@ -1285,7 +1304,8 @@ function EVENT:onEvent( Event )
Event.id == EVENTS.PlayerEnterUnit or
Event.id == EVENTS.Crash or
Event.id == EVENTS.Dead or
Event.id == EVENTS.RemoveUnit then
Event.id == EVENTS.RemoveUnit or
Event.id == EVENTS.UnitLost then
-- We can get the name of the EventClass, which is now always a GROUP object.
local GroupName = EventClass:GetName()
@@ -1296,10 +1316,6 @@ function EVENT:onEvent( Event )
-- First test if a EventFunction is Set, otherwise search for the default function
if EventData.EventFunction then
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling EventFunction for GROUP ", EventClass:GetClassNameAndID(), ", Unit ", Event.IniUnitName, EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event, unpack( EventData.Params ) )
@@ -1312,10 +1328,6 @@ function EVENT:onEvent( Event )
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F( { "Calling " .. EventMeta.Event .. " for GROUP ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventFunction( EventClass, Event, unpack( EventData.Params ) )
@@ -1328,7 +1340,7 @@ function EVENT:onEvent( Event )
--self:RemoveEvent( EventClass, Event.id )
end
else
-- If the EventData is not bound to a specific unit, then call the EventClass EventFunction.
-- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon.
if not EventData.EventUnit then
@@ -1337,9 +1349,6 @@ function EVENT:onEvent( Event )
if EventData.EventFunction then
-- There is an EventFunction defined, so call the EventFunction.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling EventFunction for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
return EventData.EventFunction( EventClass, Event )
@@ -1351,16 +1360,14 @@ function EVENT:onEvent( Event )
if EventFunction and type( EventFunction ) == "function" then
-- Now call the default event function.
if Event.IniObjectCategory ~= 3 then
self:F2( { "Calling " .. EventMeta.Event .. " for Class ", EventClass:GetClassNameAndID(), EventPriority } )
end
local Result, Value = xpcall(
function()
local Result, Value = EventFunction( EventClass, Event )
return Result, Value
end, ErrorHandler )
end
end
end
+17 -17
View File
@@ -410,7 +410,7 @@ do -- FSM
Transition.To = To
-- Debug message.
self:T2( Transition )
--self:T3( Transition )
self._Transitions[Transition] = Transition
self:_eventmap( self.Events, Transition )
@@ -432,7 +432,7 @@ do -- FSM
-- @param #table ReturnEvents A table indicating for which returned events of the SubFSM which Event must be triggered in the FSM.
-- @return Core.Fsm#FSM_PROCESS The SubFSM.
function FSM:AddProcess( From, Event, Process, ReturnEvents )
self:T( { From, Event } )
--self:T3( { From, Event } )
local Sub = {}
Sub.From = From
@@ -533,7 +533,7 @@ do -- FSM
Process._Scores[State].ScoreText = ScoreText
Process._Scores[State].Score = Score
self:T( Process._Scores )
--self:T3( Process._Scores )
return Process
end
@@ -576,7 +576,7 @@ do -- FSM
self[__Event] = self[__Event] or self:_delayed_transition(Event)
-- Debug message.
self:T2( "Added methods: " .. Event .. ", " .. __Event )
--self:T3( "Added methods: " .. Event .. ", " .. __Event )
Events[Event] = self.Events[Event] or { map = {} }
self:_add_to_map( Events[Event].map, EventStructure )
@@ -791,7 +791,7 @@ do -- FSM
return function( self, DelaySeconds, ... )
-- Debug.
self:T2( "Delayed Event: " .. EventName )
self:T3( "Delayed Event: " .. EventName )
local CallID = 0
if DelaySeconds ~= nil then
@@ -809,23 +809,23 @@ do -- FSM
self._EventSchedules[EventName] = CallID
-- Debug output.
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
else
self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
self:T2(string.format("NEGATIVE Event %s delayed by %.3f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds))
-- reschedule
end
else
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
self:T2(string.format("Event %s delayed by %.3f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID)))
end
else
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
end
-- Debug.
self:T2( { CallID = CallID } )
--self:T3( { CallID = CallID } )
end
end
@@ -846,7 +846,7 @@ do -- FSM
function FSM:_gosub( ParentFrom, ParentEvent )
local fsmtable = {}
if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then
self:T( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
--self:T3( { ParentFrom, ParentEvent, self.subs[ParentFrom], self.subs[ParentFrom][ParentEvent] } )
return self.subs[ParentFrom][ParentEvent]
else
return {}
@@ -893,7 +893,7 @@ do -- FSM
end
end
self:T3( { Map, Event } )
--self:T3( { Map, Event } )
end
--- Get current state.
@@ -913,7 +913,7 @@ do -- FSM
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
-- @return #boolean If true, FSM is in this state.
function FSM:Is( State )
return self.current == State
end
@@ -921,7 +921,7 @@ do -- FSM
--- Check if FSM is in state.
-- @param #FSM self
-- @param #string State State name.
-- @param #boolean If true, FSM is in this state.
-- @return #boolean If true, FSM is in this state.
function FSM:is(state)
return self.current == state
end
@@ -1150,7 +1150,7 @@ do -- FSM_PROCESS
-- @param #FSM_PROCESS self
-- @return #FSM_PROCESS
function FSM_PROCESS:Copy( Controllable, Task )
self:T( { self:GetClassNameAndID() } )
--self:T3( { self:GetClassNameAndID() } )
local NewFsm = self:New( Controllable, Task ) -- Core.Fsm#FSM_PROCESS
@@ -1176,13 +1176,13 @@ do -- FSM_PROCESS
-- Copy End States
for EndStateID, EndState in pairs( self:GetEndStates() ) do
self:T( EndState )
--self:T3( EndState )
NewFsm:AddEndState( EndState )
end
-- Copy the score tables
for ScoreID, Score in pairs( self:GetScores() ) do
self:T( Score )
--self:T3( Score )
NewFsm:AddScore( ScoreID, Score.ScoreText, Score.Score )
end
@@ -1431,7 +1431,7 @@ do -- FSM_SET
-- @param #FSM_SET self
-- @return Core.Set#SET_BASE
function FSM_SET:Get()
return self.Controllable
return self.Set
end
function FSM_SET:_call_handler( step, trigger, params, EventName )
@@ -0,0 +1,251 @@
--- **Core** - MarkerOps_Base.
--
-- **Main Features:**
--
-- * Create an easy way to tap into markers added to the F10 map by users.
-- * Recognize own tag and list of keywords.
-- * Matched keywords are handed down to functions.
--
-- ===
--
-- ### Author: **Applevangelist**
--
-- Date: 5 May 2021
--
-- ===
---
-- @module Core.MarkerOps_Base
-- @image MOOSE_Core.JPG
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition.
--------------------------------------------------------------------------
--- MARKEROPS_BASE class.
-- @type MARKEROPS_BASE
-- @field #string ClassName Name of the class.
-- @field #string Tag Tag to identify commands.
-- @field #table Keywords Table of keywords to recognize.
-- @field #string version Version of #MARKEROPS_BASE.
-- @extends Core.Fsm#FSM
--- *Fiat lux.* -- Latin proverb.
--
-- ===
--
-- # The MARKEROPS_BASE Concept
--
-- This class enable scripting text-based actions from markers.
--
-- @field #MARKEROPS_BASE
MARKEROPS_BASE = {
ClassName = "MARKEROPS",
Tag = "mytag",
Keywords = {},
version = "0.0.1",
debug = false,
}
--- Function to instantiate a new #MARKEROPS_BASE object.
-- @param #MARKEROPS_BASE self
-- @param #string Tagname Name to identify us from the event text.
-- @param #table Keywords Table of keywords recognized from the event text.
-- @return #MARKEROPS_BASE self
function MARKEROPS_BASE:New(Tagname,Keywords)
-- Inherit FSM
local self=BASE:Inherit(self, FSM:New()) -- #MARKEROPS_BASE
-- Set some string id for output to DCS.log file.
self.lid=string.format("MARKEROPS_BASE %s | ", tostring(self.version))
self.Tag = Tagname or "mytag"-- #string
self.Keywords = Keywords or {} -- #table - might want to use lua regex here, too
self.debug = false
-----------------------
--- FSM Transitions ---
-----------------------
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start the FSM.
self:AddTransition("*", "MarkAdded", "*") -- Start the FSM.
self:AddTransition("*", "MarkChanged", "*") -- Start the FSM.
self:AddTransition("*", "MarkDeleted", "*") -- Start the FSM.
self:AddTransition("Running", "Stop", "Stopped") -- Stop the FSM.
self:HandleEvent(EVENTS.MarkAdded, self.OnEventMark)
self:HandleEvent(EVENTS.MarkChange, self.OnEventMark)
self:HandleEvent(EVENTS.MarkRemoved, self.OnEventMark)
-- start
self:I(self.lid..string.format("started for %s",self.Tag))
self:__Start(1)
return self
-------------------
-- PSEUDO Functions
-------------------
--- On after "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkAdded
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkChanged
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
--- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map.
-- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
--- "Stop" trigger. Used to stop the function an unhandle events
-- @function [parent=#MARKEROPS_BASE] Stop
end
--- (internal) Handle events.
-- @param #MARKEROPS self
-- @param Core.Event#EVENTDATA Event
function MARKEROPS_BASE:OnEventMark(Event)
self:T({Event})
if Event == nil or Event.idx == nil then
self:E("Skipping onEvent. Event or Event.idx unknown.")
return true
end
--position
local vec3={y=Event.pos.y, x=Event.pos.x, z=Event.pos.z}
local coord=COORDINATE:NewFromVec3(vec3)
if self.debug then
local coordtext = coord:ToStringLLDDM()
local text = tostring(Event.text)
local m = MESSAGE:New(string.format("Mark added at %s with text: %s",coordtext,text),10,"Info",false):ToAll()
end
-- decision
if Event.id==world.event.S_EVENT_MARK_ADDED then
self:T({event="S_EVENT_MARK_ADDED", carrier=self.groupname, vec3=Event.pos})
-- Handle event
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkAdded(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_CHANGE then
self:T({event="S_EVENT_MARK_CHANGE", carrier=self.groupname, vec3=Event.pos})
-- Handle event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
local matchtable = self:_MatchKeywords(Eventtext)
self:MarkChanged(Eventtext,matchtable,coord)
end
end
elseif Event.id==world.event.S_EVENT_MARK_REMOVED then
self:T({event="S_EVENT_MARK_REMOVED", carrier=self.groupname, vec3=Event.pos})
-- Hande event.
local Eventtext = tostring(Event.text)
if Eventtext~=nil then
if self:_MatchTag(Eventtext) then
self:MarkDeleted()
end
end
end
end
--- (internal) Match tag.
-- @param #MARKEROPS self
-- @param #string Eventtext Text added to the marker.
-- @return #boolean
function MARKEROPS_BASE:_MatchTag(Eventtext)
local matches = false
local type = string.lower(self.Tag) -- #string
if string.find(string.lower(Eventtext),type) then
matches = true --event text contains tag
end
return matches
end
--- (internal) Match keywords table.
-- @param #MARKEROPS self
-- @param #string Eventtext Text added to the marker.
-- @return #table
function MARKEROPS_BASE:_MatchKeywords(Eventtext)
local matchtable = {}
local keytable = self.Keywords
for _index,_word in pairs (keytable) do
if string.find(string.lower(Eventtext),string.lower(_word))then
table.insert(matchtable,_word)
end
end
return matchtable
end
--- On before "MarkAdded" event. Triggered when a Marker is added to the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkAdded(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkChanged" event. Triggered when a Marker is changed on the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
-- @param #string Text The text on the marker
-- @param #table Keywords Table of matching keywords found in the Event text
-- @param Core.Point#COORDINATE Coord Coordinate of the marker.
function MARKEROPS_BASE:onbeforeMarkChanged(From,Event,To,Text,Keywords,Coord)
self:T({self.lid,From,Event,To,Text,Keywords,Coord:ToStringLLDDM()})
end
--- On before "MarkDeleted" event. Triggered when a Marker is removed from the F10 map.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onbeforeMarkDeleted(From,Event,To)
self:T({self.lid,From,Event,To})
end
--- On enter "Stopped" event. Unsubscribe events.
-- @param #MARKEROPS_BASE self
-- @param #string From The From state
-- @param #string Event The Event called
-- @param #string To The To state
function MARKEROPS_BASE:onenterStopped(From,Event,To)
self:T({self.lid,From,Event,To})
-- unsubscribe from events
self:UnHandleEvent(EVENTS.MarkAdded)
self:UnHandleEvent(EVENTS.MarkChange)
self:UnHandleEvent(EVENTS.MarkRemoved)
end
--------------------------------------------------------------------------
-- MARKEROPS_BASE Class Definition End.
--------------------------------------------------------------------------
+35 -166
View File
@@ -14,7 +14,7 @@
-- * Only create or delete menus when required, and keep existing menus persistent.
-- * Update menu structures.
-- * Refresh menu structures intelligently, based on a time stamp of updates.
-- - Delete obscolete menus.
-- - Delete obsolete menus.
-- - Create new one where required.
-- - Don't touch the existing ones.
-- * Provide a variable amount of parameters to menus.
@@ -23,7 +23,7 @@
-- * Provide a great tool to manage menus in your code.
--
-- DCS Menus can be managed using the MENU classes.
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scanerios where you need to
-- The advantage of using MENU classes is that it hides the complexity of dealing with menu management in more advanced scenarios where you need to
-- set menus and later remove them, and later set them again. You'll find while using use normal DCS scripting functions, that setting and removing
-- menus is not a easy feat if you have complex menu hierarchies defined.
-- Using the MOOSE menu classes, the removal and refreshing of menus are nicely being handled within these classes, and becomes much more easy.
@@ -53,7 +53,6 @@
-- @module Core.Menu
-- @image Core_Menu.JPG
MENU_INDEX = {}
MENU_INDEX.MenuMission = {}
MENU_INDEX.MenuMission.Menus = {}
@@ -64,10 +63,7 @@ MENU_INDEX.Coalition[coalition.side.RED] = {}
MENU_INDEX.Coalition[coalition.side.RED].Menus = {}
MENU_INDEX.Group = {}
function MENU_INDEX:ParentPath( ParentMenu, MenuText )
local Path = ParentMenu and "@" .. table.concat( ParentMenu.MenuPath or {}, "@" ) or ""
if ParentMenu then
if ParentMenu:IsInstanceOf( "MENU_GROUP" ) or ParentMenu:IsInstanceOf( "MENU_GROUP_COMMAND" ) then
@@ -95,20 +91,16 @@ function MENU_INDEX:ParentPath( ParentMenu, MenuText )
Path = Path .. "@" .. MenuText
return Path
end
function MENU_INDEX:PrepareMission()
self.MenuMission.Menus = self.MenuMission.Menus or {}
end
function MENU_INDEX:PrepareCoalition( CoalitionSide )
self.Coalition[CoalitionSide] = self.Coalition[CoalitionSide] or {}
self.Coalition[CoalitionSide].Menus = self.Coalition[CoalitionSide].Menus or {}
end
---
-- @param Wrapper.Group#GROUP Group
function MENU_INDEX:PrepareGroup( Group )
@@ -119,42 +111,26 @@ function MENU_INDEX:PrepareGroup( Group )
end
end
function MENU_INDEX:HasMissionMenu( Path )
return self.MenuMission.Menus[Path]
end
function MENU_INDEX:SetMissionMenu( Path, Menu )
self.MenuMission.Menus[Path] = Menu
end
function MENU_INDEX:ClearMissionMenu( Path )
self.MenuMission.Menus[Path] = nil
end
function MENU_INDEX:HasCoalitionMenu( Coalition, Path )
return self.Coalition[Coalition].Menus[Path]
end
function MENU_INDEX:SetCoalitionMenu( Coalition, Path, Menu )
self.Coalition[Coalition].Menus[Path] = Menu
end
function MENU_INDEX:ClearCoalitionMenu( Coalition, Path )
self.Coalition[Coalition].Menus[Path] = nil
end
function MENU_INDEX:HasGroupMenu( Group, Path )
if Group and Group:IsAlive() then
local MenuGroupName = Group:GetName()
@@ -162,53 +138,36 @@ function MENU_INDEX:HasGroupMenu( Group, Path )
end
return nil
end
function MENU_INDEX:SetGroupMenu( Group, Path, Menu )
local MenuGroupName = Group:GetName()
Group:F({MenuGroupName=MenuGroupName,Path=Path})
self.Group[MenuGroupName].Menus[Path] = Menu
end
function MENU_INDEX:ClearGroupMenu( Group, Path )
local MenuGroupName = Group:GetName()
self.Group[MenuGroupName].Menus[Path] = nil
end
function MENU_INDEX:Refresh( Group )
for MenuID, Menu in pairs( self.MenuMission.Menus ) do
Menu:Refresh()
end
for MenuID, Menu in pairs( self.Coalition[coalition.side.BLUE].Menus ) do
Menu:Refresh()
end
for MenuID, Menu in pairs( self.Coalition[coalition.side.RED].Menus ) do
Menu:Refresh()
end
local GroupName = Group:GetName()
for MenuID, Menu in pairs( self.Group[GroupName].Menus ) do
Menu:Refresh()
end
return self
end
do -- MENU_BASE
--- @type MENU_BASE
-- @extends Core.Base#BASE
--- Defines the main MENU class where other MENU classes are derived from.
-- This is an abstract class, so don't use it.
-- @field #MENU_BASE
@@ -216,10 +175,10 @@ do -- MENU_BASE
ClassName = "MENU_BASE",
MenuPath = nil,
MenuText = "",
MenuParentPath = nil
MenuParentPath = nil,
}
--- Consructor
--- Constructor
-- @param #MENU_BASE
-- @return #MENU_BASE
function MENU_BASE:New( MenuText, ParentMenu )
@@ -228,27 +187,25 @@ do -- MENU_BASE
if ParentMenu ~= nil then
MenuParentPath = ParentMenu.MenuPath
end
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, BASE:New() )
self.MenuPath = nil
self.MenuText = MenuText
self.ParentMenu = ParentMenu
self.MenuParentPath = MenuParentPath
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
self.MenuPath = nil
self.MenuText = MenuText
self.ParentMenu = ParentMenu
self.MenuParentPath = MenuParentPath
self.Path = ( self.ParentMenu and "@" .. table.concat( self.MenuParentPath or {}, "@" ) or "" ) .. "@" .. self.MenuText
self.Menus = {}
self.MenuCount = 0
self.MenuStamp = timer.getTime()
self.MenuRemoveParent = false
if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
self.ParentMenu.Menus[MenuText] = self
end
return self
return self
end
function MENU_BASE:SetParentMenu( MenuText, Menu )
if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
@@ -256,7 +213,6 @@ do -- MENU_BASE
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
end
end
function MENU_BASE:ClearParentMenu( MenuText )
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
self.ParentMenu.Menus[MenuText] = nil
@@ -266,7 +222,6 @@ do -- MENU_BASE
end
end
end
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
-- @param #MENU_BASE self
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
@@ -276,7 +231,6 @@ do -- MENU_BASE
self.MenuRemoveParent = RemoveParent
return self
end
--- Gets a @{Menu} from a parent @{Menu}
-- @param #MENU_BASE self
@@ -285,7 +239,6 @@ do -- MENU_BASE
function MENU_BASE:GetMenu( MenuText )
return self.Menus[MenuText]
end
--- Sets a menu stamp for later prevention of menu removal.
-- @param #MENU_BASE self
-- @param MenuStamp
@@ -323,9 +276,7 @@ do -- MENU_BASE
end
end
do -- MENU_COMMAND_BASE
--- @type MENU_COMMAND_BASE
-- @field #function MenuCallHandler
-- @extends Core.Menu#MENU_BASE
@@ -346,8 +297,7 @@ do -- MENU_COMMAND_BASE
-- @return #MENU_COMMAND_BASE
function MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArguments )
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) ) -- #MENU_COMMAND_BASE
-- When a menu function goes into error, DCS displays an obscure menu message.
-- This error handler catches the menu error and displays the full call stack.
local ErrorHandler = function( errmsg )
@@ -367,7 +317,7 @@ do -- MENU_COMMAND_BASE
local Status, Result = xpcall( MenuFunction, ErrorHandler )
end
return self
return self
end
--- This sets the new command function of a menu,
@@ -380,7 +330,6 @@ do -- MENU_COMMAND_BASE
self.CommandMenuFunction = CommandMenuFunction
return self
end
--- This sets the new command arguments of a menu,
-- so that if a menu is regenerated, or if command arguments change,
-- that the arguments set for the menu are loosely coupled with the menu itself!!!
@@ -391,35 +340,30 @@ do -- MENU_COMMAND_BASE
self.CommandMenuArguments = CommandMenuArguments
return self
end
end
do -- MENU_MISSION
--- @type MENU_MISSION
-- @extends Core.Menu#MENU_BASE
--- Manages the main menus for a complete mission.
--
-- You can add menus with the @{#MENU_MISSION.New} method, which constructs a MENU_MISSION object and returns you the object reference.
-- Using this object reference, you can then remove ALL the menus and submenus underlying automatically with @{#MENU_MISSION.Remove}.
-- @field #MENU_MISSION
MENU_MISSION = {
ClassName = "MENU_MISSION"
ClassName = "MENU_MISSION",
}
--- MENU_MISSION constructor. Creates a new MENU_MISSION object and creates the menu for a complete mission file.
-- @param #MENU_MISSION self
-- @param #string MenuText The text for the menu.
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
-- @return #MENU_MISSION
function MENU_MISSION:New( MenuText, ParentMenu )
MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu then
return MissionMenu
else
@@ -432,17 +376,15 @@ do -- MENU_MISSION
end
end
--- Refreshes a radio item for a mission
-- @param #MENU_MISSION self
-- @return #MENU_MISSION
function MENU_MISSION:Refresh()
do
missionCommands.removeItem( self.MenuPath )
self.MenuPath = missionCommands.addSubMenu( self.MenuText, self.MenuParentPath )
end
return self
end
--- Removes the sub menus recursively of this MENU_MISSION. Note that the main menu is kept!
@@ -466,7 +408,6 @@ do -- MENU_MISSION
MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu == self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -487,10 +428,7 @@ do -- MENU_MISSION
return self
end
end
do -- MENU_MISSION_COMMAND
--- @type MENU_MISSION_COMMAND
@@ -503,7 +441,7 @@ do -- MENU_MISSION_COMMAND
--
-- @field #MENU_MISSION_COMMAND
MENU_MISSION_COMMAND = {
ClassName = "MENU_MISSION_COMMAND"
ClassName = "MENU_MISSION_COMMAND",
}
--- MENU_MISSION constructor. Creates a new radio command item for a complete mission file, which can invoke a function with parameters.
@@ -518,7 +456,6 @@ do -- MENU_MISSION_COMMAND
MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu then
MissionMenu:SetCommandMenuFunction( CommandMenuFunction )
MissionMenu:SetCommandMenuArguments( arg )
@@ -532,17 +469,15 @@ do -- MENU_MISSION_COMMAND
return self
end
end
--- Refreshes a radio item for a mission
-- @param #MENU_MISSION_COMMAND self
-- @return #MENU_MISSION_COMMAND
function MENU_MISSION_COMMAND:Refresh()
do
missionCommands.removeItem( self.MenuPath )
missionCommands.addCommand( self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end
return self
end
--- Removes a radio command item for a coalition
@@ -553,7 +488,6 @@ do -- MENU_MISSION_COMMAND
MENU_INDEX:PrepareMission()
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local MissionMenu = MENU_INDEX:HasMissionMenu( Path )
if MissionMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -572,13 +506,8 @@ do -- MENU_MISSION_COMMAND
return self
end
end
do -- MENU_COALITION
--- @type MENU_COALITION
-- @extends Core.Menu#MENU_BASE
@@ -634,18 +563,15 @@ do -- MENU_COALITION
-- @param #MENU_COALITION self
-- @param DCS#coalition.side Coalition The coalition owning the menu.
-- @param #string MenuText The text for the menu.
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the perent menu of DCS world (under F10 other).
-- @param #table ParentMenu The parent menu. This parameter can be ignored if you want the menu to be located at the parent menu of DCS world (under F10 other).
-- @return #MENU_COALITION self
function MENU_COALITION:New( Coalition, MenuText, ParentMenu )
MENU_INDEX:PrepareCoalition( Coalition )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
if CoalitionMenu then
return CoalitionMenu
else
local self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetCoalitionMenu( Coalition, Path, self )
@@ -656,17 +582,15 @@ do -- MENU_COALITION
return self
end
end
--- Refreshes a radio item for a coalition
-- @param #MENU_COALITION self
-- @return #MENU_COALITION
function MENU_COALITION:Refresh()
do
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
missionCommands.addSubMenuForCoalition( self.Coalition, self.MenuText, self.MenuParentPath )
end
return self
end
--- Removes the sub menus recursively of this MENU_COALITION. Note that the main menu is kept!
@@ -689,7 +613,6 @@ do -- MENU_COALITION
MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
if CoalitionMenu == self then
self:RemoveSubMenus()
if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -709,11 +632,7 @@ do -- MENU_COALITION
return self
end
end
do -- MENU_COALITION_COMMAND
--- @type MENU_COALITION_COMMAND
@@ -742,7 +661,6 @@ do -- MENU_COALITION_COMMAND
MENU_INDEX:PrepareCoalition( Coalition )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( Coalition, Path )
if CoalitionMenu then
CoalitionMenu:SetCommandMenuFunction( CommandMenuFunction )
CoalitionMenu:SetCommandMenuArguments( arg )
@@ -757,20 +675,17 @@ do -- MENU_COALITION_COMMAND
self:SetParentMenu( self.MenuText, self )
return self
end
end
--- Refreshes a radio item for a coalition
-- @param #MENU_COALITION_COMMAND self
-- @return #MENU_COALITION_COMMAND
function MENU_COALITION_COMMAND:Refresh()
do
missionCommands.removeItemForCoalition( self.Coalition, self.MenuPath )
missionCommands.addCommandForCoalition( self.Coalition, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end
return self
end
--- Removes a radio command item for a coalition
@@ -781,7 +696,6 @@ do -- MENU_COALITION_COMMAND
MENU_INDEX:PrepareCoalition( self.Coalition )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local CoalitionMenu = MENU_INDEX:HasCoalitionMenu( self.Coalition, Path )
if CoalitionMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -800,20 +714,16 @@ do -- MENU_COALITION_COMMAND
return self
end
end
--- MENU_GROUP
do
-- This local variable is used to cache the menus registered under groups.
-- Menus don't dissapear when groups for players are destroyed and restarted.
-- Menus don't disappear when groups for players are destroyed and restarted.
-- So every menu for a client created must be tracked so that program logic accidentally does not create.
-- the same menus twice during initialization logic.
-- These menu classes are handling this logic with this variable.
local _MENUGROUPS = {}
--- @type MENU_GROUP
-- @extends Core.Menu#MENU_BASE
@@ -889,16 +799,13 @@ do
MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then
return GroupMenu
else
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group
self.GroupID = Group:GetID()
self.MenuPath = missionCommands.addSubMenuForGroup( self.GroupID, MenuText, self.MenuParentPath )
self:SetParentMenu( self.MenuText, self )
@@ -906,12 +813,10 @@ do
end
end
--- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP self
-- @return #MENU_GROUP
function MENU_GROUP:Refresh()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -920,7 +825,8 @@ do
Menu:Refresh()
end
end
return self
end
--- Removes the sub menus recursively of this MENU_GROUP.
@@ -929,7 +835,6 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP self
function MENU_GROUP:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuStamp, MenuTag )
end
@@ -938,18 +843,15 @@ do
end
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP self
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -993,18 +895,15 @@ do
-- @param CommandMenuArgument An argument for the function.
-- @return #MENU_GROUP_COMMAND
function MENU_GROUP_COMMAND:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
GroupMenu:SetCommandMenuArguments( arg )
return GroupMenu
else
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group
@@ -1015,19 +914,17 @@ do
self:SetParentMenu( self.MenuText, self )
return self
end
end
--- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND self
-- @return #MENU_GROUP_COMMAND
function MENU_GROUP_COMMAND:Refresh()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end
return self
end
--- Removes a menu structure for a group.
@@ -1036,11 +933,9 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_COMMAND:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -1059,13 +954,9 @@ do
return self
end
end
--- MENU_GROUP_DELAYED
do
--- @type MENU_GROUP_DELAYED
-- @extends Core.Menu#MENU_BASE
@@ -1093,16 +984,13 @@ do
MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then
return GroupMenu
else
self = BASE:Inherit( self, MENU_BASE:New( MenuText, ParentMenu ) )
MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group
self.GroupID = Group:GetID()
if self.MenuParentPath then
self.MenuPath = UTILS.DeepCopy( self.MenuParentPath )
else
@@ -1116,12 +1004,10 @@ do
end
--- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Set()
do
if not self.MenuSet then
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -1132,15 +1018,12 @@ do
Menu:Set()
end
end
end
--- Refreshes a new radio item for a group and submenus
-- @param #MENU_GROUP_DELAYED self
-- @return #MENU_GROUP_DELAYED
function MENU_GROUP_DELAYED:Refresh()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addSubMenuForGroup( self.GroupID, self.MenuText, self.MenuParentPath )
@@ -1149,7 +1032,8 @@ do
Menu:Refresh()
end
end
return self
end
--- Removes the sub menus recursively of this MENU_GROUP_DELAYED.
@@ -1158,7 +1042,6 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #MENU_GROUP_DELAYED self
function MENU_GROUP_DELAYED:RemoveSubMenus( MenuStamp, MenuTag )
for MenuText, Menu in pairs( self.Menus or {} ) do
Menu:Remove( MenuStamp, MenuTag )
end
@@ -1167,18 +1050,15 @@ do
end
--- Removes the main menu and sub menus recursively of this MENU_GROUP.
-- @param #MENU_GROUP_DELAYED self
-- @param MenuStamp
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
self:RemoveSubMenus( MenuStamp, MenuTag )
if not MenuStamp or self.MenuStamp ~= MenuStamp then
@@ -1223,18 +1103,15 @@ do
-- @param CommandMenuArgument An argument for the function.
-- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:New( Group, MenuText, ParentMenu, CommandMenuFunction, ... )
MENU_INDEX:PrepareGroup( Group )
local Path = MENU_INDEX:ParentPath( ParentMenu, MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( Group, Path )
if GroupMenu then
GroupMenu:SetCommandMenuFunction( CommandMenuFunction )
GroupMenu:SetCommandMenuArguments( arg )
return GroupMenu
else
self = BASE:Inherit( self, MENU_COMMAND_BASE:New( MenuText, ParentMenu, CommandMenuFunction, arg ) )
MENU_INDEX:SetGroupMenu( Group, Path, self )
self.Group = Group
@@ -1250,33 +1127,29 @@ do
self:SetParentMenu( self.MenuText, self )
return self
end
end
--- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND_DELAYED self
-- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:Set()
do
if not self.MenuSet then
self.MenuPath = missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
self.MenuSet = true
end
end
end
--- Refreshes a radio item for a group
-- @param #MENU_GROUP_COMMAND_DELAYED self
-- @return #MENU_GROUP_COMMAND_DELAYED
function MENU_GROUP_COMMAND_DELAYED:Refresh()
do
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
missionCommands.addCommandForGroup( self.GroupID, self.MenuText, self.MenuParentPath, self.MenuCallHandler )
end
return self
end
--- Removes a menu structure for a group.
@@ -1285,11 +1158,9 @@ do
-- @param MenuTag A Tag or Key to filter the menus to be refreshed with the Tag set.
-- @return #nil
function MENU_GROUP_COMMAND_DELAYED:Remove( MenuStamp, MenuTag )
MENU_INDEX:PrepareGroup( self.Group )
local Path = MENU_INDEX:ParentPath( self.ParentMenu, self.MenuText )
local GroupMenu = MENU_INDEX:HasGroupMenu( self.Group, Path )
if GroupMenu == self then
if not MenuStamp or self.MenuStamp ~= MenuStamp then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
@@ -1308,6 +1179,4 @@ do
return self
end
end
+32 -2
View File
@@ -10,6 +10,7 @@
-- * Send message to all players.
-- * Send messages to a coalition.
-- * Send messages to a specific group.
-- * Send messages to a specific unit or client.
--
-- ===
--
@@ -35,6 +36,7 @@
--
-- * To a @{Client} using @{#MESSAGE.ToClient}().
-- * To a @{Wrapper.Group} using @{#MESSAGE.ToGroup}()
-- * To a @{Wrapper.Unit} using @{#MESSAGE.ToUnit}()
-- * To a coalition using @{#MESSAGE.ToCoalition}().
-- * To the red coalition using @{#MESSAGE.ToRed}().
-- * To the blue coalition using @{#MESSAGE.ToBlue}().
@@ -199,11 +201,14 @@ function MESSAGE:ToClient( Client, Settings )
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
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.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
@@ -233,6 +238,31 @@ function MESSAGE:ToGroup( Group, Settings )
return self
end
--- Sends a MESSAGE to a Unit.
-- @param #MESSAGE self
-- @param Wrapper.Unit#UNIT Unit to which the message is displayed.
-- @return #MESSAGE Message object.
function MESSAGE:ToUnit( Unit, Settings )
self:F( Unit.IdentifiableName )
if Unit then
if self.MessageType then
local Settings = Settings or ( Unit and _DATABASE:GetPlayerSettings( Unit:GetPlayerName() ) ) or _SETTINGS -- Core.Settings#SETTINGS
self.MessageDuration = Settings:GetMessageTime( self.MessageType )
self.MessageCategory = "" -- self.MessageType .. ": "
end
if self.MessageDuration ~= 0 then
self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration )
trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration, self.ClearScreen )
end
end
return self
end
--- Sends a MESSAGE to the Blue coalition.
-- @param #MESSAGE self
-- @return #MESSAGE
File diff suppressed because it is too large Load Diff
@@ -109,9 +109,11 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.ObjectSchedulers = self.ObjectSchedulers or setmetatable( {}, { __mode = "v" } )
if Scheduler.MasterObject then
--env.info("FF Object Scheduler")
self.ObjectSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, ObjectScheduler = tostring(self.ObjectSchedulers[CallID]), MasterObject = tostring(Scheduler.MasterObject) } )
else
--env.info("FF Persistent Scheduler")
self.PersistentSchedulers[CallID] = Scheduler
self:F3( { CallID = CallID, PersistentScheduler = self.PersistentSchedulers[CallID] } )
end
@@ -122,7 +124,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.Schedule[Scheduler][CallID].Function = ScheduleFunction
self.Schedule[Scheduler][CallID].Arguments = ScheduleArguments
self.Schedule[Scheduler][CallID].StartTime = timer.getTime() + ( Start or 0 )
self.Schedule[Scheduler][CallID].Start = Start + 0.1
self.Schedule[Scheduler][CallID].Start = Start + 0.001
self.Schedule[Scheduler][CallID].Repeat = Repeat or 0
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop
@@ -219,6 +221,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
if ShowTrace then
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end
-- The master object is passed as first parameter. A few :Schedule() calls in MOOSE expect this currently. But in principle it should be removed.
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end
Status, Result = xpcall( Timer, ErrorHandler )
@@ -317,7 +320,7 @@ end
--- Stop dispatcher.
-- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler Scheduler object.
-- @param #table CallID Call ID.
-- @param #string CallID (Optional) Scheduler Call ID. If nil, all pending schedules are stopped recursively.
function SCHEDULEDISPATCHER:Stop( Scheduler, CallID )
self:F2( { Stop = CallID, Scheduler = Scheduler } )
+2 -2
View File
@@ -238,7 +238,7 @@ end
-- @param #number Stop Time interval in seconds after which the scheduler will be stoppe.
-- @param #number TraceLevel Trace level [0,3]. Default 3.
-- @param Core.Fsm#FSM Fsm Finite state model.
-- @return #table The ScheduleID of the planned schedule.
-- @return #string The Schedule ID of the planned schedule.
function SCHEDULER:Schedule( MasterObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
self:F2( { Start, Repeat, RandomizeFactor, Stop } )
self:T3( { SchedulerArguments } )
@@ -273,7 +273,7 @@ end
--- (Re-)Starts the schedules or a specific schedule if a valid ScheduleID is provided.
-- @param #SCHEDULER self
-- @param #string ScheduleID (Optional) The ScheduleID of the planned (repeating) schedule.
-- @param #string ScheduleID (Optional) The Schedule ID of the planned (repeating) schedule.
function SCHEDULER:Start( ScheduleID )
self:F3( { ScheduleID } )
self:T(string.format("Starting scheduler ID=%s", tostring(ScheduleID)))
File diff suppressed because it is too large Load Diff
+16 -1
View File
@@ -253,6 +253,7 @@ do -- SETTINGS
self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 )
self:SetEraModern()
self:SetLocale("en")
return self
else
local Settings = _DATABASE:GetPlayerSettings( PlayerName )
@@ -284,7 +285,21 @@ do -- SETTINGS
function SETTINGS:SetMetric()
self.Metric = true
end
--- Sets the SETTINGS default text locale.
-- @param #SETTINGS self
-- @param #string Locale
function SETTINGS:SetLocale(Locale)
self.Locale = Locale or "en"
end
--- Gets the SETTINGS text locale.
-- @param #SETTINGS self
-- @return #string
function SETTINGS:GetLocale()
return self.Locale or _SETTINGS:GetLocale()
end
--- Gets if the SETTINGS is metric.
-- @param #SETTINGS self
-- @return #boolean true if metric.
+48 -30
View File
@@ -2423,8 +2423,7 @@ end
-- @param #SPAWN self
-- @param DCS#Vec3 Vec3 The Vec3 coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, Vec3, SpawnIndex } )
@@ -2493,8 +2492,7 @@ end
-- @param #SPAWN self
-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
@@ -2510,8 +2508,7 @@ end
-- @param #SPAWN self
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground.
@@ -2535,8 +2532,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnVec2 = ZONE:New( ZoneName ):GetVec2()
@@ -2569,8 +2565,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
@@ -2626,8 +2621,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnStatic = STATIC:FindByName( StaticName )
@@ -2658,8 +2652,7 @@ end
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil when nothing was spawned.
-- @return Wrapper.Group#GROUP that was spawned or #nil if nothing was spawned.
-- @usage
--
-- local SpawnZone = ZONE:New( ZoneName )
@@ -2854,18 +2847,37 @@ end
-- The method will search for a #-mark, and will return the text before the #-mark.
-- It will return nil of no prefix was found.
-- @param #SPAWN self
-- @param DCS#UNIT DCSUnit The @{DCSUnit} to be searched.
-- @return #string The prefix
-- @return #nil Nothing found
-- @param Wrapper.Group#GROUP SpawnGroup The GROUP object.
-- @return #string The prefix or #nil if nothing was found.
function SPAWN:_GetPrefixFromGroup( SpawnGroup )
self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
local GroupName = SpawnGroup:GetName()
if GroupName then
local SpawnPrefix = string.match( GroupName, ".*#" )
local SpawnPrefix=self:_GetPrefixFromGroupName(GroupName)
return SpawnPrefix
end
return nil
end
--- Return the prefix of a spawned group.
-- The method will search for a `#`-mark, and will return the text before the `#`-mark. It will return nil of no prefix was found.
-- @param #SPAWN self
-- @param #string SpawnGroupName The name of the spawned group.
-- @return #string The prefix or #nil if nothing was found.
function SPAWN:_GetPrefixFromGroupName(SpawnGroupName)
if SpawnGroupName then
local SpawnPrefix=string.match(SpawnGroupName, ".*#")
if SpawnPrefix then
SpawnPrefix = SpawnPrefix:sub( 1, -2 )
SpawnPrefix = SpawnPrefix:sub(1, -2)
end
return SpawnPrefix
end
@@ -3279,19 +3291,25 @@ end
-- @param Core.Event#EVENTDATA EventData
function SPAWN:_OnDeadOrCrash( EventData )
self:F( self.SpawnTemplatePrefix )
local SpawnGroup = EventData.IniGroup
if SpawnGroup then
local EventPrefix = self:_GetPrefixFromGroup( SpawnGroup )
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
local unit=UNIT:FindByName(EventData.IniUnitName)
if unit then
local EventPrefix = self:_GetPrefixFromGroupName(unit.GroupName)
if EventPrefix then -- EventPrefix can be nil if no # is found, which means, no spawnable group!
self:T( { "Dead event: " .. EventPrefix } )
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
self.AliveUnits = self.AliveUnits - 1
self:T( "Alive Units: " .. self.AliveUnits )
end
if EventPrefix == self.SpawnTemplatePrefix or ( self.SpawnAliasPrefix and EventPrefix == self.SpawnAliasPrefix ) then
self.AliveUnits = self.AliveUnits - 1
self:T( "Alive Units: " .. self.AliveUnits )
end
end
end
end
end
--- Will detect AIR Units taking off... When the event takes place, the spawned Group is registered as airborne...
+23 -12
View File
@@ -46,14 +46,14 @@
-- @field #number InitOffsetAngle Link offset angle in degrees.
-- @field #number InitStaticHeading Heading of the static.
-- @field #string InitStaticLivery Livery for aircraft.
-- @field #string InitStaticShape Shape of teh static.
-- @field #string InitStaticShape Shape of the static.
-- @field #string InitStaticType Type of the static.
-- @field #string InitStaticCategory Categrory of the static.
-- @field #string InitStaticName Name of the static.
-- @field Core.Point#COORDINATE InitStaticCoordinate Coordinate where to spawn the static.
-- @field #boolean InitDead Set static to be dead if true.
-- @field #boolean InitCargo If true, static can act as cargo.
-- @field #number InitCargoMass Mass of cargo in kg.
-- @field #boolean InitStaticDead Set static to be dead if true.
-- @field #boolean InitStaticCargo If true, static can act as cargo.
-- @field #number InitStaticCargoMass Mass of cargo in kg.
-- @extends Core.Base#BASE
@@ -260,7 +260,7 @@ end
-- @param #number Mass Mass of the cargo in kg.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargoMass(Mass)
self.InitCargoMass=Mass
self.InitStaticCargoMass=Mass
return self
end
@@ -269,7 +269,16 @@ end
-- @param #boolean IsCargo If true, this static can act as cargo.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitCargo(IsCargo)
self.InitCargo=IsCargo
self.InitStaticCargo=IsCargo
return self
end
--- Initialize as dead.
-- @param #SPAWNSTATIC self
-- @param #boolean IsCargo If true, this static is dead.
-- @return #SPAWNSTATIC self
function SPAWNSTATIC:InitDead(IsDead)
self.InitStaticDead=IsDead
return self
end
@@ -417,16 +426,16 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
Template.livery_id=self.InitStaticLivery
end
if self.InitDead~=nil then
Template.dead=self.InitDead
if self.InitStaticDead~=nil then
Template.dead=self.InitStaticDead
end
if self.InitCargo~=nil then
Template.canCargo=self.InitCargo
if self.InitStaticCargo~=nil then
Template.canCargo=self.InitStaticCargo
end
if self.InitCargoMass~=nil then
Template.mass=self.InitCargoMass
if self.InitStaticCargoMass~=nil then
Template.mass=self.InitStaticCargoMass
end
if self.InitLinkUnit then
@@ -479,6 +488,8 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID)
-- ED's dirty way to spawn FARPS.
Static=coalition.addGroup(CountryID, -1, TemplateGroup)
else
self:T("Spawning Static")
self:T2({Template=Template})
Static=coalition.addStaticObject(CountryID, Template)
end
@@ -0,0 +1,202 @@
--- **Core** - TEXTANDSOUND (MOOSE gettext) system
--
-- ===
--
-- ## Main Features:
--
-- * A GetText for Moose
-- * Build a set of localized text entries, alongside their sounds and subtitles
-- * Aimed at class developers to offer localizable language support
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
--
-- ===
--
-- ### Author: **applevangelist**
-- ## Date: April 2022
--
-- ===
--
-- @module Core.TextAndSound
-- @image MOOSE.JPG
--- Text and Sound class.
-- @type TEXTANDSOUND
-- @field #string ClassName Name of this class.
-- @field #string version Versioning.
-- @field #string lid LID for log entries.
-- @field #string locale Default locale of this object.
-- @field #table entries Table of entries.
-- @field #string textclass Name of the class the texts belong to.
-- @extends Core.Base#BASE
---
--
-- @field #TEXTANDSOUND
TEXTANDSOUND = {
ClassName = "TEXTANDSOUND",
version = "0.0.1",
lid = "",
locale = "en",
entries = {},
textclass = "",
}
--- Text and Sound entry.
-- @type TEXTANDSOUND.Entry
-- @field #string Classname Name of the class this entry is for.
-- @field #string Locale Locale of this entry, defaults to "en".
-- @field #table Data The list of entries.
--- Text and Sound data
-- @type TEXTANDSOUND.Data
-- @field #string ID ID of this entry for retrieval.
-- @field #string Text Text of this entry.
-- @field #string Soundfile (optional) Soundfile File name of the corresponding sound file.
-- @field #number Soundlength (optional) Length of the sound file in seconds.
-- @field #string Subtitle (optional) Subtitle for the sound file.
--- Instantiate a new object
-- @param #TEXTANDSOUND self
-- @param #string ClassName Name of the class this instance is providing texts for.
-- @param #string Defaultlocale (Optional) Default locale of this instance, defaults to "en".
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:New(ClassName,Defaultlocale)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", self.ClassName, self.version)
self.locale = Defaultlocale or (_SETTINGS:GetLocale() or "en")
self.textclass = ClassName or "none"
self.entries = {}
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = ClassName
initentry.Data = {}
initentry.Locale = self.locale
self.entries[self.locale] = initentry
self:I(self.lid .. "Instantiated.")
self:T({self.entries[self.locale]})
return self
end
--- Add an entry
-- @param #TEXTANDSOUND self
-- @param #string Locale Locale to set for this entry, e.g. "de".
-- @param #string ID Unique(!) ID of this entry under this locale (i.e. use the same ID to get localized text for the entry in another language).
-- @param #string Text Text for this entry.
-- @param #string Soundfile (Optional) Sound file name for this entry.
-- @param #number Soundlength (Optional) Length of the sound file in seconds.
-- @param #string Subtitle (Optional) Subtitle to be used alongside the sound file.
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:AddEntry(Locale,ID,Text,Soundfile,Soundlength,Subtitle)
self:T(self.lid .. "AddEntry")
local locale = Locale or self.locale
local dataentry = {} -- #TEXTANDSOUND.Data
dataentry.ID = ID or "1"
dataentry.Text = Text or "none"
dataentry.Soundfile = Soundfile
dataentry.Soundlength = Soundlength or 0
dataentry.Subtitle = Subtitle
if not self.entries[locale] then
local initentry = {} -- #TEXTANDSOUND.Entry
initentry.Classname = self.textclass -- class name entry
initentry.Data = {} -- data array
initentry.Locale = locale -- default locale
self.entries[locale] = initentry
end
self.entries[locale].Data[ID] = dataentry
self:T({self.entries[locale].Data})
return self
end
--- Get an entry
-- @param #TEXTANDSOUND self
-- @param #string ID The unique ID of the data to be retrieved.
-- @param #string Locale (Optional) The locale of the text to be retrieved - defauls to default locale set with `New()`.
-- @return #string Text Text or nil if not found and no fallback.
-- @return #string Soundfile Filename or nil if not found and no fallback.
-- @return #string Soundlength Length of the sound or 0 if not found and no fallback.
-- @return #string Subtitle Text for subtitle or nil if not found and no fallback.
function TEXTANDSOUND:GetEntry(ID,Locale)
self:T(self.lid .. "GetEntry")
local locale = Locale or self.locale
if not self.entries[locale] then
-- fall back to default "en"
locale = self.locale
end
local Text,Soundfile,Soundlength,Subtitle = nil, nil, 0, nil
if self.entries[locale] then
if self.entries[locale].Data then
local data = self.entries[locale].Data[ID] -- #TEXTANDSOUND.Data
if data then
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
elseif self.entries[self.locale].Data[ID] then
-- no matching entry, try default
local data = self.entries[self.locale].Data[ID]
Text = data.Text
Soundfile = data.Soundfile
Soundlength = data.Soundlength
Subtitle = data.Subtitle
end
end
else
return nil, nil, 0, nil
end
return Text,Soundfile,Soundlength,Subtitle
end
--- Get the default locale of this object
-- @param #TEXTANDSOUND self
-- @return #string locale
function TEXTANDSOUND:GetDefaultLocale()
self:T(self.lid .. "GetDefaultLocale")
return self.locale
end
--- Set default locale of this object
-- @param #TEXTANDSOUND self
-- @param #string locale
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:SetDefaultLocale(locale)
self:T(self.lid .. "SetDefaultLocale")
self.locale = locale or "en"
return self
end
--- Check if a locale exists
-- @param #TEXTANDSOUND self
-- @return #boolean outcome
function TEXTANDSOUND:HasLocale(Locale)
self:T(self.lid .. "HasLocale")
return self.entries[Locale] and true or false
end
--- Flush all entries to the log
-- @param #TEXTANDSOUND self
-- @return #TEXTANDSOUND self
function TEXTANDSOUND:FlushToLog()
self:I(self.lid .. "Flushing entries:")
local text = string.format("Textclass: %s | Default Locale: %s",self.textclass, self.locale)
for _,_entry in pairs(self.entries) do
local entry = _entry -- #TEXTANDSOUND.Entry
local text = string.format("Textclassname: %s | Locale: %s",entry.Classname, entry.Locale)
self:I(text)
for _ID,_data in pairs(entry.Data) do
local data = _data -- #TEXTANDSOUND.Data
local text = string.format("ID: %s\nText: %s\nSoundfile: %s With length: %d\nSubtitle: %s",tostring(_ID), data.Text or "none",data.Soundfile or "none",data.Soundlength or 0,data.Subtitle or "none")
self:I(text)
end
end
return self
end
----------------------------------------------------------------
-- End TextAndSound
----------------------------------------------------------------
+14 -15
View File
@@ -34,8 +34,6 @@
--
-- ===
--
-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg)
--
-- # The TIMER Concept
--
-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases.
@@ -107,19 +105,17 @@ TIMER = {
--- Timer ID.
_TIMERID=0
--- Timer data base.
--_TIMERDB={}
--- TIMER class version.
-- @field #string version
TIMER.version="0.1.1"
TIMER.version="0.1.2"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-- TODO: Write docs.
-- TODO: Randomization.
-- TODO: Pause/unpause.
-- DONE: Write docs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
@@ -156,9 +152,6 @@ function TIMER:New(Function, ...)
-- Log id.
self.lid=string.format("TIMER UID=%d | ", self.uid)
-- Add to DB.
--_TIMERDB[self.uid]=self
return self
end
@@ -219,10 +212,7 @@ function TIMER:Stop(Delay)
-- Not running any more.
self.isrunning=false
-- Remove DB entry.
--_TIMERDB[self.uid]=nil
end
end
@@ -239,6 +229,15 @@ function TIMER:SetMaxFunctionCalls(Nmax)
return self
end
--- Set time interval. Can also be set when the timer is already running and is applied after the next function call.
-- @param #TIMER self
-- @param #number dT Time interval in seconds.
-- @return #TIMER self
function TIMER:SetTimeInterval(dT)
self.dT=dT
return self
end
--- Check if the timer has been started and was not stopped.
-- @param #TIMER self
-- @return #boolean If `true`, the timer is running.
+152 -73
View File
@@ -9,7 +9,7 @@
-- * Create polygon zones.
-- * Create moving zones around a unit.
-- * Create moving zones around a group.
-- * Provide the zone behaviour. Some zones are static, while others are moveable.
-- * Provide the zone behavior. Some zones are static, while others are moveable.
-- * Enquiry if a coordinate is within a zone.
-- * Smoke zones.
-- * Set a zone probability to control zone selection.
@@ -20,10 +20,10 @@
-- * Draw zones (circular and polygon) on the F10 map.
--
--
-- There are essentially two core functions that zones accomodate:
-- There are essentially two core functions that zones accommodate:
--
-- * Test if an object is within the zone boundaries.
-- * Provide the zone behaviour. Some zones are static, while others are moveable.
-- * Provide the zone behavior. Some zones are static, while others are moveable.
--
-- The object classes are using the zone classes to test the zone boundaries, which can take various forms:
--
@@ -59,6 +59,8 @@
-- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability.
-- @field #number DrawID Unique ID of the drawn zone on the F10 map.
-- @field #table Color Table with four entries, e.g. {1, 0, 0, 0.15}. First three are RGB color code. Fourth is the transparency Alpha value.
-- @field #number ZoneID ID of zone. Only zones defined in the ME have an ID!
-- @field #number Surface Type of surface. Only determined at the center of the zone!
-- @extends Core.Fsm#FSM
@@ -108,7 +110,9 @@ ZONE_BASE = {
ZoneName = "",
ZoneProbability = 1,
DrawID=nil,
Color={}
Color={},
ZoneID=nil,
Sureface=nil,
}
@@ -129,7 +133,9 @@ function ZONE_BASE:New( ZoneName )
self:F( ZoneName )
self.ZoneName = ZoneName
--_DATABASE:AddZone(ZoneName,self)
return self
end
@@ -170,6 +176,7 @@ end
-- @param DCS#Vec3 Vec3 The point to test.
-- @return #boolean true if the Vec3 is within the zone.
function ZONE_BASE:IsVec3InZone( Vec3 )
if not Vec3 then return false end
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
end
@@ -288,6 +295,23 @@ function ZONE_BASE:GetCoordinate( Height ) --R2.1
return self.Coordinate
end
--- Get 2D distance to a coordinate.
-- @param #ZONE_BASE self
-- @param Core.Point#COORDINATE Coordinate Reference coordinate. Can also be a DCS#Vec2 or DCS#Vec3 object.
-- @return #number Distance to the reference coordinate in meters.
function ZONE_BASE:Get2DDistance(Coordinate)
local a=self:GetVec2()
local b={}
if Coordinate.z then
b.x=Coordinate.x
b.y=Coordinate.z
else
b.x=Coordinate.x
b.y=Coordinate.y
end
local dist=UTILS.VecDist2D(a,b)
return dist
end
--- Define a random @{DCS#Vec2} within the zone.
-- @param #ZONE_BASE self
@@ -314,15 +338,22 @@ end
-- @param #ZONE_BASE self
-- @return #nil The bounding square.
function ZONE_BASE:GetBoundingSquare()
--return { x1 = 0, y1 = 0, x2 = 0, y2 = 0 }
return nil
end
--- Get surface type of the zone.
-- @param #ZONE_BASE self
-- @return DCS#SurfaceType Type of surface.
function ZONE_BASE:GetSurfaceType()
local coord=self:GetCoordinate()
local surface=coord:GetSurfaceType()
return surface
end
--- Bound the zone boundaries with a tires.
-- @param #ZONE_BASE self
function ZONE_BASE:BoundZone()
self:F2()
end
@@ -601,7 +632,7 @@ function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, Lin
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or Color
FillColor=FillColor or UTILS.DeepCopy(Color)
FillAlpha=FillAlpha or self:GetColorAlpha()
self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly)
@@ -789,7 +820,7 @@ end
--- Scan the zone for the presence of units of the given ObjectCategories.
-- Note that after a zone has been scanned, the zone can be evaluated by:
-- Note that **only after** a zone has been scanned, the zone can be evaluated by:
--
-- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition.
-- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition.
@@ -798,10 +829,10 @@ end
-- * @{ZONE_RADIUS.IsNoneInZone}(): Scan if the zone is empty.
-- @{#ZONE_RADIUS.
-- @param #ZONE_RADIUS self
-- @param ObjectCategories An array of categories of the objects to find in the zone.
-- @param UnitCategories An array of unit categories of the objects to find in the zone.
-- @param ObjectCategories An array of categories of the objects to find in the zone. E.g. `{Object.Category.UNIT}`
-- @param UnitCategories An array of unit categories of the objects to find in the zone. E.g. `{Unit.Category.GROUND_UNIT,Unit.Category.SHIP}`
-- @usage
-- self.Zone:Scan()
-- self.Zone:Scan({Object.Category.UNIT},{Unit.Category.GROUND_UNIT})
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
@@ -1135,7 +1166,9 @@ end
-- @return #boolean true if the location is within the zone.
function ZONE_RADIUS:IsVec2InZone( Vec2 )
self:F2( Vec2 )
if not Vec2 then return false end
local ZoneVec2 = self:GetVec2()
if ZoneVec2 then
@@ -1153,7 +1186,7 @@ end
-- @return #boolean true if the point is within the zone.
function ZONE_RADIUS:IsVec3InZone( Vec3 )
self:F2( Vec3 )
if not Vec3 then return false end
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
@@ -1161,24 +1194,54 @@ end
--- Returns a random Vec2 location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @param #number inner (Optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone. 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 100 times to find the right type!
-- @return DCS#Vec2 The random location within the zone.
function ZONE_RADIUS:GetRandomVec2( inner, outer )
self:F( self.ZoneName, inner, outer )
function ZONE_RADIUS:GetRandomVec2(inner, outer, surfacetypes)
local Point = {}
local Vec2 = self:GetVec2()
local _inner = inner or 0
local _outer = outer or self:GetRadius()
local Vec2 = self:GetVec2()
local _inner = inner or 0
local _outer = outer or self:GetRadius()
local angle = math.random() * math.pi * 2;
Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer);
Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer);
if surfacetypes and type(surfacetypes)~="table" then
surfacetypes={surfacetypes}
end
self:T( { Point } )
local function _getpoint()
local point = {}
local angle = math.random() * math.pi * 2
point.x = Vec2.x + math.cos(angle) * math.random(_inner, _outer)
point.y = Vec2.y + math.sin(angle) * math.random(_inner, _outer)
return point
end
return Point
local function _checkSurface(point)
local stype=land.getSurfaceType(point)
for _,sf in pairs(surfacetypes) do
if sf==stype then
return true
end
end
return false
end
local point=_getpoint()
if surfacetypes then
local N=1 ; local Nmax=100 ; local gotit=false
while gotit==false and N<=Nmax do
gotit=_checkSurface(point)
if gotit then
--env.info(string.format("Got random coordinate with surface type %d after N=%d/%d iterations", land.getSurfaceType(point), N, Nmax))
else
point=_getpoint()
N=N+1
end
end
end
return point
end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
@@ -1230,15 +1293,15 @@ end
--- Returns a @{Core.Point#COORDINATE} object reflecting a random 3D location within the zone.
-- @param #ZONE_RADIUS self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
-- @param #number outer (optional) Maximal distance from the outer edge of the zone. Default is the radius of the zone.
-- @return Core.Point#COORDINATE
function ZONE_RADIUS:GetRandomCoordinate( inner, outer )
self:F( self.ZoneName, inner, outer )
-- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m.
-- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone.
-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
-- @return Core.Point#COORDINATE The random coordinate.
function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes)
local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) )
local vec2=self:GetRandomVec2(inner, outer, surfacetypes)
self:T3( { Coordinate = Coordinate } )
local Coordinate = COORDINATE:NewFromVec2(vec2)
return Coordinate
end
@@ -1301,7 +1364,7 @@ function ZONE:New( ZoneName )
-- Error!
if not Zone then
error( "Zone " .. ZoneName .. " does not exist." )
env.error( "ERROR: Zone " .. ZoneName .. " does not exist!" )
return nil
end
@@ -1811,7 +1874,8 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
Color=Color or self:GetColorRGB()
Alpha=Alpha or 1
FillColor=FillColor or Color
FillColor=FillColor or UTILS.DeepCopy(Color)
FillAlpha=FillAlpha or self:GetColorAlpha()
@@ -1913,7 +1977,7 @@ end
-- @return #boolean true if the location is within the zone.
function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
self:F2( Vec2 )
if not Vec2 then return false end
local Next
local Prev
local InPolygon = false
@@ -1937,30 +2001,46 @@ function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 )
return InPolygon
end
--- Returns if a point is within the zone.
-- @param #ZONE_POLYGON_BASE self
-- @param DCS#Vec3 Vec3 The point to test.
-- @return #boolean true if the point is within the zone.
function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 )
self:F2( Vec3 )
if not Vec3 then return false end
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
end
--- Define a random @{DCS#Vec2} within the zone.
-- @param #ZONE_POLYGON_BASE self
-- @return DCS#Vec2 The Vec2 coordinate.
function ZONE_POLYGON_BASE:GetRandomVec2()
self:F2()
--- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way...
local Vec2Found = false
local Vec2
-- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way...
-- Get the bounding square.
local BS = self:GetBoundingSquare()
self:T2( BS )
local Nmax=1000 ; local n=0
while n<Nmax do
while Vec2Found == false do
Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) }
self:T2( Vec2 )
if self:IsVec2InZone( Vec2 ) then
Vec2Found = true
-- Random point in the bounding square.
local Vec2={x=math.random(BS.x1, BS.x2), y=math.random(BS.y1, BS.y2)}
-- Check if this is in the polygon.
if self:IsVec2InZone(Vec2) then
return Vec2
end
n=n+1
end
self:T2( Vec2 )
return Vec2
self:E("Could not find a random point in the polygon zone!")
return nil
end
--- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone.
@@ -2075,12 +2155,12 @@ end
--
-- ## Declare a ZONE_POLYGON directly in the DCS mission editor!
--
-- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name.
-- You can declare a ZONE_POLYGON using the DCS mission editor by adding the #ZONE_POLYGON tag in the group name.
--
-- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`.
-- So, imagine you have a group declared in the mission editor, with group name `DefenseZone#ZONE_POLYGON`.
-- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration.
-- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group.
-- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag.
-- The ZONE_POLYGON name will be the group name without the #ZONE_POLYGON tag.
--
-- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method.
-- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object
@@ -2152,6 +2232,9 @@ end
do -- ZONE_AIRBASE
--- @type ZONE_AIRBASE
-- @field #boolean isShip If `true`, airbase is a ship.
-- @field #boolean isHelipad If `true`, airbase is a helipad.
-- @field #boolean isAirdrome If `true`, airbase is an airdrome.
-- @extends #ZONE_RADIUS
@@ -2180,6 +2263,20 @@ do -- ZONE_AIRBASE
self._.ZoneAirbase = Airbase
self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2()
if Airbase:IsShip() then
self.isShip=true
self.isHelipad=false
self.isAirdrome=false
elseif Airbase:IsHelipad() then
self.isShip=false
self.isHelipad=true
self.isAirdrome=false
elseif Airbase:IsAirdrome() then
self.isShip=false
self.isHelipad=false
self.isAirdrome=true
end
-- Zone objects are added to the _DATABASE and SET_ZONE objects.
_EVENTDISPATCHER:CreateEventNewZone( self )
@@ -2194,9 +2291,9 @@ do -- ZONE_AIRBASE
return self._.ZoneAirbase
end
--- Returns the current location of the @{Wrapper.Group}.
--- Returns the current location of the AIRBASE.
-- @param #ZONE_AIRBASE self
-- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location.
-- @return DCS#Vec2 The location of the zone based on the AIRBASE location.
function ZONE_AIRBASE:GetVec2()
self:F( self.ZoneName )
@@ -2214,24 +2311,6 @@ do -- ZONE_AIRBASE
return ZoneVec2
end
--- Returns a random location within the zone of the @{Wrapper.Group}.
-- @param #ZONE_AIRBASE self
-- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location.
function ZONE_AIRBASE:GetRandomVec2()
self:F( self.ZoneName )
local Point = {}
local Vec2 = self._.ZoneAirbase:GetVec2()
local angle = math.random() * math.pi*2;
Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius();
Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius();
self:T( { Point } )
return Point
end
--- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone.
-- @param #ZONE_AIRBASE self
-- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0.
+27 -1
View File
@@ -471,6 +471,22 @@ do -- Types
--@field #boolean lateActivated
--@field #boolean uncontrolled
--- DCS template data structure.
-- @type Template
-- @field #boolean uncontrolled Aircraft is uncontrolled.
-- @field #boolean lateActivation Group is late activated.
-- @field #number x 2D Position on x-axis in meters.
-- @field #number y 2D Position on y-axis in meters.
-- @field #table units Unit list.
--
--- Unit data structure.
--@type Template.Unit
--@field #string name Name of the unit.
--@field #number x
--@field #number y
--@field #number alt
end --
@@ -487,8 +503,9 @@ do -- Object
-- @field UNIT
-- @field WEAPON
-- @field STATIC
-- @field SCENERY
-- @field BASE
-- @field SCENERY
-- @field CARGO
--- @type Object.Desc
-- @extends #Desc
@@ -498,6 +515,10 @@ do -- Object
--- @function [parent=#Object] isExist
-- @param #Object self
-- @return #boolean
--- @function [parent=#Object] isActive
-- @param #Object self
-- @return #boolean
--- @function [parent=#Object] destroy
-- @param #Object self
@@ -1130,6 +1151,11 @@ do -- Unit
-- @function [parent=#Unit] getAmmo
-- @param #Unit self
-- @return #Unit.Ammo
--- Returns the number of infantry that can be embark onto the aircraft. Only returns a value if run on airplanes or helicopters. Returns nil if run on ground or ship units.
-- @function [parent=#Unit] getDescentCapacity
-- @param #Unit self
-- @return #number Number of soldiers that embark.
--- Returns the unit sensors.
-- @function [parent=#Unit] getSensors
@@ -0,0 +1,980 @@
--- **Functional** - AI CSAR system
--
-- ## Main Features:
--
-- * Send out helicopters to downed pilots
-- * Rescues players and AI alike
-- * Coalition specific
-- * Starting from a FARP or Airbase
-- * Dedicated MASH zone
-- * Some FSM functions to include in your mission scripts
-- * Limit number of available helos
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/CSR-001%20-%20Basics).
--
-- ===
--
-- ### Author: **applevangelist**
-- Last Update April 2022
--
-- ===
-- @module Functional.AICSAR
-- @image MOOSE.JPG
--- AI CSAR class.
-- @type AICSAR
-- @field #string ClassName Name of this class.
-- @field #string version Versioning.
-- @field #string lid LID for log entries.
-- @field #number coalition Colition side.
-- @field #string template Template for pilot.
-- @field #string helotemplate Template for CSAR helo.
-- @field #string alias Alias Name.
-- @field Wrapper.Airbase#AIRBASE farp FARP object from where to start.
-- @field Core.Zone#ZONE farpzone MASH zone to drop rescued pilots.
-- @field #number maxdistance Max distance to go for a rescue.
-- @field #table pilotqueue Queue of pilots to rescue.
-- @field #number pilotindex Table index to bind pilot to helo.
-- @field #table helos Table of Ops.FlightGroup#FLIGHTGROUP objects
-- @field #boolean verbose Switch more output.
-- @field #number rescuezoneradius Radius around downed pilot for the helo to land in.
-- @field #table rescued Track number of rescued pilot.
-- @field #boolean autoonoff Only send a helo when no human heli pilots are available.
-- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available.
-- @field #boolean limithelos limit available number of helos going on mission (defaults to true)
-- @field #number helonumber number of helos available (default: 3)
-- @extends Core.Fsm#FSM
--- *I once donated a pint of my finest red corpuscles to the great American Red Cross and the doctor opined my blood was very helpful; contained so much alcohol they could use it to sterilize their instruments.*
-- W.C.Fields
--
-- ===
--
-- # AICSAR Concept
--
-- For an AI or human pilot landing with a parachute, a rescue mission will be spawned. The helicopter will fly to the pilot, pick him or her up,
-- and fly back to a designated MASH (medical) zone, drop the pilot and then return to base.
-- Operational maxdistance can be set as well as the landing radius around the downed pilot.
-- Keep in mind that AI helicopters cannot hover-load at the time of writing, so rescue operations over water or in the mountains might not
-- work.
-- Optionally, if you have a CSAR operation with human pilots in your mission, you can set AICSAR to ignore missions when human helicopter
-- pilots are around.
--
-- ## Setup
--
-- Setup is a one-liner:
--
-- -- @param #string Alias Name of this instance.
-- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
-- -- @param #string Pilottemplate Pilot template name.
-- -- @param #string Helotemplate Helicopter template name.
-- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
-- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
-- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH"))
--
-- ## Options are
--
-- my_aicsar.maxdistance -- maximum operational distance in meters. Defaults to 50NM or 92.6km
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true)
-- my_aicsar.helonumber -- number of helos available (default: 3)
--
-- ## Radio options
--
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
--
-- Defaults are:
--
-- AICSAR.Messages = {
-- EN = {
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position
-- PILOTKIA = "Pilot KIA!",
-- HELODOWN = "CSAR Helo Down!",
-- PILOTRESCUED = "Pilot rescued!",
-- PILOTINHELO = "Pilot picked up!",
-- },
-- }
--
-- Correspondingly, sound file names are defined as these defaults:
--
-- AICSAR.RadioMessages = {
-- EN = {
-- INITIALOK = "initialok.ogg",
-- INITIALNOTOK = "initialnotok.ogg",
-- PILOTDOWN = "pilotdown.ogg",
-- PILOTKIA = "pilotkia.ogg",
-- HELODOWN = "helodown.ogg",
-- PILOTRESCUED = "pilotrescued.ogg",
-- PILOTINHELO = "pilotinhelo.ogg",
-- },
-- }
--
-- and these default transmission lengths in seconds:
--
-- AICSAR.RadioLength = {
-- EN = {
-- INITIALOK = 4.1,
-- INITIALNOTOK = 4.6,
-- PILOTDOWN = 2.6,
-- PILOTKIA = 1.1,
-- HELODOWN = 2.1,
-- PILOTRESCUED = 3.5,
-- PILOTINHELO = 2.6,
-- },
-- }
--
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
-- save your sound file inside of the .miz mission file.
--
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
--
-- -- parameters are: locale, ID, text, soundfilename, duration
-- my_aicsar.gettext:AddEntry("de","INITIALOK","Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind, ein Hubschrauber sammelt Sie auf!","okneu.ogg",5.0)
-- my_aicsar.locale = "de" -- plays and shows the defined German language texts and sound. Fallback is "en", if something is undefined.
--
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
--
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002)
--
-- or
--
-- my_aicsar:SetDCSRadio(true,300,radio.modulation.AM,GROUP:FindByName("FARP-Radio"))
--
-- See the function documentation for parameter details.
--
-- ===
---
--
-- @field #AICSAR
AICSAR = {
ClassName = "AICSAR",
version = "0.0.8",
lid = "",
coalition = coalition.side.BLUE,
template = "",
helotemplate = "",
alias = "",
farp = nil,
farpzone = nil,
maxdistance = UTILS.NMToMeters(50),
pilotqueue = {},
pilotindex = 0,
helos = {},
verbose = true,
rescuezoneradius = 200,
rescued = {},
autoonoff = true,
playerset = nil,
Messages = {},
SRS = nil,
SRSRadio = false,
SRSFrequency = 243,
SRSPath = "\\",
SRSModulation = radio.modulation.AM,
SRSSoundPath = nil, -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sount to..." in the ME
SRSPort = 5002,
DCSRadio = false,
DCSFrequency = 243,
DCSModulation = radio.modulation.AM,
DCSRadioGroup = nil,
limithelos = true,
helonumber = 3,
gettext = nil,
locale ="en", -- default text language
}
-- TODO Messages
--- Messages enum
-- @field Messages
AICSAR.Messages = {
EN = {
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
PILOTDOWN = "Pilot down at ",
PILOTKIA = "Pilot KIA!",
HELODOWN = "CSAR Helo Down!",
PILOTRESCUED = "Pilot rescued!",
PILOTINHELO = "Pilot picked up!",
},
DE = {
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
PILOTDOWN = "Pilot abgestürzt: ",
PILOTKIA = "Pilot gefallen!",
HELODOWN = "CSAR Hubschrauber verloren!",
PILOTRESCUED = "Pilot gerettet!",
PILOTINHELO = "Pilot an Bord geholt!",
},
}
-- TODO Radio Messages
--- Radio Messages enum for ogg files
-- @field RadioMessages
AICSAR.RadioMessages = {
EN = {
INITIALOK = "initialok.ogg", -- 4.1 secs
INITIALNOTOK = "initialnotok.ogg", -- 4.6 secs
PILOTDOWN = "pilotdown.ogg", -- 2.6 secs
PILOTKIA = "pilotkia.ogg", -- 1.1 sec
HELODOWN = "helodown.ogg", -- 2.1 secs
PILOTRESCUED = "pilotrescued.ogg", -- 3.5 secs
PILOTINHELO = "pilotinhelo.ogg", -- 2.6 secs
},
}
-- TODO Radio Messages
--- Radio Messages enum for ogg files length in secs
-- @field RadioLength
AICSAR.RadioLength = {
EN = {
INITIALOK = 4.1,
INITIALNOTOK = 4.6,
PILOTDOWN = 2.6,
PILOTKIA = 1.1,
HELODOWN = 2.1,
PILOTRESCUED = 3.5,
PILOTINHELO = 2.6,
},
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function to create a new AICSAR object
-- @param #AICSAR self
-- @param #string Alias Name of this instance.
-- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
-- @param #string Pilottemplate Pilot template name.
-- @param #string Helotemplate Helicopter template name.
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
-- @return #AICSAR self
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New())
--set Coalition
if Coalition and type(Coalition)=="string" then
if Coalition=="blue" then
self.coalition=coalition.side.BLUE
self.coalitiontxt = Coalition
elseif Coalition=="red" then
self.coalition=coalition.side.RED
self.coalitiontxt = Coalition
elseif Coalition=="neutral" then
self.coalition=coalition.side.NEUTRAL
self.coalitiontxt = Coalition
else
self:E("ERROR: Unknown coalition in AICSAR!")
end
else
self.coalition = Coalition
self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition))
end
-- Set alias.
if Alias then
self.alias=tostring(Alias)
else
self.alias="Red Cross"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="IFRC"
elseif self.coalition==coalition.side.BLUE then
self.alias="CSAR"
end
end
end
self.template = Pilottemplate
self.helotemplate = Helotemplate
self.farp = FARP
self.farpzone = MASHZone
self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
-- Radio
self.SRS = nil
self.SRSRadio = false
self.SRSFrequency = 243
self.SRSPath = "\\"
self.SRSModulation = radio.modulation.AM
self.SRSSoundPath = nil -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sound to..." in the ME
self.SRSPort = 5002
-- DCS Radio - add messages via "Sound to..." in the ME
self.DCSRadio = false
self.DCSFrequency = 243
self.DCSModulation = radio.modulation.AM
self.DCSRadioGroup = nil
self.DCSRadioQueue = nil
self.MGRS_Accuracy = 2
-- limit number of available helos at the same time
self.limithelos = true
self.helonumber = 3
-- localization
self:InitLocalization()
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
self:AddTransition("*", "Status", "*") -- CSAR status update.
self:AddTransition("*", "PilotDown", "*") -- Pilot down
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
self:AddTransition("*", "HeloDown", "*") -- Helo dead
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
self:HandleEvent(EVENTS.LandingAfterEjection)
self:__Start(math.random(2,5))
local text = string.format("%sAICSAR Version %s Starting",self.lid,self.version)
self:I(text)
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Status".
-- @function [parent=#AICSAR] Status
-- @param #AICSAR self
--- Triggers the FSM event "Status" after a delay.
-- @function [parent=#AICSAR] __Status
-- @param #AICSAR self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop".
-- @function [parent=#AICSAR] Stop
-- @param #AICSAR self
--- Triggers the FSM event "Stop" after a delay.
-- @function [parent=#AICSAR] __Stop
-- @param #AICSAR self
-- @param #number delay Delay in seconds.
--- On after "PilotDown" event.
-- @function [parent=#AICSAR] OnAfterPilotDown
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
-- @param #boolean InReach True if in maxdistance else false.
--- On after "PilotPickedUp" event.
-- @function [parent=#AICSAR] OnAfterPilotPickedUp
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
-- @param #number Index
--- On after "PilotRescued" event.
-- @function [parent=#AICSAR] OnAfterPilotRescued
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On after "PilotKIA" event.
-- @function [parent=#AICSAR] OnAfterPilotKIA
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- On after "HeloDown" event.
-- @function [parent=#AICSAR] OnAfterHeloDown
-- @param #AICSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param #number Index
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- [Internal] Create the Moose TextAndSoundEntries
-- @param #AICSAR self
-- @return #AICSAR self
function AICSAR:InitLocalization()
self:T(self.lid .. "InitLocalization")
-- English standard localization
self.gettext=TEXTANDSOUND:New(self.ClassName, "en")
self.gettext:AddEntry("en","INITIALOK",AICSAR.Messages.EN.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
self.gettext:AddEntry("en","INITIALNOTOK",AICSAR.Messages.EN.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
self.gettext:AddEntry("en","HELODOWN",AICSAR.Messages.EN.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
self.gettext:AddEntry("en","PILOTDOWN",AICSAR.Messages.EN.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
self.gettext:AddEntry("en","PILOTINHELO",AICSAR.Messages.EN.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
self.gettext:AddEntry("en","PILOTKIA",AICSAR.Messages.EN.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
self.gettext:AddEntry("en","PILOTRESCUED",AICSAR.Messages.EN.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
-- German localization - we keep the sound files English
self.gettext:AddEntry("de","INITIALOK",AICSAR.Messages.DE.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
self.gettext:AddEntry("de","INITIALNOTOK",AICSAR.Messages.DE.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
self.gettext:AddEntry("de","HELODOWN",AICSAR.Messages.DE.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
self.gettext:AddEntry("de","PILOTDOWN",AICSAR.Messages.DE.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
self.gettext:AddEntry("de","PILOTINHELO",AICSAR.Messages.DE.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
self.gettext:AddEntry("de","PILOTKIA",AICSAR.Messages.DE.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
self.locale = "en"
return self
end
--- [User] Switch sound output on and use SRS
-- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false).
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
-- @param #number Frequency Defaults to 243 (guard)
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
-- @param #number Port Port of the SRS, defaults to 5002.
-- @return #AICSAR self
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
self:T(self.lid .. "SetSRSRadio")
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
self.SRSRadio = OnOff and true
self.SRSFrequency = Frequency or 243
self.SRSPath = Path or "c:\\"
self.SRSModulation = Modulation or radio.modulation.AM
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
self.SRSSoundPath = SoundPath or soundpath
self.SRSPort = Port or 5002
if OnOff then
self.SRS = MSRS:New(Path,Frequency,Modulation)
self.SRS:SetPort(self.SRSPort)
end
return self
end
--- [User] Switch sound output on and use normale (DCS) radio
-- @param #AICSAR self
-- @param #boolean OnOff Switch on (true) or off (false).
-- @param #number Frequency Defaults to 243 (guard).
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM.
-- @param Wrapper.Group#GROUP Group The group to use as sending station.
-- @return #AICSAR self
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
self:T(self.lid .. "SetDCSRadio")
self:T(self.lid .. "SetDCSRadio to "..tostring(OnOff))
self.DCSRadio = OnOff and true
self.DCSFrequency = Frequency or 243
self.DCSModulation = Modulation or radio.modulation.AM
self.DCSRadioGroup = Group
if self.DCSRadio then
self.DCSRadioQueue = RADIOQUEUE:New(Frequency,Modulation,"AI-CSAR")
self.DCSRadioQueue:Start(5,5)
self.DCSRadioQueue:SetRadioPower(1000)
self.DCSRadioQueue:SetSenderCoordinate(Group:GetCoordinate())
else
if self.DCSRadioQueue then
self.DCSRadioQueue:Stop()
end
end
return self
end
--- [Internal] Sound output via non-SRS Radio. Add message files (.ogg) via "Sound to..." in the ME.
-- @param #AICSAR self
-- @param #string Soundfile Name of the soundfile
-- @param #number Duration Duration of the sound
-- @param #string Subtitle Text to display
-- @return #AICSAR self
function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
self:T(self.lid .. "DCSRadioBroadcast")
local radioqueue = self.DCSRadioQueue -- Sound.RadioQueue#RADIOQUEUE
radioqueue:NewTransmission(Soundfile,Duration,nil,2,nil,Subtitle,10)
return self
end
--- [Internal] Catch the landing after ejection and spawn a pilot in situ.
-- @param #AICSAR self
-- @param Core.Event#EVENTDATA EventData
-- @return #AICSAR self
function AICSAR:OnEventLandingAfterEjection(EventData)
self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id)
-- autorescue on off?
if self.autoonoff then
if self.playerset:CountAlive() > 0 then
return self
end
end
local _event = EventData -- Core.Event#EVENTDATA
-- get position and spawn in a template pilot
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _country = _event.initiator:getCountry()
local _coalition = coalition.getCountryCoalition( _country )
-- DONE: add distance check
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
-- Mayday Message
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
local text = ""
if _coalition == self.coalition then
if self.verbose then
local setting = {}
setting.MGRS_Accuracy = self.MGRS_Accuracy
local location = _LandingPos:ToStringMGRS(setting)
text = Text .. location .. "!"
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
end
-- further processing
if _coalition == self.coalition and distancetofarp <= self.maxdistance then
-- in reach
self:T(self.lid .. "Spawning new Pilot")
self.pilotindex = self.pilotindex + 1
local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex))
newpilot:InitDelayOff()
newpilot:OnSpawnGroup(
function (grp)
self.pilotqueue[self.pilotindex] = grp
end
)
newpilot:SpawnFromCoordinate(_LandingPos)
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
self:__PilotDown(2,_LandingPos,true)
elseif _coalition == self.coalition and distancetofarp > self.maxdistance then
-- out of reach, apologies, too far off
self:T(self.lid .. "Pilot out of reach")
self:__PilotDown(2,_LandingPos,false)
end
return self
end
--- [Internal] Get FlightGroup
-- @param #AICSAR self
-- @return Ops.FlightGroup#FLIGHTGROUP The FlightGroup
function AICSAR:_GetFlight()
self:T(self.lid .. "_GetFlight")
-- Helo Carrier.
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
:InitDelayOff()
:InitUnControlled(true)
:Spawn()
local nhelo=FLIGHTGROUP:New(newhelo)
nhelo:SetHomebase(self.farp)
nhelo:Activate()
return nhelo
end
--- [Internal] Create a new rescue mission
-- @param #AICSAR self
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
-- @param #number Index Index number of this pilot
-- @return #AICSAR self
function AICSAR:_InitMission(Pilot,Index)
self:T(self.lid .. "_InitMission")
local pickupzone = ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius)
--local pilotset = SET_GROUP:New()
--pilotset:AddGroup(Pilot)
-- Cargo transport assignment.
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
local helo = self:_GetFlight()
-- inject reservation
helo.AICSARReserved = true
-- Cargo transport assignment to first Huey group.
helo:AddOpsTransport(opstransport)
-- callback functions
local function AICPickedUp(Helo,Cargo,Index)
self:__PilotPickedUp(2,Helo,Cargo,Index)
end
local function AICHeloDead(Helo,Index)
self:__HeloDown(2,Helo,Index)
end
function helo:OnAfterLoadingDone(From,Event,To)
AICPickedUp(helo,helo:GetCargoGroups(),Index)
end
function helo:OnAfterDead(From,Event,To)
AICHeloDead(helo,Index)
end
self.helos[Index] = helo
return self
end
--- [Internal] Check if pilot arrived in rescue zone (MASH)
-- @param #AICSAR self
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
-- @return #boolean outcome
function AICSAR:_CheckInMashZone(Pilot)
self:T(self.lid .. "_CheckQueue")
if Pilot:IsInZone(self.farpzone) then
return true
else
return false
end
end
--- [Internal] Check helo queue
-- @param #AICSAR self
-- @return #AICSAR self
function AICSAR:_CheckHelos()
self:T(self.lid .. "_CheckHelos")
for _index,_helo in pairs(self.helos) do
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
if helo and helo.ClassName == "FLIGHTGROUP" then
local state = helo:GetState()
local name = helo:GetName()
self:T("Helo group "..name.." in state "..state)
if state == "Arrived" then
helo:__Stop(5)
self.helos[_index] = nil
end
else
self.helos[_index] = nil
end
end
return self
end
--- [Internal] Count helos queue
-- @param #AICSAR self
-- @return #number Number of helos on mission
function AICSAR:_CountHelos()
self:T(self.lid .. "_CountHelos")
local count = 0
for _index,_helo in pairs(self.helos) do
count = count + 1
end
return count
end
--- [Internal] Check pilot queue for next mission
-- @param #AICSAR self
-- @return #AICSAR self
function AICSAR:_CheckQueue()
self:T(self.lid .. "_CheckQueue")
for _index, _pilot in pairs(self.pilotqueue) do
local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
local name = _pilot.GroupName and _pilot.GroupName or "NONE"
local helocount = self:_CountHelos()
--self:T("Looking at " .. classname .. " " .. name)
-- find one w/o mission
if _pilot and _pilot.ClassName and _pilot.ClassName == "GROUP" then
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
-- rescued?
if self:_CheckInMashZone(_pilot) then
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
_pilot:Destroy(false)
self.pilotqueue[_index] = nil
self.rescued[_index] = true
self:__PilotRescued(2)
if flightgroup then
flightgroup.AICSARReserved = false
end
end -- end rescued
-- has no mission assigned?
if not _pilot.AICSAR then
-- helo available?
if self.limithelos and helocount >= self.helonumber then
-- none free
break
end -- end limit
_pilot.AICSAR = {}
_pilot.AICSAR.Status = "Initiated"
_pilot.AICSAR.Boarded = false
self:_InitMission(_pilot,_index)
break
else
-- update status from OPSGROUP
if flightgroup then
local state = flightgroup:GetState()
_pilot.AICSAR.Status = state
end
--self:T("Flight for " .. _pilot.GroupName .. " in state " .. state)
end -- end has mission
end -- end if pilot
end -- end loop
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- [Internal] onafterStart
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AICSAR self
function AICSAR:onafterStart(From, Event, To)
self:T({From, Event, To})
self:__Status(3)
return self
end
--- [Internal] onafterStatus
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AICSAR self
function AICSAR:onafterStatus(From, Event, To)
self:T({From, Event, To})
self:_CheckQueue()
self:_CheckHelos()
self:__Status(30)
return self
end
--- [Internal] onafterStop
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AICSAR self
function AICSAR:onafterStop(From, Event, To)
self:T({From, Event, To})
self:UnHandleEvent(EVENTS.LandingAfterEjection)
if self.DCSRadioQueue then
self.DCSRadioQueue:Stop()
end
return self
end
--- [Internal] onafterPilotDown
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
-- @param #boolean InReach True if in maxdistance else false.
-- @return #AICSAR self
function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
self:T({From, Event, To})
local CoordinateText = Coordinate:ToStringMGRS()
local inreach = tostring(InReach)
--local text = string.format("Pilot down at %s. In reach = %s",CoordinateText,inreach)
if InReach then
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALOK",self.locale)
--local text = AICSAR.Messages.EN.INITIALOK
self:T(text)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
else
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
--local text = AICSAR.Messages.EN.INITIALNOTOK
self:T(text)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
end
return self
end
--- [Internal] onafterPilotKIA
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AICSAR self
function AICSAR:onafterPilotKIA(From, Event, To)
self:T({From, Event, To})
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTKIA",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
return self
end
--- [Internal] onafterHeloDown
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param #number Index
-- @return #AICSAR self
function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
self:T({From, Event, To})
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("HELODOWN",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
local findex = 0
local fhname = Helo:GetName()
-- find index of Helo
if Index and Index > 0 then
findex=Index
else
for _index, _helo in pairs(self.helos) do
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
local hname = helo:GetName()
if fhname == hname then
findex = _index
break
end
end
end
-- find pilot
if findex > 0 and not self.rescued[findex] then
local pilot = self.pilotqueue[findex]
self.helos[findex] = nil
if pilot.AICSAR.Boarded then
self:T("Helo Down: Found DEAD Pilot ID " .. findex .. " with name " .. pilot:GetName())
-- pilot also dead
self:__PilotKIA(2)
self.pilotqueue[findex] = nil
else
-- initiate new mission
self:T("Helo Down: Found ALIVE Pilot ID " .. findex .. " with name " .. pilot:GetName())
self:_InitMission(pilot,findex)
end
end
return self
end
--- [Internal] onafterPilotRescued
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #AICSAR self
function AICSAR:onafterPilotRescued(From, Event, To)
self:T({From, Event, To})
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
return self
end
--- [Internal] onafterPilotPickedUp
-- @param #AICSAR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
-- @param #number Index
-- @return #AICSAR self
function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
self:T({From, Event, To})
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTINHELO",self.locale)
if self.verbose then
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
end
if self.SRSRadio then
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
sound:SetPlayWithSRS(true)
self.SRS:PlaySoundFile(sound,2)
elseif self.DCSRadio then
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
end
local findex = 0
local fhname = Helo:GetName()
if Index and Index > 0 then
findex = Index
else
-- find index of Helo
for _index, _helo in pairs(self.helos) do
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
local hname = helo:GetName()
if fhname == hname then
findex = _index
break
end
end
end
-- find pilot
if findex > 0 then
local pilot = self.pilotqueue[findex]
self:T("Boarded: Found Pilot ID " .. findex .. " with name " .. pilot:GetName())
pilot.AICSAR.Boarded = true -- mark as boarded
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- END AICSAR
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -59,7 +59,13 @@ function ATC_GROUND:New( Airbases, AirbaseList )
for AirbaseID, Airbase in pairs( self.Airbases ) do
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
-- Specified ZoneBoundary is used if setted or Airbase radius by default
if Airbase.ZoneBoundary then
Airbase.ZoneBoundary = ZONE_POLYGON_BASE:New( "Boundary " .. AirbaseID, Airbase.ZoneBoundary )
else
Airbase.ZoneBoundary = _DATABASE:FindAirbase( AirbaseID ):GetZone()
end
Airbase.ZoneRunways = {}
for PointsRunwayID, PointsRunway in pairs( Airbase.PointsRunways ) do
Airbase.ZoneRunways[PointsRunwayID] = ZONE_POLYGON_BASE:New( "Runway " .. PointsRunwayID, PointsRunway )
@@ -1053,7 +1059,7 @@ end
-- * `AIRBASE.Nevada.Lincoln_County`
-- * `AIRBASE.Nevada.McCarran_International_Airport`
-- * `AIRBASE.Nevada.Mesquite`
-- * `AIRBASE.Nevada.Mina_Airport_3Q0`
-- * `AIRBASE.Nevada.Mina_Airport`
-- * `AIRBASE.Nevada.Nellis_AFB`
-- * `AIRBASE.Nevada.North_Las_Vegas`
-- * `AIRBASE.Nevada.Pahute_Mesa_Airstrip`
@@ -1288,7 +1294,7 @@ ATC_GROUND_NEVADA = {
},
},
},
[AIRBASE.Nevada.Mina_Airport_3Q0] = {
[AIRBASE.Nevada.Mina_Airport] = {
PointsRunways = {
[1] = {
[1] = {["y"]=-290054.57371429,["x"]=-160930.02228572,},
@@ -3263,5 +3269,310 @@ function ATC_GROUND_PERSIANGULF:Start( RepeatScanSeconds )
end
--- @type ATC_GROUND_MARIANAISLANDS
-- @extends #ATC_GROUND
--- # ATC\_GROUND\_MARIANA, extends @{#ATC_GROUND}
--
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
--
-- ---
--
-- ![Banner Image](..\Presentations\ATC_GROUND\Dia1.JPG)
--
-- ---
--
-- The default maximum speed for the airbases at Persian Gulf is **50 km/h**. Warnings are given if this speed limit is trespassed.
-- Players will be immediately kicked when driving faster than **150 km/h** on the taxi way.
--
-- The ATC\_GROUND\_MARIANA class monitors the speed of the airplanes at the airbase during taxi.
-- The pilots may not drive faster than the maximum speed for the airbase, or they will be despawned.
--
-- The pilot will receive 3 times a warning during speeding. After the 3rd warning, if the pilot is still driving
-- faster than the maximum allowed speed, the pilot will be kicked.
--
-- Different airbases have different maximum speeds, according safety regulations.
--
-- # Airbases monitored
--
-- The following airbases are monitored at the Mariana Island region.
-- Use the @{Wrapper.Airbase#AIRBASE.MarianaIslands} enumeration to select the airbases to be monitored.
--
-- * AIRBASE.MarianaIslands.Rota_Intl
-- * AIRBASE.MarianaIslands.Andersen_AFB
-- * AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl
-- * AIRBASE.MarianaIslands.Saipan_Intl
-- * AIRBASE.MarianaIslands.Tinian_Intl
-- * AIRBASE.MarianaIslands.Olf_Orote
--
-- # Installation
--
-- ## In Single Player Missions
--
-- ATC\_GROUND is fully functional in single player.
--
-- ## In Multi Player Missions
--
-- ATC\_GROUND is functional in multi player, however ...
--
-- Due to a bug in DCS since release 1.5, the despawning of clients are not anymore working in multi player.
-- To **work around this problem**, a much better solution has been made, using the **slot blocker** script designed
-- by Ciribob.
--
-- With the help of __Ciribob__, this script has been extended to also kick client players while in flight.
-- ATC\_GROUND is communicating with this modified script to kick players!
--
-- Install the file **SimpleSlotBlockGameGUI.lua** on the server, following the installation instructions described by Ciribob.
--
-- [Simple Slot Blocker from Ciribob & FlightControl](https://github.com/ciribob/DCS-SimpleSlotBlock)
--
-- # Script it!
--
-- ## 1. ATC_GROUND_MARIANAISLANDS Constructor
--
-- Creates a new ATC_GROUND_MARIANAISLANDS object that will monitor pilots taxiing behaviour.
--
-- -- This creates a new ATC_GROUND_MARIANAISLANDS object.
--
-- -- Monitor for these clients the airbases.
-- AirbasePoliceCaucasus = ATC_GROUND_MARIANAISLANDS:New()
--
-- ATC_Ground = ATC_GROUND_MARIANAISLANDS:New(
-- { AIRBASE.MarianaIslands.Andersen_AFB,
-- AIRBASE.MarianaIslands.Saipan_Intl
-- }
-- )
--
--
-- ## 2. Set various options
--
-- There are various methods that you can use to tweak the behaviour of the ATC\_GROUND classes.
--
-- ### 2.1 Speed limit at an airbase.
--
-- * @{#ATC_GROUND.SetKickSpeed}(): Set the speed limit allowed at an airbase in meters per second.
-- * @{#ATC_GROUND.SetKickSpeedKmph}(): Set the speed limit allowed at an airbase in kilometers per hour.
-- * @{#ATC_GROUND.SetKickSpeedMiph}(): Set the speed limit allowed at an airbase in miles per hour.
--
-- ### 2.2 Prevent Takeoff at an airbase. Players will be kicked immediately.
--
-- * @{#ATC_GROUND.SetMaximumKickSpeed}(): Set the maximum speed allowed at an airbase in meters per second.
-- * @{#ATC_GROUND.SetMaximumKickSpeedKmph}(): Set the maximum speed allowed at an airbase in kilometers per hour.
-- * @{#ATC_GROUND.SetMaximumKickSpeedMiph}(): Set the maximum speed allowed at an airbase in miles per hour.
--
---- @field #ATC_GROUND_MARIANAISLANDS
ATC_GROUND_MARIANAISLANDS = {
ClassName = "ATC_GROUND_MARIANAISLANDS",
Airbases = {
[AIRBASE.MarianaIslands.Andersen_AFB] = {
ZoneBoundary = {
[1]={["y"]=16534.138036037,["x"]=11357.42159178,},
[2]={["y"]=16193.406442738,["x"]=12080.012957533,},
[3]={["y"]=13846.966851869,["x"]=12017.348398727,},
[4]={["y"]=13085.815989171,["x"]=11686.317876875,},
[5]={["y"]=13157.991797443,["x"]=11307.826209991,},
[6]={["y"]=12055.725179065,["x"]=10795.955695916,},
[7]={["y"]=12762.455491112,["x"]=8890.9830441032,},
[8]={["y"]=15955.829493693,["x"]=10333.527220132,},
[9]={["y"]=16537.500532414,["x"]=11302.009499603,},
},
PointsRunways = {
[1]={
[1]={["y"]=12586.683049611,["x"]=10224.374497932,},
[2]={["y"]=16191.720475696,["x"]=11791.299100017,},
[3]={["y"]=16126.93956642,["x"]=11938.855615591,},
[4]={["y"]=12520.758127164,["x"]=10385.177131701,},
[5]={["y"]=12584.654720512,["x"]=10227.416991581,},
},
[2]={
[1]={["y"]=12663.030391743,["x"]=9661.9623015306,},
[2]={["y"]=16478.347303358,["x"]=11328.665745976,},
[3]={["y"]=16405.4731048,["x"]=11479.11570429,},
[4]={["y"]=12597.277684174,["x"]=9817.9733769647,},
[5]={["y"]=12661.894752524,["x"]=9674.4462086962,},
},
},
},
[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl] = {
ZoneBoundary = {
[1]={["y"]=2288.5182403943,["x"]=1469.0170841716,},
[2]={["y"]=1126.2025877996,["x"]=1174.37135631,},
[3]={["y"]=-2015.6461924287,["x"]=-484.62000718931,},
[4]={["y"]=-2102.1292389114,["x"]=-988.03393750566,},
[5]={["y"]=476.03853524366,["x"]=-1220.1783269883,},
[6]={["y"]=2059.2220058047,["x"]=78.889693514402,},
[7]={["y"]=1898.1396965104,["x"]=705.67531284795,},
[8]={["y"]=2760.1768681934,["x"]=1026.0681119777,},
[9]={["y"]=2317.2278959994,["x"]=1460.8143254273,},
},
PointsRunways = {
[1]={
[1]={["y"]=-1872.6620108821,["x"]=-924.3572605835,},
[2]={["y"]=1763.4754603305,["x"]=735.35988877983,},
[3]={["y"]=1700.6941677961,["x"]=866.32615476157,},
[4]={["y"]=-1934.0078007732,["x"]=-779.8149298453,},
[5]={["y"]=-1875.0113982627,["x"]=-914.95971106094,},
},
[2]={
[1]={["y"]=-1512.9403660377,["x"]=-1005.5903386188,},
[2]={["y"]=1577.9055714735,["x"]=413.22750176368,},
[3]={["y"]=1523.1182807849,["x"]=543.89726442232,},
[4]={["y"]=-1572.5102998047,["x"]=-867.04004322806,},
[5]={["y"]=-1514.2790162347,["x"]=-1003.5823633233,},
},
},
},
[AIRBASE.MarianaIslands.Rota_Intl] = {
ZoneBoundary = {
[1]={["y"]=47237.615412849,["x"]=76048.890408862,},
[2]={["y"]=49938.030053628,["x"]=75921.721582932,},
[3]={["y"]=49931.24873272,["x"]=75735.184004851,},
[4]={["y"]=49295.999227075,["x"]=75754.716414519,},
[5]={["y"]=49286.963307515,["x"]=75510.037806569,},
[6]={["y"]=48774.280745707,["x"]=75513.331990155,},
[7]={["y"]=48785.021396773,["x"]=75795.691662161,},
[8]={["y"]=47232.749278491,["x"]=75839.239059146,},
[9]={["y"]=47236.687866223,["x"]=76042.706764692,},
},
PointsRunways = {
[1]={
[1]={["y"]=49741.295228062,["x"]=75901.50955922,},
[2]={["y"]=49739.033213305,["x"]=75768.333440425,},
[3]={["y"]=47448.460520408,["x"]=75857.400271466,},
[4]={["y"]=47452.270177742,["x"]=75999.965448133,},
[5]={["y"]=49738.502011054,["x"]=75905.338915708,},
},
},
},
[AIRBASE.MarianaIslands.Saipan_Intl] = {
ZoneBoundary = {
[1]={["y"]=100489.08491445,["x"]=179799.05158855,},
[2]={["y"]=100869.73415313,["x"]=179948.98719903,},
[3]={["y"]=101364.78967515,["x"]=180831.98517043,},
[4]={["y"]=101563.85713359,["x"]=180885.21496237,},
[5]={["y"]=101733.92591034,["x"]=180457.73296886,},
[6]={["y"]=103340.30228775,["x"]=180990.08362622,},
[7]={["y"]=103459.55080438,["x"]=180453.77747027,},
[8]={["y"]=100406.63048095,["x"]=179266.60983762,},
[9]={["y"]=100225.55027532,["x"]=179423.9380961,},
[10]={["y"]=100477.48558937,["x"]=179791.9827288,},
},
PointsRunways = {
[1]={
[1]={["y"]=103170.38882002,["x"]=180654.56630524,},
[2]={["y"]=103235.37868835,["x"]=180497.25368418,},
[3]={["y"]=100564.72969504,["x"]=179435.41443498,},
[4]={["y"]=100509.30718722,["x"]=179584.65394733,},
[5]={["y"]=103163.53918905,["x"]=180651.82645285,},
},
[2]={
[1]={["y"]=103048.83223261,["x"]=180819.94107128,},
[2]={["y"]=103087.60579257,["x"]=180720.06315265,},
[3]={["y"]=101037.52694966,["x"]=179899.50061624,},
[4]={["y"]=100994.61708907,["x"]=180009.33151758,},
[5]={["y"]=103043.26643227,["x"]=180820.40488798,},
},
},
},
[AIRBASE.MarianaIslands.Tinian_Intl] = {
ZoneBoundary = {
[1]={["y"]=88393.477575413,["x"]=166704.16076438,},
[2]={["y"]=91581.732441809,["x"]=167402.54409276,},
[3]={["y"]=91533.451647402,["x"]=166826.23670062,},
[4]={["y"]=90827.604136952,["x"]=166699.75590414,},
[5]={["y"]=90894.853975623,["x"]=166375.37836304,},
[6]={["y"]=89995.027922869,["x"]=166224.92495935,},
[7]={["y"]=88937.62899352,["x"]=166244.48573911,},
[8]={["y"]=88408.916178231,["x"]=166480.39896864,},
[9]={["y"]=88387.745481732,["x"]=166685.82715656,},
},
PointsRunways = {
[1]={
[1]={["y"]=91329.480937912,["x"]=167204.44064529,},
[2]={["y"]=91363.95475433,["x"]=167038.15603429,},
[3]={["y"]=88585.849307337,["x"]=166520.3807647,},
[4]={["y"]=88554.422227212,["x"]=166686.49505251,},
[5]={["y"]=91318.8152578,["x"]=167203.31794212,},
},
},
},
},
}
--- Creates a new ATC_GROUND_MARIANAISLANDS object.
-- @param #ATC_GROUND_MARIANAISLANDS self
-- @param AirbaseNames A list {} of airbase names (Use AIRBASE.MarianaIslands enumerator).
-- @return #ATC_GROUND_MARIANAISLANDS self
function ATC_GROUND_MARIANAISLANDS:New( AirbaseNames )
-- Inherits from BASE
local self = BASE:Inherit( self, ATC_GROUND:New( self.Airbases, AirbaseNames ) )
self:SetKickSpeedKmph( 50 )
self:SetMaximumKickSpeedKmph( 150 )
-- -- Andersen
-- local AndersenBoundary = GROUP:FindByName( "Andersen Boundary" )
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneBoundary = ZONE_POLYGON:New( "Andersen Boundary", AndersenBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
--
-- local AndersenRunway1 = GROUP:FindByName( "Andersen Runway 1" )
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[1] = ZONE_POLYGON:New( "Andersen Runway 1", AndersenRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
-- local AndersenRunway2 = GROUP:FindByName( "Andersen Runway 2" )
-- self.Airbases[AIRBASE.MarianaIslands.Andersen_AFB].ZoneRunways[2] = ZONE_POLYGON:New( "Andersen Runway 2", AndersenRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
--
-- -- Antonio_B_Won_Pat_International_Airport
-- local AntonioBoundary = GROUP:FindByName( "Antonio Boundary" )
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneBoundary = ZONE_POLYGON:New( "Antonio Boundary", AntonioBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
--
-- local AntonioRunway1 = GROUP:FindByName( "Antonio Runway 1" )
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Antonio Runway 1", AntonioRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
-- local AntonioRunway2 = GROUP:FindByName( "Antonio Runway 2" )
-- self.Airbases[AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Antonio Runway 2", AntonioRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
--
-- -- Rota_International_Airport
-- local RotaBoundary = GROUP:FindByName( "Rota Boundary" )
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneBoundary = ZONE_POLYGON:New( "Rota Boundary", RotaBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
--
-- local RotaRunway1 = GROUP:FindByName( "Rota Runway 1" )
-- self.Airbases[AIRBASE.MarianaIslands.Rota_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Rota Runway 1", RotaRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
--
-- -- Saipan_International_Airport
-- local SaipanBoundary = GROUP:FindByName( "Saipan Boundary" )
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneBoundary = ZONE_POLYGON:New( "Saipan Boundary", SaipanBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
--
-- local SaipanRunway1 = GROUP:FindByName( "Saipan Runway 1" )
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Saipan Runway 1", SaipanRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
-- local SaipanRunway2 = GROUP:FindByName( "Saipan Runway 2" )
-- self.Airbases[AIRBASE.MarianaIslands.Saipan_Intl].ZoneRunways[2] = ZONE_POLYGON:New( "Saipan Runway 2", SaipanRunway2 ):SmokeZone(SMOKECOLOR.Red):Flush()
--
--
-- -- Tinian_International_Airport
-- local TinianBoundary = GROUP:FindByName( "Tinian Boundary" )
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneBoundary = ZONE_POLYGON:New( "Tinian Boundary", TinianBoundary ):SmokeZone(SMOKECOLOR.White):Flush()
--
-- local TinianRunway1 = GROUP:FindByName( "Tinian Runway 1" )
-- self.Airbases[AIRBASE.MarianaIslands.Tinian_Intl].ZoneRunways[1] = ZONE_POLYGON:New( "Tinian Runway 1", TinianRunway1 ):SmokeZone(SMOKECOLOR.Red):Flush()
return self
end
--- Start SCHEDULER for ATC_GROUND_MARIANAISLANDS object.
-- @param #ATC_GROUND_MARIANAISLANDS self
-- @param RepeatScanSeconds Time in second for defining occurency of alerts.
-- @return nothing
function ATC_GROUND_MARIANAISLANDS:Start( RepeatScanSeconds )
RepeatScanSeconds = RepeatScanSeconds or 0.05
self.AirbaseMonitor = SCHEDULER:New( self, self._AirbaseMonitor, { self }, 0, 2, RepeatScanSeconds )
end
@@ -0,0 +1,961 @@
--- **Functional** - Autolase targets in the field.
--
-- ===
--
-- **AUOTLASE** - Autolase targets in the field.
--
-- ===
--
-- ## Missions:
--
-- ### [Autolase](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/)
--
-- ===
--
-- **Main Features:**
--
-- * Detect and lase contacts automatically
-- * Targets are lased by threat priority order
-- * Use FSM events to link functionality into your scripts
-- * Easy setup
--
-- ===
--
--- Spot on!
--
-- ===
--
-- # 1 Autolase concept
--
-- * Detect and lase contacts automatically
-- * Targets are lased by threat priority order
-- * Use FSM events to link functionality into your scripts
-- * Set laser codes and smoke colors per Recce unit
-- * Easy set-up
--
-- # 2 Basic usage
--
-- ## 2.2 Set up a group of Recce Units:
--
-- local FoxSet = SET_GROUP:New():FilterPrefixes("Recce"):FilterCoalitions("blue"):FilterStart()
--
-- ## 2.3 (Optional) Set up a group of pilots, this will drive who sees the F10 menu entry:
--
-- local Pilotset = SET_CLIENT:New():FilterCoalitions("blue"):FilterActive(true):FilterStart()
--
-- ## 2.4 Set up and start Autolase:
--
-- local autolaser = AUTOLASE:New(FoxSet,coalition.side.BLUE,"Wolfpack",Pilotset)
--
-- ## 2.5 Example - Using a fixed laser code and color for a specific Recce unit:
--
-- local recce = SPAWN:New("Reaper")
-- :InitDelayOff()
-- :OnSpawnGroup(
-- function (group)
-- local unit = group:GetUnit(1)
-- local name = unit:GetName()
-- autolaser:SetRecceLaserCode(name,1688)
-- autolaser:SetRecceSmokeColor(name,SMOKECOLOR.Red)
-- end
-- )
-- :InitCleanUp(60)
-- :InitLimit(1,0)
-- :SpawnScheduled(30,0.5)
--
-- ## 2.6 Example - Inform pilots about events:
--
-- autolaser:SetNotifyPilots(true) -- defaults to true, also shown if debug == true
-- -- Note - message are shown to pilots in the #SET_CLIENT only if using the pilotset option, else to the coalition.
--
--
-- ### Author: **applevangelist**
-- @module Functional.Autolase
-- @image Designation.JPG
--
-- Date: 24 Oct 2021
--
--- Class AUTOLASE
-- @type AUTOLASE
-- @field #string ClassName
-- @field #string lid
-- @field #number verbose
-- @field #string alias
-- @field #boolean debug
-- @field #string version
-- @extends Ops.Intel#INTEL
---
-- @field #AUTOLASE
AUTOLASE = {
ClassName = "AUTOLASE",
lid = "",
verbose = 0,
alias = "",
debug = false,
}
--- Laser spot info
-- @type AUTOLASE.LaserSpot
-- @field Core.Spot#SPOT laserspot
-- @field Wrapper.Unit#UNIT lasedunit
-- @field Wrapper.Unit#UNIT lasingunit
-- @field #number lasercode
-- @field #string location
-- @field #number timestamp
-- @field #string unitname
-- @field #string reccename
-- @field #string unittype
--- AUTOLASE class version.
-- @field #string version
AUTOLASE.version = "0.0.11"
-------------------------------------------------------------------
-- Begin Functional.Autolase.lua
-------------------------------------------------------------------
--- Constructor for a new Autolase instance.
-- @param #AUTOLASE self
-- @param Core.Set#SET_GROUP RecceSet Set of detecting and lasing units
-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral".
-- @param #string Alias (Optional) An alias how this object is called in the logs etc.
-- @param Core.Set#SET_CLIENT PilotSet (Optional) Set of clients for precision bombing, steering menu creation. Leave nil for a coalition-wide F10 entry and display.
-- @return #AUTOLASE self
function AUTOLASE:New(RecceSet, Coalition, Alias, PilotSet)
BASE:T({RecceSet, Coalition, Alias, PilotSet})
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #AUTOLASE
if Coalition and type(Coalition)=="string" then
if Coalition=="blue" then
self.coalition=coalition.side.BLUE
elseif Coalition=="red" then
self.coalition=coalition.side.RED
elseif Coalition=="neutral" then
self.coalition=coalition.side.NEUTRAL
else
self:E("ERROR: Unknown coalition in AUTOLASE!")
end
end
-- Set alias.
if Alias then
self.alias=tostring(Alias)
else
self.alias="Lion"
if self.coalition then
if self.coalition==coalition.side.RED then
self.alias="Wolf"
elseif self.coalition==coalition.side.BLUE then
self.alias="Fox"
end
end
end
-- inherit from INTEL
local self=BASE:Inherit(self, INTEL:New(RecceSet, Coalition, Alias)) -- #AUTOLASE
self.RecceSet = RecceSet
self.DetectVisual = true
self.DetectOptical = true
self.DetectRadar = true
self.DetectIRST = true
self.DetectRWR = true
self.DetectDLINK = true
self.LaserCodes = UTILS.GenerateLaserCodes()
self.LaseDistance = 5000
self.LaseDuration = 300
self.GroupsByThreat = {}
self.UnitsByThreat = {}
self.RecceNames = {}
self.RecceLaserCode = {}
self.RecceSmokeColor = {}
self.RecceUnitNames= {}
self.maxlasing = 4
self.CurrentLasing = {}
self.lasingindex = 0
self.deadunitnotes = {}
self.usepilotset = false
self.reporttimeshort = 10
self.reporttimelong = 30
self.smoketargets = false
self.smokecolor = SMOKECOLOR.Red
self.notifypilots = true
self.targetsperrecce = {}
self.RecceUnits = {}
self.forcecooldown = true
self.cooldowntime = 60
self.useSRS = false
self.SRSPath = ""
self.SRSFreq = 251
self.SRSMod = radio.modulation.AM
-- Set some string id for output to DCS.log file.
self.lid=string.format("AUTOLASE %s (%s) | ", self.alias, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "Monitor", "*") -- Start FSM
self:AddTransition("*", "Lasing", "*") -- Lasing target
self:AddTransition("*", "TargetLost", "*") -- Lost target
self:AddTransition("*", "TargetDestroyed", "*") -- Target destroyed
self:AddTransition("*", "RecceKIA", "*") -- Recce KIA
self:AddTransition("*", "LaserTimeout", "*") -- Laser timed out
self:AddTransition("*", "Cancel", "*") -- Stop Autolase
-- Menu Entry
if not PilotSet then
self.Menu = MENU_COALITION_COMMAND:New(self.coalition,"Autolase",nil,self.ShowStatus,self)
else
self.usepilotset = true
self.pilotset = PilotSet
self:HandleEvent(EVENTS.PlayerEnterAircraft)
self:SetPilotMenu()
end
self:SetClusterAnalysis(false, false)
self:__Start(2)
self:__Monitor(math.random(5,10))
return self
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Monitor".
-- @function [parent=#AUTOLASE] Status
-- @param #AUTOLASE self
--- Triggers the FSM event "Monitor" after a delay.
-- @function [parent=#AUTOLASE] __Status
-- @param #AUTOLASE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Cancel".
-- @function [parent=#AUTOLASE] Cancel
-- @param #AUTOLASE self
--- Triggers the FSM event "Cancel" after a delay.
-- @function [parent=#AUTOLASE] __Cancel
-- @param #AUTOLASE self
-- @param #number delay Delay in seconds.
--- On After "RecceKIA" event.
-- @function [parent=#AUTOLASE] OnAfterRecceKIA
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string RecceName The lost Recce
--- On After "TargetDestroyed" event.
-- @function [parent=#AUTOLASE] OnAfterTargetDestroyed
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The destroyed unit\'s name
-- @param #string RecceName The Recce name lasing
--- On After "TargetLost" event.
-- @function [parent=#AUTOLASE] OnAfterTargetLost
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The lost unit\'s name
-- @param #string RecceName The Recce name lasing
--- On After "LaserTimeout" event.
-- @function [parent=#AUTOLASE] OnAfterLaserTimeout
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The lost unit\'s name
-- @param #string RecceName The Recce name lasing
--- On After "Lasing" event.
-- @function [parent=#AUTOLASE] OnAfterLasing
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
end
-------------------------------------------------------------------
-- Helper Functions
-------------------------------------------------------------------
--- (Internal) Function to set pilot menu.
-- @param #AUTOLASE self
-- @return #AUTOLASE self
function AUTOLASE:SetPilotMenu()
if self.usepilotset then
local pilottable = self.pilotset:GetSetObjects() or {}
for _,_unit in pairs (pilottable) do
local Unit = _unit -- Wrapper.Unit#UNIT
if Unit and Unit:IsAlive() then
local Group = Unit:GetGroup()
local lasemenu = MENU_GROUP_COMMAND:New(Group,"Autolase Status",nil,self.ShowStatus,self,Group)
lasemenu:Refresh()
end
end
end
return self
end
--- (Internal) Event function for new pilots.
-- @param #AUTOLASE self
-- @param Core.Event#EVENTDATA EventData
-- @return #AUTOLASE self
function AUTOLASE:OnEventPlayerEnterAircraft(EventData)
self:SetPilotMenu()
return self
end
--- (Internal) Function to get a laser code by recce name
-- @param #AUTOLASE self
-- @param #string RecceName Unit(!) name of the Recce
-- @return #AUTOLASE self
function AUTOLASE:GetLaserCode(RecceName)
local code = 1688
if self.RecceLaserCode[RecceName] == nil then
code = self.LaserCodes[math.random(#self.LaserCodes)]
self.RecceLaserCode[RecceName] = code
else
code = self.RecceLaserCode[RecceName]
end
return code
end
--- (Internal) Function to get a smoke color by recce name
-- @param #AUTOLASE self
-- @param #string RecceName Unit(!) name of the Recce
-- @return #AUTOLASE self
function AUTOLASE:GetSmokeColor(RecceName)
local color = self.smokecolor
if self.RecceSmokeColor[RecceName] == nil then
self.RecceSmokeColor[RecceName] = color
else
color = self.RecceSmokeColor[RecceName]
end
return color
end
--- (User) Function enable sending messages via SRS.
-- @param #AUTOLASE self
-- @param #boolean OnOff Switch usage on and off
-- @param #string Path Path to SRS directory, e.g. C:\\Program Files\\DCS-SimpleRadio-Standalon
-- @param #number Frequency Frequency to send, e.g. 243
-- @param #number Modulation Modulation i.e. radio.modulation.AM or radio.modulation.FM
-- @return #AUTOLASE self
function AUTOLASE:SetUsingSRS(OnOff,Path,Frequency,Modulation)
self.useSRS = OnOff or true
self.SRSPath = Path or "E:\\Program Files\\DCS-SimpleRadio-Standalone"
self.SRSFreq = Frequency or 271
self.SRSMod = Modulation or radio.modulation.AM
return self
end
--- (User) Function set max lasing targets
-- @param #AUTOLASE self
-- @param #number Number Max number of targets to lase at once
-- @return #AUTOLASE self
function AUTOLASE:SetMaxLasingTargets(Number)
self.maxlasing = Number or 4
return self
end
--- (Internal) Function set notify pilots on events
-- @param #AUTOLASE self
-- @param #boolean OnOff Switch messaging on (true) or off (false)
-- @return #AUTOLASE self
function AUTOLASE:SetNotifyPilots(OnOff)
self.notifypilots = OnOff and true
return self
end
--- (User) Function to set a specific code to a Recce.
-- @param #AUTOLASE self
-- @param #string RecceName (Unit!) Name of the Recce
-- @param #number Code The lase code
-- @return #AUTOLASE self
function AUTOLASE:SetRecceLaserCode(RecceName, Code)
local code = Code or 1688
self.RecceLaserCode[RecceName] = code
return self
end
--- (User) Function to set a specific smoke color for a Recce.
-- @param #AUTOLASE self
-- @param #string RecceName (Unit!) Name of the Recce
-- @param #number Color The color, e.g. SMOKECOLOR.Red, SMOKECOLOR.Green etc
-- @return #AUTOLASE self
function AUTOLASE:SetRecceSmokeColor(RecceName, Color)
local color = Color or self.smokecolor
self.RecceSmokeColor[RecceName] = color
return self
end
--- (User) Function to force laser cooldown and cool down time
-- @param #AUTOLASE self
-- @param #boolean OnOff Switch cool down on (true) or off (false) - defaults to true
-- @param #number Seconds Number of seconds for cooldown - dafaults to 60 seconds
-- @return #AUTOLASE self
function AUTOLASE:SetLaserCoolDown(OnOff, Seconds)
self.forcecooldown = OnOff and true
self.cooldowntime = Seconds or 60
return self
end
--- (User) Function to set message show times.
-- @param #AUTOLASE self
-- @param #number long Longer show time
-- @param #number short Shorter show time
-- @return #AUTOLASE self
function AUTOLASE:SetReportingTimes(long, short)
self.reporttimeshort = short or 10
self.reporttimelong = long or 30
return self
end
--- (User) Function to set lasing distance in meters and duration in seconds
-- @param #AUTOLASE self
-- @param #number Distance (Max) distance for lasing in meters - default 5000 meters
-- @param #number Duration (Max) duration for lasing in seconds - default 300 secs
-- @return #AUTOLASE self
function AUTOLASE:SetLasingParameters(Distance, Duration)
self.LaseDistance = Distance or 5000
self.LaseDuration = Duration or 300
return self
end
--- (User) Function to set smoking of targets.
-- @param #AUTOLASE self
-- @param #boolean OnOff Switch smoking on or off
-- @param #number Color Smokecolor, e.g. SMOKECOLOR.Red
-- @return #AUTOLASE self
function AUTOLASE:SetSmokeTargets(OnOff,Color)
self.smoketargets = OnOff
self.smokecolor = Color or SMOKECOLOR.Red
return self
end
--- (Internal) Function to calculate line of sight.
-- @param #AUTOLASE self
-- @param Wrapper.Unit#UNIT Unit
-- @return #number LOS Line of sight in meters
function AUTOLASE:GetLosFromUnit(Unit)
local lasedistance = self.LaseDistance
local unitheight = Unit:GetHeight()
local coord = Unit:GetCoordinate()
local landheight = coord:GetLandHeight()
local asl = unitheight - landheight
if asl > 100 then
local absquare = lasedistance^2+asl^2
lasedistance = math.sqrt(absquare)
end
return lasedistance
end
--- (Internal) Function to check on lased targets.
-- @param #AUTOLASE self
-- @return #AUTOLASE self
function AUTOLASE:CleanCurrentLasing()
local lasingtable = self.CurrentLasing
local newtable = {}
local newreccecount = {}
local lasing = 0
for _ind,_entry in pairs(lasingtable) do
local entry = _entry -- #AUTOLASE.LaserSpot
if not newreccecount[entry.reccename] then
newreccecount[entry.reccename] = 0
end
end
for _,_recce in pairs (self.RecceSet:GetSetObjects()) do
local recce = _recce --Wrapper.Group#GROUP
if recce and recce:IsAlive() then
local unit = recce:GetUnit(1)
local name = unit:GetName()
if not self.RecceUnits[name] then
self.RecceUnits[name] = { name=name, unit=unit, cooldown = false, timestamp = timer.getAbsTime() }
end
end
end
for _ind,_entry in pairs(lasingtable) do
local entry = _entry -- #AUTOLASE.LaserSpot
local valid = 0
local reccedead = false
local unitdead = false
local lostsight = false
local timeout = false
local Tnow = timer.getAbsTime()
-- check recce dead
local recce = entry.lasingunit
if recce and recce:IsAlive() then
valid = valid + 1
else
reccedead = true
self:__RecceKIA(2,entry.reccename)
end
-- check entry dead
local unit = entry.lasedunit
if unit and unit:IsAlive() == true then
valid = valid + 1
else
unitdead = true
if not self.deadunitnotes[entry.unitname] then
self.deadunitnotes[entry.unitname] = true
self:__TargetDestroyed(2,entry.unitname,entry.reccename)
end
end
-- check entry out of sight
if not reccedead and not unitdead then
if self:CanLase(recce,unit) then
valid = valid + 1
else
lostsight = true
entry.laserspot:LaseOff()
self:__TargetLost(2,entry.unitname,entry.reccename)
end
end
-- check timed out
local timestamp = entry.timestamp
if Tnow - timestamp < self.LaseDuration and not lostsight then
valid = valid + 1
else
timeout = true
entry.laserspot:LaseOff()
self.RecceUnits[entry.reccename].cooldown = true
self.RecceUnits[entry.reccename].timestamp = timer.getAbsTime()
if not lostsight then
self:__LaserTimeout(2,entry.unitname,entry.reccename)
end
end
if valid == 4 then
self.lasingindex = self.lasingindex + 1
newtable[self.lasingindex] = entry
newreccecount[entry.reccename] = newreccecount[entry.reccename] + 1
lasing = lasing + 1
end
end
self.CurrentLasing = newtable
self.targetsperrecce = newreccecount
return lasing
end
--- (Internal) Function to show status.
-- @param #AUTOLASE self
-- @param Wrapper.Group#GROUP Group (Optional) show to a certain group
-- @return #AUTOLASE self
function AUTOLASE:ShowStatus(Group)
local report = REPORT:New("Autolase")
local reccetable = self.RecceSet:GetSetObjects()
for _,_recce in pairs(reccetable) do
if _recce and _recce:IsAlive() then
local unit = _recce:GetUnit(1)
local name = unit:GetName()
local code = self:GetLaserCode(name)
report:Add(string.format("Recce %s has code %d",name,code))
end
end
local lines = 0
for _ind,_entry in pairs(self.CurrentLasing) do
local entry = _entry -- #AUTOLASE.LaserSpot
local reccename = entry.reccename
local typename = entry.unittype
local code = entry.lasercode
local locationstring = entry.location
local text = string.format("%s lasing %s code %d\nat %s",reccename,typename,code,locationstring)
report:Add(text)
lines = lines + 1
end
if lines == 0 then
report:Add("No targets!")
end
local reporttime = self.reporttimelong
if lines == 0 then reporttime = self.reporttimeshort end
if Group and Group:IsAlive() then
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToGroup(Group)
else
local m = MESSAGE:New(report:Text(),reporttime,"Info"):ToCoalition(self.coalition)
end
return self
end
--- (Internal) Function to show messages.
-- @param #AUTOLASE self
-- @param #string Message The message to be sent
-- @param #number Duration Duration in seconds
-- @return #AUTOLASE self
function AUTOLASE:NotifyPilots(Message,Duration)
if self.usepilotset then
local pilotset = self.pilotset:GetSetObjects() --#table
for _,_pilot in pairs(pilotset) do
local pilot = _pilot -- Wrapper.Unit#UNIT
if pilot and pilot:IsAlive() then
local Group = pilot:GetGroup()
local m = MESSAGE:New(Message,Duration,"Autolase"):ToGroup(Group)
end
end
elseif not self.debug then
local m = MESSAGE:New(Message,Duration,"Autolase"):ToCoalition(self.coalition)
else
local m = MESSAGE:New(Message,Duration,"Autolase"):ToAll()
end
if self.debug then self:I(Message) end
return self
end
--- (User) Send messages via SRS.
-- @param #AUTOLASE self
-- @param #string Message The (short!) message to be sent, e.g. "Lasing target!"
-- @return #AUTOLASE self
-- @usage Step 1 - set up the radio basics **once** with
-- my_autolase:SetUsingSRS(true,"C:\\path\\SRS-Folder",251,radio.modulation.AM)
-- Step 2 - send a message, e.g.
-- function my_autolase:OnAfterLasing(From, Event, To, LaserSpot)
-- my_autolase:NotifyPilotsWithSRS("Reaper lasing new target!")
-- end
function AUTOLASE:NotifyPilotsWithSRS(Message)
if self.useSRS then
-- Create a SOUNDTEXT object.
if self.debug then
BASE:TraceOn()
BASE:TraceClass("SOUNDTEXT")
BASE:TraceClass("MSRS")
end
local path = self.SRSPath or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
local freq = self.SRSFreq or 271
local mod = self.SRSMod or radio.modulation.AM
local text=SOUNDTEXT:New(Message)
-- MOOSE SRS
local msrs=MSRS:New(path, freq, mod)
-- Text-to speech with default voice after 2 seconds.
msrs:PlaySoundText(text, 2)
end
if self.debug then self:I(Message) end
return self
end
--- (Internal) Function to check if a unit is already lased.
-- @param #AUTOLASE self
-- @param #string unitname Name of the unit to check
-- @return #boolean outcome True or false
function AUTOLASE:CheckIsLased(unitname)
local outcome = false
for _,_laserspot in pairs(self.CurrentLasing) do
local spot = _laserspot -- #AUTOLASE.LaserSpot
if spot.unitname == unitname then
outcome = true
break
end
end
return outcome
end
--- (Internal) Function to check if a unit can be lased.
-- @param #AUTOLASE self
-- @param Wrapper.Unit#UNIT Recce The Recce #UNIT
-- @param Wrapper.Unit#UNIT Unit The lased #UNIT
-- @return #boolean outcome True or false
function AUTOLASE:CanLase(Recce,Unit)
local canlase = false
-- cooldown?
if Recce and Recce:IsAlive() == true then
local name = Recce:GetName()
local cooldown = self.RecceUnits[name].cooldown and self.forcecooldown
if cooldown then
local Tdiff = timer.getAbsTime() - self.RecceUnits[name].timestamp
if Tdiff < self.cooldowntime then
return false
else
self.RecceUnits[name].cooldown = false
end
end
-- calculate LOS
local reccecoord = Recce:GetCoordinate()
local unitcoord = Unit:GetCoordinate()
local islos = reccecoord:IsLOS(unitcoord,2.5)
-- calculate distance
local distance = math.floor(reccecoord:Get3DDistance(unitcoord))
local lasedistance = self:GetLosFromUnit(Recce)
if distance <= lasedistance and islos then
canlase = true
end
end
return canlase
end
-------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------
--- (Internal) FSM Function for monitoring
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @return #AUTOLASE self
function AUTOLASE:onbeforeMonitor(From, Event, To)
self:T({From, Event, To})
-- Check if group has detected any units.
self:UpdateIntel()
return self
end
--- (Internal) FSM Function for monitoring
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @return #AUTOLASE self
function AUTOLASE:onafterMonitor(From, Event, To)
self:T({From, Event, To})
-- Housekeeping
local countlases = self:CleanCurrentLasing()
self:SetPilotMenu()
local detecteditems = self.Contacts or {} -- #table of Ops.Intelligence#INTEL.Contact
local groupsbythreat = {}
local report = REPORT:New("Detections")
local lines = 0
for _,_contact in pairs(detecteditems) do
local contact = _contact -- Ops.Intelligence#INTEL.Contact
local grp = contact.group
local coord = contact.position
local reccename = contact.recce or "none"
local reccegrp = UNIT:FindByName(reccename)
if reccegrp then
local reccecoord = reccegrp:GetCoordinate()
local distance = math.floor(reccecoord:Get3DDistance(coord))
local text = string.format("%s of %s | Distance %d km | Threatlevel %d",contact.attribute, contact.groupname, math.floor(distance/1000), contact.threatlevel)
report:Add(text)
self:T(text)
if self.debug then self:I(text) end
lines = lines + 1
-- sort out groups beyond sight
local lasedistance = self:GetLosFromUnit(reccegrp)
if grp:IsGround() and lasedistance >= distance then
table.insert(groupsbythreat,{contact.group,contact.threatlevel})
self.RecceNames[contact.groupname] = contact.recce
end
end
end
self.GroupsByThreat = groupsbythreat
if self.verbose > 2 and lines > 0 then
local m=MESSAGE:New(report:Text(),self.reporttimeshort,"Autolase"):ToAll()
end
table.sort(self.GroupsByThreat, function(a,b)
local aNum = a[2] -- Coin value of a
local bNum = b[2] -- Coin value of b
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
end)
-- build table of Units
local unitsbythreat = {}
for _,_entry in pairs(self.GroupsByThreat) do
local group = _entry[1] -- Wrapper.Group#GROUP
if group and group:IsAlive() then
local units = group:GetUnits()
local reccename = self.RecceNames[group:GetName()]
for _,_unit in pairs(units) do
local unit = _unit -- Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
local threat = unit:GetThreatLevel()
local coord = unit:GetCoordinate()
if threat > 0 then
local unitname = unit:GetName()
table.insert(unitsbythreat,{unit,threat})
self.RecceUnitNames[unitname] = reccename
end
end
end
end
end
self.UnitsByThreat = unitsbythreat
table.sort(self.UnitsByThreat, function(a,b)
local aNum = a[2] -- Coin value of a
local bNum = b[2] -- Coin value of b
return aNum > bNum -- Return their comparisons, < for ascending, > for descending
end)
local unitreport = REPORT:New("Detected Units")
local lines = 0
for _,_entry in pairs(self.UnitsByThreat) do
local threat = _entry[2]
local unit = _entry[1]
local unitname = unit:GetName()
local text = string.format("Unit %s | Threatlevel %d | Detected by %s",unitname,threat,self.RecceUnitNames[unitname])
unitreport:Add(text)
lines = lines + 1
self:T(text)
if self.debug then self:I(text) end
end
if self.verbose > 2 and lines > 0 then
local m=MESSAGE:New(unitreport:Text(),self.reporttimeshort,"Autolase"):ToAll()
end
for _,_detectingunit in pairs(self.RecceUnits) do
local reccename = _detectingunit.name
local recce = _detectingunit.unit
local reccecount = self.targetsperrecce[reccename] or 0
local targets = 0
for _,_entry in pairs(self.UnitsByThreat) do
local unit = _entry[1] -- Wrapper.Unit#UNIT
local unitname = unit:GetName()
local canlase = self:CanLase(recce,unit)
if targets+reccecount < self.maxlasing and not self:CheckIsLased(unitname) and unit:IsAlive() and canlase then
targets = targets + 1
local code = self:GetLaserCode(reccename)
local spot = SPOT:New(recce)
spot:LaseOn(unit,code,self.LaseDuration)
local locationstring = unit:GetCoordinate():ToStringLLDDM()
local laserspot = { -- #AUTOLASE.LaserSpot
laserspot = spot,
lasedunit = unit,
lasingunit = recce,
lasercode = code,
location = locationstring,
timestamp = timer.getAbsTime(),
unitname = unitname,
reccename = reccename,
unittype = unit:GetTypeName(),
}
if self.smoketargets then
local coord = unit:GetCoordinate()
local color = self:GetSmokeColor(reccename)
coord:Smoke(color)
end
self.lasingindex = self.lasingindex + 1
self.CurrentLasing[self.lasingindex] = laserspot
self:__Lasing(2,laserspot)
end
end
end
self:__Monitor(-30)
return self
end
--- (Internal) FSM Function onbeforeRecceKIA
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string RecceName The lost Recce
-- @return #AUTOLASE self
function AUTOLASE:onbeforeRecceKIA(From,Event,To,RecceName)
self:T({From, Event, To, RecceName})
if self.notifypilots or self.debug then
local text = string.format("Recce %s KIA!",RecceName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
--- (Internal) FSM Function onbeforeTargetDestroyed
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The destroyed unit\'s name
-- @param #string RecceName The Recce name lasing
-- @return #AUTOLASE self
function AUTOLASE:onbeforeTargetDestroyed(From,Event,To,UnitName,RecceName)
self:T({From, Event, To, UnitName, RecceName})
if self.notifypilots or self.debug then
local text = string.format("Unit %s destroyed! Good job!",UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
--- (Internal) FSM Function onbeforeTargetLost
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The lost unit\'s name
-- @param #string RecceName The Recce name lasing
-- @return #AUTOLASE self
function AUTOLASE:onbeforeTargetLost(From,Event,To,UnitName,RecceName)
self:T({From, Event, To, UnitName,RecceName})
if self.notifypilots or self.debug then
local text = string.format("%s lost sight of unit %s.",RecceName,UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
--- (Internal) FSM Function onbeforeLaserTimeout
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param #string UnitName The lost unit\'s name
-- @param #string RecceName The Recce name lasing
-- @return #AUTOLASE self
function AUTOLASE:onbeforeLaserTimeout(From,Event,To,UnitName,RecceName)
self:T({From, Event, To, UnitName,RecceName})
if self.notifypilots or self.debug then
local text = string.format("%s laser timeout on unit %s.",RecceName,UnitName)
self:NotifyPilots(text,self.reporttimeshort)
end
return self
end
--- (Internal) FSM Function onbeforeLasing
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @param Functional.Autolase#AUTOLASE.LaserSpot LaserSpot The LaserSpot data table
-- @return #AUTOLASE self
function AUTOLASE:onbeforeLasing(From,Event,To,LaserSpot)
self:T({From, Event, To, LaserSpot.unittype})
if self.notifypilots or self.debug then
local laserspot = LaserSpot -- #AUTOLASE.LaserSpot
local text = string.format("%s is lasing %s code %d\nat %s",laserspot.reccename,laserspot.unittype,laserspot.lasercode,laserspot.location)
self:NotifyPilots(text,self.reporttimeshort+5)
end
return self
end
--- (Internal) FSM Function onbeforeCancel
-- @param #AUTOLASE self
-- @param #string From The from state
-- @param #string Event The event
-- @param #string To The to state
-- @return #AUTOLASE self
function AUTOLASE:onbeforeCancel(From,Event,To)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:__Stop(2)
return self
end
-------------------------------------------------------------------
-- End Functional.Autolase.lua
-------------------------------------------------------------------
@@ -599,7 +599,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 Wrapper.Group#GROUP DetectionGroup The Group detecting.
-- @param Wrapper.Group#GROUP Detection The Group detecting.
-- @param #number DetectionTimeStamp Time stamp of detection event.
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
@@ -670,7 +670,7 @@ do -- DETECTION_BASE
local DetectedObjectVec3 = DetectedObject:getPoint()
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
local DetectionGroupVec3 = Detection:GetVec3()
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 +
@@ -714,7 +714,7 @@ do -- DETECTION_BASE
if self.RejectZones then
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
if RejectZone:IsPointVec2InZone( DetectedObjectVec2 ) == true then
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
DetectionAccepted = false
end
end
@@ -759,7 +759,7 @@ do -- DETECTION_BASE
local ZoneProbability = ZoneData[2] -- #number
ZoneProbability = ZoneProbability * 30 / 300
if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
local Probability = math.random() -- Selects a number between 0 and 1
--self:T( { Probability, ZoneProbability } )
if Probability > ZoneProbability then
@@ -2485,13 +2485,13 @@ do -- DETECTION_AREAS
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
--
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
-- the methods to manage the DetectedItems[].Zone(s) is implemented in @{Functional.Detection#DETECTION_AREAS}.
-- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}.
--
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
--
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZones}().
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZoneCount}().
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_BASE.GetDetectionZone}() with a given index.
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}().
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index.
--
-- ## 4.4) Flare or Smoke detected units
--
@@ -2535,7 +2535,49 @@ do -- DETECTION_AREAS
return self
end
--- Retrieve set of detected zones.
-- @param #DETECTION_AREAS self
-- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected.
function DETECTION_AREAS:GetDetectionZones()
local zoneset = SET_ZONE:New()
for _ID,_Item in pairs (self.DetectedItems) do
local item = _Item -- #DETECTION_BASE.DetectedItem
if item.Zone then
zoneset:AddZone(item.Zone)
end
end
return zoneset
end
--- Retrieve a specific zone by its ID (number)
-- @param #DETECTION_AREAS self
-- @param #number ID
-- @return Core.Zone#ZONE_UNIT The zone or nil if it does not exist
function DETECTION_AREAS:GetDetectionZoneByID(ID)
local zone = nil
for _ID,_Item in pairs (self.DetectedItems) do
local item = _Item -- #DETECTION_BASE.DetectedItem
if item.ID == ID then
zone = item.Zone
break
end
end
return zone
end
--- Retrieve number of detected zones.
-- @param #DETECTION_AREAS self
-- @return #number The number of zones.
function DETECTION_AREAS:GetDetectionZoneCount()
local zoneset = 0
for _ID,_Item in pairs (self.DetectedItems) do
if _Item.Zone then
zoneset = zoneset + 1
end
end
return zoneset
end
--- Report summary of a detected item using a given numeric index.
-- @param #DETECTION_AREAS self
-- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem.
@@ -0,0 +1,730 @@
--- **Functional** - (R2.5) - Monitor and export flight model data.
--
-- Record flight data and export it to a csv file.
--
-- ## Recorded Data:
--
-- * Time,
-- * Altitude,
-- * Temperature,
-- * Pressure,
-- * Velocity (total and x,y,z components),
-- * Acceleration (total and x,y,z components),
-- * AoA,
-- * Pitch and pitch rate,
-- * Roll and roll rate,
-- * Yaw and yaw rate,
-- * Turn rate.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Functional.FMData
-- @image Functional_Rat2.png
--- FMD class.
-- @type FMD
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table players Player table.
-- @field #table menuadded Table of units where the F10 radio menu was added.
-- @field #string savepath Path to save data files.
-- @extends Core.Fsm#FSM
--- Be sure!
--
-- ===
--
-- ![Banner Image](..\Presentations\FMD\FMD_Main.png)
--
-- # The FMD Concept
--
-- This class can be used to record flight data such as velocity, acceleration, pitch, roll, yaw and turn rates. The output is written to a csv file for later analysis.
--
-- # Usage
--
-- The script is very easy to use. It only requires the line
--
-- fmd=FMD:New()
--
-- This will automatically start the FMD script.
--
-- Each player will get an entry "FMD" in the F-10 radio menu. There he can start or stop the data recording.
--
-- **IMPORTANT**
-- Due to a DCS bug, it is necessary that (in single player mode), player/clients have to **hit ESC twice** before entering an aircraft client slot.
-- Otherwise, the script will not load and now menus will be created for the player.
--
-- # Output
--
-- After the data recording is completed, the data is written to a csv file. The file name starts with **FMD** and contains the employed airframe plus a running number.
--
-- However, one must desanitize the **io** and **lfs** lines the DCS root directory. Otherwise, DCS will not allow data to be written to file.
--
-- @field #FMD
FMD = {
ClassName = "FMD",
Debug = false,
lid = nil,
players = {},
menuadded = {},
savepath = nil,
}
--- Player data table.
-- @type FMD.PlayerData
-- @field Core.Scheduler#SCHEDULER scheduler Scheduler
-- @field Wrapper.Unit#UNIT unit Player unit.
-- @field #string unitname Name of the unit.
-- @field Wrapper.Client#CLIENT client Player client.
-- @field #string actype Aircraft type.
-- @field #string name Player name.
-- @field #number dt Time step for data recording in seconds. Default 0.01 sec ==> 100 data points per second!
-- @field #number rd Recording duration in seconds.
-- @field #boolean recording If true, recording started.
-- @field #table data Data table.
-- @field #number SID Scheduler ID.
--- Data point table.
-- @type FMD.DataPoint
-- @field #number time Abs mission time.
-- @field #number T Temperature in degrees Celsius.
-- @field #number P Pressure.
-- @field #number Alt Altitude ASL in meters.
-- @field #number AoA Angle of Attack in degrees.
-- @field #number Pitch Pitch angle in degrees.
-- @field #number Roll Roll angle in degrees.
-- @field #number Yaw Yaw angle in degrees.
-- @field #number Climbrate Climb rate in m/s.
-- @field #number Vtot Total velocity in m/s.
-- @field DCS#Vec3 v Velocity vector. Components x,y,z in m/s.
-- @field DCS#Vec3 o Orientation vector. Components x,y,z in meters.
-- @field #number omega Angle velocity in m/s.
-- @field #number Hdg Heading in degrees.
-- @field #number Atot Total acceleration m/s^2.
-- @field DCS#Vec3 a Acceleration vector.
-- @field #number DRoll Roll speed in degrees/second.
-- @field #number DPitch Pitch speed in degrees/second.
-- @field #number DYaw Yaw speed in degrees/second.
--- Main group level radio menu: F10 Other/FMD.
-- @field #table MenuF10
FMD.MenuF10={}
--- FMD mission level F10 root menu.
-- @field #table MenuF10Root
FMD.MenuF10Root=nil
--- FMD script version.
-- @field #string version
FMD.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FMD class object.
-- @param #FMD self
-- @return #FMD self.
function FMD:New()
-- Inherit everthing from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #FMD
-- Start State.
self:SetStartState("Stopped")
-- Log string.
self.lid="FMD | "
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start FMD script.
self:AddTransition("*", "Status", "*") -- Start FMD script.
-- Start FMD.
self:Start()
return self
end
--- On after Start event.
-- @param #FMD self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FMD:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting Flight Model Data script version %s", FMD.version)
self:I(self.lid..text)
-- Handle events.
self:HandleEvent(EVENTS.Birth)
end
--- On after Stop event.
-- @param #FMD self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FMD:onafterStop(From, Event, To)
-- Short info.
local text=string.format("Stopping Flight Model Data script version %s", FMD.version)
self:I(self.lid..text)
-- Handle events.
self:UnHandleEvent(EVENTS.Birth)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- FMD event handler for event birth.
-- @param #FMD self
-- @param Core.Event#EVENTDATA EventData
function FMD:OnEventBirth(EventData)
self:F3({eventbirth = EventData})
local _unitName=EventData.IniUnitName
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
self:T2(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
self:T2(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
self:T2(self.lid.."BIRTH: player = "..tostring(_playername))
if _unit and _playername then
local _uid=_unit:GetID()
local _group=_unit:GetGroup()
local _callsign=_unit:GetCallsign()
-- Debug output.
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", _playername, _callsign, _unitName, _group:GetName())
self:T(self.lid..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Add Menu commands.
self:_AddF10Commands(_unitName)
-- Player data.
self.players[_playername]={}
local player=self.players[_playername] --#FMD.PlayerData
player.scheduler=SCHEDULER:New()
player.unit=_unit
player.unitname=_unitName
player.name=_playername
player.client=CLIENT:FindByName(_unitName, nil, true)
player.actype=_unit:GetTypeName()
player.dt=0.01
player.rd=30
player.recording=false
player.data={}
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Data Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get data of unit.
-- @param #FMD self
-- @param Wrapper.Unit#UNIT unit
-- @return #FMD.DataPoint Datapoint.
function FMD:_GetDataPoint(unit)
if unit and unit:IsAlive() then
-- Current coordinate.
local coord=unit:GetCoordinate()
local dp={} --#FMD.DataPoint
dp.a={}
dp.Alt=coord.y
dp.AoA=unit:GetAoA()
dp.Atot=nil
dp.P=coord:GetPressure()
dp.Pitch=unit:GetPitch()
dp.Roll=unit:GetRoll()
dp.time=timer.getAbsTime()
dp.T=coord:GetTemperature()
dp.v=unit:GetVelocityVec3()
dp.Vtot=UTILS.VecNorm(dp.v)
dp.Yaw=unit:GetYaw()
dp.o=unit:GetOrientationX()
dp.Hdg=unit:GetHeading()
return dp
else
return nil
end
end
--- Get data of unit.
-- @param #FMD self
-- @param #FMD.PlayerData playerData Player data.
-- @return #FMD.DataPoint Datapoint.
function FMD:_Derivative(playerData)
local function numderiv(fpm,fpp,h)
return 0.5*(fpp-fpm)/h
end
local datapoints=playerData.data
--local dt=playerData.dt
for i=2,#datapoints-1 do
local dpm=datapoints[i-1] --#FMD.DataPoint
local dpp=datapoints[i+1] --#FMD.DataPoint
local dpi=datapoints[i] --#FMD.DataPoint
-- Time step.
local dt=0.5*(dpp.time-dpm.time)
dpi.Atot=numderiv(dpm.Vtot, dpp.Vtot, dt)
dpi.a.x=numderiv(dpm.v.x, dpp.v.x, dt)
dpi.a.y=numderiv(dpm.v.y, dpp.v.y, dt)
dpi.a.z=numderiv(dpm.v.z, dpp.v.z, dt)
dpi.DPitch=numderiv(dpm.Pitch, dpp.Pitch, dt)
--dpi.DRoll=numderiv(dpm.Roll, dpp.Roll, dt)
dpi.DYaw=numderiv(dpm.Yaw, dpp.Yaw, dt)
-- Roll shortcuts.
local r1=dpm.Roll
local r2=dpp.Roll
-- Put roll in [0,360)
if (r1<0) then
r1=r1+360
end
if (r2<0) then
r2=r2+360
end
-- Handle case where 360 deg periodicity strikes.
if r1<90 and r2>270 then
r1=r1+360
end
if r1>270 and r2<90 then
r2=r2+360
end
--
dpi.DRoll=numderiv(r1, r2, dt)
local ang=UTILS.VecAngle(dpm.o, dpp.o)
dpi.omega=numderiv(0, ang, dt)
end
end
--- Record data
-- @param #FMD self
-- @param #FMD.PlayerData playerData Player data table.
function FMD:_RecordData(playerData)
-- Check if we are recording already.
if not playerData.recording then
-- Inform player.
local text=string.format("Data recording starts for %.1f sec.", playerData.rd)
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
-- Activate recording switch.
playerData.recording=true
end
-- Check if unit is alive.
if playerData.unit and playerData.unit:IsAlive() then
-- Get data point.
local dp=self:_GetDataPoint(playerData.unit)
-- Add data point to player table.
table.insert(playerData.data, dp)
else
-- Stop recording if player unit is not alive.
self:_StopRecording(playerData.unitname)
end
end
--- Save data.
-- @param #FMD self
-- @param #FMD.PlayerData playerData Player data table.
function FMD:_SaveData(playerData)
-- Nothing to save.
if playerData==nil or #playerData.data==0 or not io then
return
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.
local path=self.savepath
if lfs then
path=path or lfs.writedir()
end
-- Create unused file name.
local filename=nil
for i=1,9999 do
-- Create file name.
filename=string.format("FMD-%s_%s-%04d.csv", playerData.name, playerData.actype, i)
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local _exists=UTILS.FileExists(filename)
if not _exists then
break
end
end
-- Info
local text=string.format("Saving player %s flight data to file %s", playerData.name, filename)
self:I(self.lid..text)
-- Header line
local data="#Time,Altitude,Temperature,Pressure,Vtot,Vx,Vy,Vz,Atot,ax,ay,az,AoA,Pitch,dPitch/dt,Roll,dRoll/dt,Yaw,dYaw/dt,Turn Rate\n"
local g0=playerData.data[1] --#FMD.DataPoint
local T0=g0.time
-- Calculate derivatives.
self:_Derivative(playerData)
for i=2,#playerData.data-1 do
local dp=playerData.data[i] --#FMD.DataPoint
-- Conversion m/s == Mach.
local ms2mach=0.00291545
local t=(dp.time-T0) or 0
local a=dp.Alt or 0
local b=dp.T or 0
local c=dp.P or 0
local d=dp.Vtot or 0
local e=dp.v.x or 0
local f=dp.v.y or 0
local g=dp.v.z or 0
local h=dp.Atot or 0
local i=dp.a.x or 0
local j=dp.a.y or 0
local k=dp.a.z or 0
local l=dp.AoA or 0
local m=dp.Pitch or 0
local n=dp.DPitch or 0
local roll=dp.Roll or 0
if roll<0 then
roll=roll+360
end
local o=roll --dp.Roll or 0
local p=dp.DRoll or 0
local q=dp.Yaw or 0
local r=dp.DYaw or 0
local s=dp.omega or 0
-- Debug output.
self:T3(t)
self:T3(a)
self:T3(b)
self:T3(c)
self:T3(d)
self:T3(e)
self:T3(f)
self:T3(g)
self:T3(h)
self:T3(i)
self:T3(j)
self:T3(k)
self:T3(l)
self:T3(m)
self:T3(n)
self:T3(o)
self:T3(p)
self:T3(q)
self:T3(r)
self:T3(s)
-- t a b c d e f g h i j k l m n o p q r s
data=data..string.format("%.2f,%.2f,%.2f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f\n",
t, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s)
end
-- Save file.
_savefile(filename, data)
-- Clear data.
playerData.data={}
end
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- RADIO MENU Functions
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add menu commands for player.
-- @param #FMD self
-- @param #string _unitName Name of player unit.
function FMD:_AddF10Commands(_unitName)
self:F(_unitName)
-- Get player unit and name.
local _unit, playername = self:_GetPlayerUnitAndName(_unitName)
-- Check for player unit.
if _unit and playername then
-- Get group and ID.
local group=_unit:GetGroup()
local gid=group:GetID()
if group and gid then
if not self.menuadded[gid] then
-- Enable switch so we don't do this twice.
self.menuadded[gid]=true
-- Set menu root path.
local _rootPath=nil
if FMD.MenuF10Root then
------------------------
-- MISSON LEVEL MENUE --
------------------------
-- F10/FMD/...
_rootPath=FMD.MenuF10Root
else
------------------------
-- GROUP LEVEL MENUES --
------------------------
-- Main F10 menu: F10/FMD/
if FMD.MenuF10[gid]==nil then
FMD.MenuF10[gid]=missionCommands.addSubMenuForGroup(gid, "FMD")
end
-- F10/FMD/...
_rootPath=FMD.MenuF10[gid]
end
--------------------------------
-- F10/F<X> FMD/F1 Time Interval
--------------------------------
local _timePath=missionCommands.addSubMenuForGroup(gid, "Time Interval", _rootPath)
-- F10/FMD/F1 Time Interval/
missionCommands.addCommandForGroup(gid, "Delta t=0.01 s", _timePath, self._SetTimeInterval, self, _unitName, 0.01)
missionCommands.addCommandForGroup(gid, "Delta t=0.1 s", _timePath, self._SetTimeInterval, self, _unitName, 0.1)
missionCommands.addCommandForGroup(gid, "Delta t=1.0 s", _timePath, self._SetTimeInterval, self, _unitName, 1.0)
missionCommands.addCommandForGroup(gid, "Delta t=10 s", _timePath, self._SetTimeInterval, self, _unitName, 10.0)
missionCommands.addCommandForGroup(gid, "Delta t=30 s", _timePath, self._SetTimeInterval, self, _unitName, 30.0)
missionCommands.addCommandForGroup(gid, "Delta t=60 s", _timePath, self._SetTimeInterval, self, _unitName, 60.0)
-------------------------------
-- F10/F<X> FMD/F1 Rec Duration
-------------------------------
local _durPath=missionCommands.addSubMenuForGroup(gid, "Rec Duration", _rootPath)
-- F10/FMD/F1 Rec Duration/
missionCommands.addCommandForGroup(gid, "T=10 s", _durPath, self._SetRecDuration, self, _unitName, 10)
missionCommands.addCommandForGroup(gid, "T=30 s", _durPath, self._SetRecDuration, self, _unitName, 30)
missionCommands.addCommandForGroup(gid, "T=60 s", _durPath, self._SetRecDuration, self, _unitName, 60)
missionCommands.addCommandForGroup(gid, "T=5 min", _durPath, self._SetRecDuration, self, _unitName, 5*60)
missionCommands.addCommandForGroup(gid, "T=10 min", _durPath, self._SetRecDuration, self, _unitName, 10*60)
--------------------------------
-- F10/F<X> FMD/
--------------------------------
-- F10/FMD/Start Recording
missionCommands.addCommandForGroup(gid, "Start Recording", _rootPath, self._StartRecording, self, _unitName) -- F1
missionCommands.addCommandForGroup(gid, "Stop Recording", _rootPath, self._StopRecording, self, _unitName) -- F2
end
end
end
end
--- Set recording duration.
-- @param #FMD self
-- @param #string _unitName Name of player unit.
-- @param #number rd Recording duration in sec.
function FMD:_SetRecDuration(_unitName, rd)
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
-- Check if we have a unit which is a player.
if _unit and _playername then
local playerData=self.players[_playername] --#FMD.PlayerData
if playerData then
-- Set dt.
playerData.rd=rd
-- Inform player.
local text=string.format("Data recording duration set to %.1f sec.", playerData.rd)
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
end
end
end
--- Set time interval for data recording.
-- @param #FMD self
-- @param #string _unitName Name of player unit.
-- @param #number dt Time interval in seconds.
function FMD:_SetTimeInterval(_unitName, dt)
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
-- Check if we have a unit which is a player.
if _unit and _playername then
local playerData=self.players[_playername] --#FMD.PlayerData
if playerData then
-- Set dt.
playerData.dt=dt
-- Inform player.
local text=string.format("Data recording time interval set to %.3f sec.", playerData.dt)
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
end
end
end
--- Start data recording.
-- @param #FMD self
-- @param #string _unitName Name of player unit.
function FMD:_StartRecording(_unitName)
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
-- Check if we have a unit which is a player.
if _unit and _playername then
local playerData=self.players[_playername] --#FMD.PlayerData
if playerData then
-- Delay before recording starts.
local delay=3
-- Inform player.
local text=string.format("Data recording will be started in %d seconds with %.3f sec timestep for %.1f sec.", delay, playerData.dt, playerData.rd)
MESSAGE:New(text, 3, "FMD"):ToClient(playerData.client)
-- Start scheduler.
playerData.SID=playerData.scheduler:Schedule(nil, self._RecordData, {self, playerData}, delay, playerData.dt, 0.0)
-- Stop scheduler once.
playerData.scheduler:ScheduleOnce(playerData.rd+delay, self._StopRecording, self,_unitName)
end
end
end
--- Stop data recording.
-- @param #FMD self
-- @param #string _unitName Name of player unit.
function FMD:_StopRecording(_unitName)
-- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitName)
-- Check if we have a unit which is a player.
if _unit and _playername then
local playerData=self.players[_playername] --#FMD.PlayerData
if playerData then
local ndata=#playerData.data
-- Inform player.
local text=string.format("Data recording stopped. Data points recorded: %d", ndata)
MESSAGE:New(text, 10, "FMD"):ToClient(playerData.client)
-- No recording switch.
playerData.recording=false
-- Stop scheduler.
playerData.scheduler:Stop(playerData.SID)
-- Save data.
self:_SaveData(playerData)
end
end
end
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
-- @param #FMD self
-- @param #string _unitName Name of the player unit.
-- @return Wrapper.Unit#UNIT Unit of player or nil.
-- @return #string Name of the player or nil.
function FMD:_GetPlayerUnitAndName(_unitName)
self:F2(_unitName)
if _unitName ~= nil then
-- Get DCS unit from its name.
local DCSunit=Unit.getByName(_unitName)
if DCSunit then
local playername=DCSunit:getPlayerName()
local unit=UNIT:Find(DCSunit)
self:T2({DCSunit=DCSunit, unit=unit, playername=playername})
if DCSunit and unit and playername then
return unit, playername
end
end
end
-- Return nil if we could not find a player.
return nil,nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
@@ -1,28 +1,28 @@
--- **Functional** -- Train missile defence and deflection.
--
--
-- ===
--
-- ## Features:
--
--
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
-- * Provide alerts when a missile would have killed your aircraft.
-- * Provide alerts when the missile self destructs.
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
--
--
-- ===
--
--
-- ## Missions:
--
--
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
--
--
-- ===
--
--
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
--
--
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
--
--
-- * **Messages**: Menu to configure all messages.
-- * **Messages On**: Show all messages.
-- * **Messages Off**: Disable all messages.
@@ -45,23 +45,23 @@
-- * **Range Off**: Disable range information when a missile is fired to a target.
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
--
--
-- ===
--
--
-- ### Authors: **FlightControl**
--
--
-- ### Contributions:
--
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
--
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
-- Danny has shared his ideas and together we made a design.
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
-- * **132nd Squadron**: Testing and optimizing the logic.
--
--
-- ===
--
-- @module Functional.MissileTrainer
@@ -76,7 +76,7 @@
---
--
-- # Constructor:
--
--
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
--
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
@@ -84,7 +84,7 @@
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
--
-- # Initialization:
--
--
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
--
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
@@ -97,8 +97,8 @@
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
--
-- @field #MISSILETRAINER
--
-- @field #MISSILETRAINER
MISSILETRAINER = {
ClassName = "MISSILETRAINER",
TrackingMissiles = {},
@@ -167,7 +167,7 @@ end
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
-- @param #MISSILETRAINER self
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
-- @return #MISSILETRAINER
function MISSILETRAINER:New( Distance, Briefing )
local self = BASE:Inherit( self, BASE:New() )
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self:F( "ForEach:" .. Client.UnitName )
-- Client:Alive( self._Alive, self )
-- end
--
self.DBClients:ForEachClient(
--
self.DBClients:ForEachClient(
function( Client )
self:F( "ForEach:" .. Client.UnitName )
Client:Alive( self._Alive, self )
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
-- self.DB:ForEachClient(
-- --- @param Wrapper.Client#CLIENT Client
-- function( Client )
--
--
-- ... actions ...
--
--
-- end
-- )
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
self.DetailsRangeOnOff = true
self.DetailsBearingOnOff = true
self.MenusOnOff = true
self.TrackingMissiles = {}
@@ -293,7 +293,7 @@ end
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
-- @param #MISSILETRAINER self
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
-- @return #MISSILETRAINER self
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
self:F( TrackingFrequency )
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
if TrainerTargetDCSUnit then
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
self:T(TrainerTargetDCSUnitName )
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
if Client then
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
local Message = MESSAGE:New(
string.format( "%s launched a %s",
TrainerSourceUnit:GetTypeName(),
TrainerWeaponName
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
if self.AlertsToAll then
Message:ToAll()
else
Message:ToClient( Client )
end
end
local ClientID = Client:GetID()
self:T( ClientID )
local MissileData = {}
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
end
-- ALERTS PART
-- Loop for all Player Clients to check the alerts and deletion of missiles.
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
if Client and Client:IsAlive() then
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
local PositionMissile = TrainerWeapon:getPosition().p
local TargetVec3 = Client:GetVec3()
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
( PositionMissile.y - TargetVec3.y )^2 +
( PositionMissile.z - TargetVec3.z )^2
) ^ 0.5 / 1000
if Distance <= self.Distance then
-- Hit alert
TrainerWeapon:destroy()
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
self:T( "killed" )
local Message = MESSAGE:New(
string.format( "%s launched by %s killed %s",
TrainerWeapon:getTypeName(),
TrainerSourceUnit:GetTypeName(),
TrainerTargetUnit:GetPlayerName()
), 15, "Hit Alert" )
if self.AlertsToAll == true then
Message:ToAll()
else
Message:ToClient( Client )
end
MissileData = nil
table.remove( ClientData.MissileData, MissileDataID )
self:T(ClientData.MissileData)
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
TrainerWeaponTypeName,
TrainerSourceUnit:GetTypeName()
), 5, "Tracking" )
if self.AlertsToAll == true then
Message:ToAll()
else
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
-- TRACKING PART
-- For the current client, the missile range and bearing details are displayed To the Player Client.
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
-- Main Player Client loop
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
local Client = ClientData.Client
--self:T2( { Client:GetName() } )
ClientData.MessageToClient = ""
ClientData.MessageToAll = ""
-- Other Players Client loop
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
--self:T3( MissileDataID )
local TrainerSourceUnit = MissileData.TrainerSourceUnit
local TrainerWeapon = MissileData.TrainerWeapon
local TrainerTargetUnit = MissileData.TrainerTargetUnit
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
if ShowMessages == true then
local TrackingTo
TrackingTo = string.format( " -> %s",
TrainerWeaponTypeName
)
if ClientDataID == TrackingDataID then
if ClientData.MessageToClient == "" then
ClientData.MessageToClient = "Missiles to You:\n"
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
end
end
end
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
@@ -742,7 +742,7 @@ function PSEUDOATC:ReportWeather(GID, UID, position, location)
local T=position:GetTemperature()
-- Correct unit system.
local _T=string.format('%d°F', UTILS.CelciusToFarenheit(T))
local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(T))
if settings:IsMetric() then
_T=string.format('%d°C', T)
end
File diff suppressed because it is too large Load Diff
+855
View File
@@ -0,0 +1,855 @@
--- **Ops** - (R2.5) - Random Air Traffic.
--
--
--
-- RAT2 creates random air traffic on the map.
--
--
--
-- **Main Features:**
--
-- * It's very random.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Functional.Rat2
-- @image Functional_Rat2.png
--- RAT2 class.
-- @type RAT2
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table Qcraft Table of rat crafts.
-- @extends Core.Fsm#FSM
--- Be surprised!
--
-- ===
--
-- ![Banner Image](..\Presentations\RAT2\RAT2_Main.png)
--
-- # The RAT2 Concept
--
--
--
-- @field #RAT2
RAT2 = {
ClassName = "RAT2",
Debug = false,
lid = nil,
Qcraft = {},
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new RAT2 class object.
-- @param #RAT2 self
-- @return #RAT2 self.
function RAT2:New()
-- Inherit everthing from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #RAT2
-- Start State.
self:SetStartState("Stopped")
self.lid="RAT2 | "
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file.
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
self:AddTransition("*", "Status", "*") -- Start RAT2 script.
self:AddTransition("*", "Spawned", "*") -- A group was spawned.
return self
end
--- Create a new RAT2 class object.
-- @param #RAT2 self
-- @param #RATAC ratcraft The aircraft to add.
-- @return #RAT2 self.
function RAT2:AddAircraft(ratcraft)
-- Add ratcraft to spawn queue.
table.insert(self.Qcraft, ratcraft)
return self
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start random air traffic.
-- @param #RAT2 self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RAT2:onafterStart(From, Event, To)
self:__Status(-1)
end
--- Check spawn queue and spawn aircraft if necessary.
-- @param #RAT2 self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function RAT2:onafterStatus(From, Event, To)
self:I(string.format("RAT status %s", self:GetState()))
-- Check queue of aircraft to spawn.
self:_CheckQueueSpawn()
self:__Status(-10)
end
--- Check spawn queue and spawn aircraft if necessary.
-- @param #RAT2 self
function RAT2:_CheckQueueSpawn()
self:I(string.format("Checking flights"))
-- Loop over all ratcraft.
for i,_ratcraft in pairs(self.Qcraft) do
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
-- Get number of alive groups.
local Nalive=ratcraft:_GetAliveGroups()
for _,_flight in pairs(ratcraft.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
self:I(string.format("Checking flight group %s in status %s", flight.groupname, flight:GetState()))
if flight:IsInUtero() or flight:IsArrived() or flight:IsDead() then
self:I(string.format("Try Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
-- Get departure and parking.
local departure, parking=ratcraft:GetDeparture()
-- Get destination depending on departure.
local destination=ratcraft:GetDestination(departure)
if departure and destination and parking then
self:I(string.format("Spawning new flight group %s in status %s", flight.groupname, flight:GetState()))
-- Try to spawn a ratcraft group.
local group=self:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
-- Remove queue item and break loop.
if group then
-- We add a little delay
self:__Spawned(0.1, group, flight, ratcraft)
break
end -- if group
end -- if departure
end -- if spawn flight
end -- for flights
end -- for ratcraft
end
--- Check spawn queue and spawn aircraft if necessary.
-- @param #RAT2 self
function RAT2:_CheckArrived()
for _,_ratcraft in pairs(self.Qcraft) do
local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT
for _,_flight in pairs(ratcraft.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
if flight:IsArrived() then
-- Destroy group and create a remove unit event.
flight.group:Destroy(nil)
end
end
end
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Function called after a RAT group was spawned.
-- @param #RAT2 self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP group The spawned group.
-- @param Ops.FlightGroup#FLIGHTGROUP flight The flight group.
-- @param Functional.RatCraft#RATCRAFT ratcraft The ratcraft object.
function RAT2:onafterSpawned(From, Event, To, group, flight, ratcraft)
group:SmokeGreen()
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Spawn functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse.
-- @param #RAT2 self
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft to spawn.
-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group.
-- @param Functional.RatCraft#RATCRAFT.Departure departure Departure.
-- @param Functional.RatCraft#RATCRAFT.Departure destination Destination.
-- @param #table parking Parking data for this group.
-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned.
function RAT2:_SpawnRatcraft(ratcraft, flight, departure, destination, parking)
-- Prepare the spawn template.
local template=self:_SpawnAssetPrepareTemplate(ratcraft, flight.groupname)
-- Get flight path if the group goes to another warehouse by itself.
template.route.points=self:_GetFlightplan(ratcraft, AIRBASE:FindByName(departure.name), AIRBASE:FindByName(destination.name))
local airbase=AIRBASE:FindByName(departure.name) --self:_GetAirbase(departure)
-- Get airbase ID and category.
local AirbaseID = airbase:GetID()
local AirbaseCategory = airbase:GetAirbaseCategory()
-- Check enough parking spots.
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
--TODO Figure out what's necessary in this case.
else
if #parking<#template.units then
local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units)
self:_DebugMessage(text)
return nil
end
end
-- Position the units.
for i=1,#template.units do
-- Unit template.
local unit = template.units[i]
if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then
-- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense.
local coord=airbase:GetCoordinate()
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
unit.parking_id = nil
unit.parking = nil
else
local coord=parking[i].Coordinate --Core.Point#COORDINATE
local terminal=parking[i].TerminalID --#number
if self.Debug then
coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal))
end
unit.x=coord.x
unit.y=coord.z
unit.alt=coord.y
unit.parking_id = nil
unit.parking = terminal
end
if destination.parking then
local spot=destination.parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot
unit.parking_landing=spot.TerminalID
end
if ratcraft.livery==nil and #ratcraft.liveries>0 then
ratcraft.livery=ratcraft.liveries[math.random(#ratcraft.liveries)]
end
-- Set livery.
if ratcraft.livery then
unit.livery_id = ratcraft.livery
end
-- Set skill.
if ratcraft.skill then
unit.skill= ratcraft.skill
end
end
-- And template position.
template.x = template.units[1].x
template.y = template.units[1].y
-- Uncontrolled spawning.
template.uncontrolled=ratcraft.uncontrolled
-- Debug info.
self:T2({airtemplate=template})
-- Spawn group.
local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP
return group
end
--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids.
-- @param #RAT2 self
-- @param Functional.RatCraft#RATCRAFT ratcraft Aircraft that will be spawned.
-- @param #string groupname Name for the group to be spawned.
-- @return #table Prepared new spawn template.
function RAT2:_SpawnAssetPrepareTemplate(ratcraft, groupname)
-- Create an own copy of the template!
local template=UTILS.DeepCopy(ratcraft.template)
-- Set unique name.
template.name=groupname
-- Set current(!) coalition and country.
template.CoalitionID=ratcraft.coalition
template.CountryID=ratcraft.country
-- Nillify the group ID.
template.groupId=nil
-- No late activation.
template.lateActivation=false
-- Set and empty route.
template.route = {}
template.route.routeRelativeTOT=true
template.route.points = {}
-- Handle units.
for i=1,#template.units do
-- Unit template.
local unit=template.units[i]
-- Nillify the unit ID.
unit.unitId=nil
-- Set unit name: <alias>-01, <alias>-02, ...
unit.name=string.format("%s-%02d", template.name , i)
end
return template
end
--@param #RCRAFT.Attribute _attribute Generlized attribute of unit.
--- Get the proper terminal type based on generalized attribute of the group.
--@param #RAT2 self
--@param #number _category Airbase category.
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
function RAT2:_GetTerminal(_attribute, _category)
-- Default terminal is "large".
local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==RCRAFT.Attribute.FIGHTER then
-- Fighter ==> small.
_terminal=AIRBASE.TerminalType.FighterAircraft
elseif _attribute==RCRAFT.Attribute.BOMBER or _attribute==RCRAFT.Attribute.TRANSPORTPLANE or _attribute==RCRAFT.Attribute.TANKER or _attribute==RCRAFT.Attribute.AWACS then
-- Bigger aircraft.
_terminal=AIRBASE.TerminalType.OpenBig
elseif _attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO then
-- Helicopter.
_terminal=AIRBASE.TerminalType.HelicopterUsable
else
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
if _category==Airbase.Category.SHIP then
if not (_attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO) then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end
return _terminal
end
--- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned.
-- During the search also the not yet spawned asset aircraft are considered.
-- If not enough spots for all asset units could be found, the routine returns nil!
-- @param #RAT2 self
-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots.
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft.
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
function RAT2:_FindParking(airbase, ratcraft)
-- Init default
local scanradius=50
local scanunits=true
local scanstatics=true
local scanscenery=false
local verysafe=false
-- Function calculating the overlap of two (square) objects.
local function _overlap(l1,l2,dist)
local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance!
local safe = (dist > safedist)
self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe)))
return safe
end
-- Get parking spot data table. This contains all free and "non-free" spots.
local parkingdata=airbase:GetParkingSpotsTable()
-- List of obstacles.
local obstacles={}
-- Loop over all parking spots and get the currently present obstacles.
-- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay!
for _,parkingspot in pairs(parkingdata) do
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
-- Scan a radius of 100 meters around the spot.
local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery)
-- Check all units.
for _,_unit in pairs(_units) do
local unit=_unit --Wrapper.Unit#UNIT
local _coord=unit:GetCoordinate()
local _size=self:_GetObjectSize(unit:GetDCSObject())
local _name=unit:GetName()
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"})
end
-- Check all statics.
for _,static in pairs(_statics) do
local _vec3=static:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=static:getName()
local _size=self:_GetObjectSize(static)
table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"})
end
-- Check all scenery.
for _,scenery in pairs(_sceneries) do
local _vec3=scenery:getPoint()
local _coord=COORDINATE:NewFromVec3(_vec3)
local _name=scenery:getTypeName()
local _size=self:_GetObjectSize(scenery)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
end
-- TODO check clients. Clients cannot be spawned. So we can loop over them.
end
-- Parking data for all assets.
local parking={}
-- Get terminal type of this asset
local terminaltype=self:_GetTerminal(ratcraft.attribute, airbase:GetAirbaseCategory())
local Nunits=#ratcraft.templategroup:GetUnits()
-- Loop over all units - each one needs a spot.
for i=1,Nunits do
-- 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) then
-- Coordinate of the parking spot.
local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE
local _termid=parkingspot.TerminalID
local _toac=parkingspot.TOAC
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
local free=true
local problem=nil
-- Safe parking using TO_AC from DCS result.
if self.safeparking and _toac then
free=false
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
end
-- 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(ratcraft.size, obstacle.size, dist)
-- Spot is blocked.
if not safe then
--env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _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", _asset.templatename, _asset.uid, _termid, dist))
end
end
-- Check if spot is free
if free then
-- Add parkingspot for this asset unit.
table.insert(parking, parkingspot)
self:T(self.lid..string.format("Parking spot #%d is free for ratcraft unit id=%d!", _termid, i))
-- Add the unit as obstacle so that this spot will not be available for the next unit.
table.insert(obstacles, {coord=_spot, size=ratcraft.size, name=ratcraft.templatename, type="ratcraft"})
-- Break loop over parking spots.
gotit=true
break
else
-- Debug output for occupied spots.
self:T(self.lid..string.format("Parking spot #%d is occupied or not big enough!", _termid))
if self.Debug then
local coord=problem.coord --Core.Point#COORDINATE
local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist)
coord:MarkToAll(string.format(text))
end
end
end -- check terminal type
end -- loop over parking spots
-- No parking spot for at least one unit :(
if not gotit then
self:T(self.lid..string.format("WARNING: No free parking spot for ratcraft unit i=%d", i))
return nil
end
end -- loop over units
return parking
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Flightplan functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Calculate the maximum height an aircraft can reach for the given parameters.
-- @param #RAT2 self
-- @param #number D Total distance in meters from Departure to holding point at destination.
-- @param #number alphaC Climb angle in rad.
-- @param #number alphaD Descent angle in rad.
-- @param #number Hdep AGL altitude of departure point.
-- @param #number Hdest AGL altitude of destination point.
-- @param #number Deltahhold Relative altitude of holding point above destination.
-- @return #number Maximum height the aircraft can reach.
function RAT2:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold)
local Hhold=Hdest+Deltahhold
local hdest=Hdest-Hdep
local hhold=hdest+Deltahhold
local Dp=math.sqrt(D^2 + hhold^2)
local alphaS=math.atan(hdest/D) -- slope angle
local alphaH=math.atan(hhold/D) -- angle to holding point (could be necative!)
local alphaCp=alphaC-alphaH -- climb angle with slope
local alphaDp=alphaD+alphaH -- descent angle with slope
-- ASA triangle.
local gammap=math.pi-alphaCp-alphaDp
local sCp=Dp*math.sin(alphaDp)/math.sin(gammap)
local sDp=Dp*math.sin(alphaCp)/math.sin(gammap)
-- Max height from departure.
local hmax=sCp*math.sin(alphaC)
return hmax
end
--- Make a flight plan from a departure to a destination airport.
-- @param #RAT2 self
-- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft object.
-- @param Wrapper.Airbase#AIRBASE departure Departure airbase.
-- @param Wrapper.Airbase#AIRBASE destination Destination airbase.
-- @return #table Table of flightplan waypoints.
-- @return #table Table of flightplan coordinates.
function RAT2:_GetFlightplan(ratcraft, departure, destination)
-- Parameters
local Vmax=ratcraft.speedmax
local Range=ratcraft.range
local category=ratcraft.category
local ceiling=ratcraft.DCSdesc.Hmax
local Vymax=ratcraft.DCSdesc.VyMax
-- Max cruise speed 90% of max speed.
local VxCruiseMax=0.90*Vmax
-- Min cruise speed 70% of max cruise or 600 km/h whichever is lower.
local VxCruiseMin = math.min(VxCruiseMax*0.75, 750)
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
-- Climb speed 90% ov Vmax but max 720 km/h.
local VxClimb = math.min(Vmax*0.90, 720)
-- Descent speed 60% of Vmax but max 500 km/h.
local VxDescent = math.min(Vmax*0.60, 500)
-- Holding speed is 90% of descent speed.
local VxHolding = VxDescent*0.9
-- Final leg is 90% of holding speed.
local VxFinal = VxHolding*0.9
-- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate.
local VyClimb=math.min(7.6, Vymax)
-- Climb angle in rad.
local AlphaClimb=math.rad(4)
-- Descent angle in rad. Moderate 4 degrees.
local AlphaDescent=math.rad(4)
-- Expected cruise level (peak of Gaussian distribution)
local FLcruise_expect=150*RAT.unit.FL2m
if category==Group.Category.HELICOPTER then
FLcruise_expect=1000 -- 1000 m ASL
end
-------------------------
--- DEPARTURE AIRPORT ---
-------------------------
-- Coordinates of departure point.
local Pdeparture=departure:GetCoordinate()
-- Height ASL of departure point.
local H_departure=Pdeparture.y
---------------------------
--- DESTINATION AIRPORT ---
---------------------------
-- Position of destination airport.
local Pdestination=destination:GetCoordinate()
-- Height ASL of destination airport/zone.
local H_destination=Pdestination.y
-----------------------------
--- DESCENT/HOLDING POINT ---
-----------------------------
-- Get a random point between 5 and 10 km away from the destination.
local Rhmin=5000
local Rhmax=10000
-- For helos we set a distance between 500 to 1000 m.
if category==Group.Category.HELICOPTER then
Rhmin=500
Rhmax=1000
end
-- Coordinates of the holding point. y is the land height at that point.
local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin)
-- Distance from holding point to final destination (not used).
local d_holding=Pholding:Get2DDistance(Pdestination)
-- AGL height of holding point.
local H_holding=Pholding.y
---------------
--- GENERAL ---
---------------
-- We go directly to the holding point not the destination airport. From there, planes are guided by DCS to final approach.
local heading=Pdeparture:HeadingTo(Pholding)
local d_total=Pdeparture:Get2DDistance(Pholding)
------------------------------
--- Holding Point Altitude ---
------------------------------
-- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL.
local h_holding=1200
if category==Group.Category.HELICOPTER then
h_holding=150
end
h_holding=UTILS.Randomize(h_holding, 0.2)
-- Max holding altitude.
local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0)
if h_holding>DeltaholdingMax then
h_holding=math.abs(DeltaholdingMax)
end
-- This is the height ASL of the holding point we want to fly to.
local Hh_holding=H_holding+h_holding
---------------------------
--- Max Flight Altitude ---
---------------------------
-- Get max flight altitude relative to H_departure.
local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding)
-- Max flight level ASL aircraft can reach for given angles and distance.
local FLmax = h_max+H_departure
--CRUISE
-- Min cruise alt is just above holding point at destination or departure height, whatever is larger.
local FLmin=math.max(H_departure, Hh_holding)
-- Ensure that FLmax not above its service ceiling.
FLmax=math.min(FLmax, ceiling)
-- If the route is very short we set FLmin a bit lower than FLmax.
if FLmin>FLmax then
FLmin=FLmax
end
-- Expected cruise altitude - peak of gaussian distribution.
if FLcruise_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax then
FLcruise_expect=FLmax
end
-- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax.
local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
-- Climb and descent heights.
local h_climb = FLcruise - H_departure
local h_descent = FLcruise - Hh_holding
-- Get distances.
local d_climb = h_climb/math.tan(AlphaClimb)
local d_descent = h_descent/math.tan(AlphaDescent)
local d_cruise = d_total-d_climb-d_descent
-- Debug.
local text=string.format("Flight plan:\n")
text=text..string.format("Vx max = %.2f km/h\n", Vmax)
text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6)
text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6)
text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6)
text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6)
text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6)
text=text..string.format("Vy max = %.2f m/s\n", Vymax)
text=text..string.format("Vy climb = %.2f m/s\n", VyClimb)
text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb))
text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent))
text=text..string.format("Dist climb = %.3f km\n", d_climb/1000)
text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000)
text=text..string.format("Dist descent = %.3f km\n", d_descent/1000)
text=text..string.format("Dist total = %.3f km\n", d_total/1000)
text=text..string.format("h_climb = %.3f km\n", h_climb/1000)
text=text..string.format("h_desc = %.3f km\n", h_descent/1000)
text=text..string.format("h_holding = %.3f km\n", h_holding/1000)
text=text..string.format("h_max = %.3f km\n", h_max/1000)
text=text..string.format("FL min = %.3f km\n", FLmin/1000)
text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000)
text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000)
text=text..string.format("FL max = %.3f km\n", FLmax/1000)
text=text..string.format("Ceiling = %.3f km\n", ceiling/1000)
text=text..string.format("Max range = %.3f km\n", Range/1000)
self:T(self.lid..text)
-- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back.
if d_cruise<0 then
d_cruise=100
end
------------------------
--- Create Waypoints ---
------------------------
-- Waypoints and coordinates
local wp={}
local c={}
--- Departure/Take-off
c[#c+1]=Pdeparture
wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure")
--- Begin of Cruise
local Pcruise=Pdeparture:Translate(d_climb, heading)
Pcruise.y=FLcruise
c[#c+1]=Pcruise
wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise")
--- Descent
local Pdescent=Pcruise:Translate(d_cruise, heading)
Pdescent.y=FLcruise
c[#c+1]=Pdescent
wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent")
--- Holding point
Pholding.y=H_holding+h_holding
c[#c+1]=Pholding
wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding")
--- Final destination.
c[#c+1]=Pdestination
wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination")
-- Mark points at waypoints for debugging.
if self.Debug or true then
for i,coord in pairs(c) do
local coord=coord --Core.Point#COORDINATE
local dist=0
if i>1 then
dist=coord:Get2DDistance(c[i-1])
end
coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000))
end
end
return wp,c
end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,596 @@
--- **Functional** - (R2.5) - Random Air Traffic.
--
--
--
-- RATCRAFT creates random air traffic on the map.
--
--
--
-- **Main Features:**
--
-- * It's very random.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Functional.RatCraft
-- @image Functional_Rat2.png
--- RATCRAFT
--
-- @type RATCRAFT
-- @field #string ClassName Name of the class.
-- @field #boolean Debugmode Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string templatename Name of the group template.
-- @field #string alias Alias for spawn prefix. Default template group name.
-- @field #table template Table of the group template.
-- @field Wrapper.Group#GROUP templategroup Template group.
-- @field #number coalition Coalition side.
-- @field #number country Country number.
-- @field #string actype Aircraft type name.
-- @field #number category Category (plane=X, helo=Y).
-- @field #string attribute Generalized attribute.
-- @field #number ceiling Max altitude in meters of the aircraft group.
-- @field #number speedmax Max speed in km/h of the aircraft group.
-- @field #number sizex Size X-axis in meters.
-- @field #number sizez Size Z-axis in meters.
-- @field #number size Max size (X, Z) in meters.
-- @field #table liveries Table of liveries.
-- @field #string livery Current livery.
-- @field #string skill Skill of group.
-- @field #string onboard Onboard number. This should better be on UNIT level!
-- @field #table departures Defined departures.
-- @field #table departure Current departure airbase or zone.
-- @field #table destinations Defined destinations.
-- @field #table destination Current destination.
-- @field #boolean commute If true, commute between to airbases/zones.
-- @field #boolean journey If true, continue journey from the current destination.
-- @field #string takeoff Takeoff type cold, hot, air.
-- @field #string landing Landing type.
-- @field #table flights Table of flight groups.
-- @field #number Nspawn Number of max spawned aircraft groups.
-- @field #number Nunits Number of units in group.
-- @extends Core.Fsm#FSM
RATCRAFT = {
ClassName = "RATCRAFT",
Debugmode = false,
lid = nil,
templatename = nil,
alias = nil,
template = nil,
templategroup = nil,
coalition = nil,
country = nil,
actype = nil,
category = nil,
attribute = nil,
ceiling = nil,
speedmax = nil,
sizex = nil,
sizez = nil,
size = nil,
liveries = {},
livery = nil,
skill = nil,
onboard = nil,
departures = {},
destinations = {},
departure = nil,
destination = nil,
commute = nil,
journey = nil,
takeoff = nil,
landing = nil,
flights = {},
Nspawn = 2,
Nunits = nil,
}
--- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit.
-- @type RATCRAFT.Attribute
-- @field #string TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets.
-- @field #string AWACS Airborne Early Warning and Control System.
-- @field #string FIGHTER Fighter, interceptor, ... airplane.
-- @field #string BOMBER Aircraft which can be used for strategic bombing.
-- @field #string TANKER Airplane which can refuel other aircraft.
-- @field #string TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets.
-- @field #string ATTACKHELO Attack helicopter.
-- @field #string UAV Unpiloted Aerial Vehicle, e.g. drones.
RATCRAFT.Attribute = {
TRANSPORTPLANE="TransportPlane",
AWACS="AWACS",
FIGHTER="Fighter",
BOMBER="Bomber",
TANKER="Tanker",
TRANSPORTHELO="TransportHelo",
ATTACKHELO="AttackHelo",
UAV="UAV",
OTHER="Other",
}
--- Departure/destination type, i.e. airbase or zone.
-- @type RATCRAFT.DeType
-- @field #string AIRBASE Departure/destination is an airbase.
-- @field #string ZONE Departure/destination is a zone.
RATCRAFT.DeType={
AIRBASE="Airbase",
ZONE="Zone",
}
--- Departure/destination parameters.
-- @type RATCRAFT.Departure
-- @field #string name Name of the departure/destination airbase/zone.
-- @field #string type Type of the departure/destination, i.e. an airbase or a zone.
-- @field #table parking Number of parking spot IDs for the aircraft.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: helos
-- TODO: despawn when arrived
-- TODO: zones as departures
-- TODO: zones as destinations
-- TODO: monitor if stationary
-- TODO: commute
-- TODO: continue journey
-- TODO: return zone
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new RATCRAFT class object from a template group defined in the mission editor.
-- @param #RATCRAFT self
-- @param #string groupname Name of the **late activated** template group.
-- @param #number nspawn Number of groups to spawn. Default 1.
-- @param #string alias (Optional) Alias for the group name used as spawn name prefix. Default is the group name.
-- @return #RATCRAFT self
function RATCRAFT:New(groupname, nspawn, alias)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) --#RATCRAFT
-- Defaults
self.Nspawn=nspawn or 1
self.alias=alias or groupname
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Load", "Stopped") -- Load ratcraft from file.
self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script.
self:AddTransition("Running", "Pause", "Paused") -- Pause spawning RATCRAFT.
self:AddTransition("Paused", "Unpause", "Running") -- UnPause spawning RATCRAFT.
-- Initialize aircraft parameters.
self:_Init(groupname)
self:Start()
return self
end
--- Init aircraft parameters.
-- @param #RATCRAFT self
-- @param #string groupname Name of the template group.
-- @return #RATCRAFT self
function RATCRAFT:_Init(groupname)
self.templatename=groupname
self.templategroup=GROUP:FindByName(groupname)
self.template=self.templategroup:GetTemplate()
self.speedmax=self.templategroup:GetSpeedMax()
self.range=1000000
self.DCSdesc=self.templategroup:GetDCSDesc(1)
self.coalition=self.templategroup:GetCoalition()
self.country=self.templategroup:GetUnit(1):GetCountry()
self.category=self.templategroup:GetUnit(1):GetCategory()
self.Nunits=#self.templategroup:GetUnits()
-- aircraft dimensions
self.sizex=self.DCSdesc.box.max.x
self.sizey=self.DCSdesc.box.max.y
self.sizez=self.DCSdesc.box.max.z
self.size =math.max(self.sizex, self.sizez)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add adeparture airbase for the aircraft.
-- @param #RATCRAFT self
-- @param #string airbasename Departure airbase name.
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
-- @return #RATCRAFT self
function RATCRAFT:AddDepartureAirbase(airbasename, parking)
local departure={} --#RATCRAFT.Departure
local spots=nil
if parking then
if type(parking)=="number" then
parking={parking}
end
-- Convert parking IDs to complete parking data.
spots={}
local airbase=AIRBASE:FindByName(airbasename)
for _,TerminalID in pairs(parking) do
local spot=airbase:GetParkingSpotData(TerminalID)
if spot then
self:I(string.format("Add in parking spot %d", spot.TerminalID))
table.insert(spots, spot)
end
end
end
departure.name=airbasename
departure.type=RATCRAFT.DeType.AIRBASE
departure.parking=spots
table.insert(self.departures, departure)
return self
end
--- Add destination airbase for the aircraft and optionally specific parking spot IDs to be used by this group.
-- @param #RATCRAFT self
-- @param #string airbasename Destination airbase name.
-- @param #table parking (Optional) Table of parking spot IDs. At least the number of units in the group must be given.
-- @return #RATCRAFT self
function RATCRAFT:AddDestinationAirbase(airbasename, parking)
local destination={} --#RATCRAFT.Departure
-- Convert parking IDs to complete parking data.
local spots=nil
if parking then
if type(parking)=="number" then
parking={parking}
end
spots={}
local airbase=AIRBASE:FindByName(airbasename)
for _,TerminalID in pairs(parking) do
local spot=airbase:GetParkingSpotData(TerminalID)
if spot then
table.insert(spots, spot)
end
end
end
destination.name=airbasename
destination.type=RATCRAFT.DeType.AIRBASE
destination.parking=spots
table.insert(self.destinations, destination)
return self
end
--- Add a departure for the aircraft.
-- @param #RATCRAFT self
-- @param Core.Zone#ZONE zone Zone.
-- @return #RATCRAFT self
function RATCRAFT:AddDepartureZone(zone)
local departure={} --#RATCRAFT.Departure
departure.type={}
table.insert(self.departures, zone)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a departure for the aircraft.
-- @param #RATCRAFT self
function RATCRAFT:onafterStart()
-- Create flight groups.
for i=1,self.Nspawn do
-- Group name.
local groupname=string.format("%s-%02d", self.alias, i)
-- New flight group.
local flightgroup=FLIGHTGROUP:New(groupname)
-- Add flight to tabloe.
table.insert(self.flights, flightgroup)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Departure Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get departure.
-- @param #RATCRAFT self
-- @return #RATCRAFT.Departure
-- @return #table Parking data table.
function RATCRAFT:GetDeparture()
-- TODO: for commute/continue, set departure to current airbase/zone. Get same parking spot.
local departures=UTILS.DeepCopy(self.departures)
-- Try each departure in random order.
for i=1,#departures do
-- Roll dice.
local r=math.random(#departures)
-- Get random departure
local departure=departures[r] --#RATCRAFT.Departure
-- Check if airbase or zone.
if departure.type==RATCRAFT.DeType.AIRBASE then
---
-- Airbase
---
-- Departure airbase.
local airbase=AIRBASE:FindByName(departure.name)
if airbase then
-- Template group.
local group=self.templategroup --Wrapper.Group#GROUP
-- Get number of free parking spots.
--local parking=airbase:FindFreeParkingSpotForAircraft(group)
local parking=self:GetParkingDeparture(departure)
-- Check if parking is sufficient.
if parking and #parking>=self.Nunits then
self:I(string.format("Returning departure %s", departure.name))
return departure, parking
else
self:I(string.format("Removing departure %s", departure.name))
-- Remove from table so it does not get selected again.
table.remove(departures, r)
end
else
self:E("ERROR: Could not find departure airbase!")
end
else
---
-- Zone
---
-- Departure airbase.
local zone=ZONE:New(departure.name)
if zone then
-- TODO: check if in range.
return departure
else
self:E("ERROR: Could not find departure zone!")
end
end
end
end
--- Get parking at departure.
-- @param #RATCRAFT self
-- @param #RATCRAFT.Departure departure The chosen departure.
-- @return #table Parking data table or nil if not enough spots.
function RATCRAFT:GetParkingDeparture(departure)
if departure.type==RATCRAFT.DeType.AIRBASE then
local airbase=AIRBASE:FindByName(departure.name)
if airbase then
local parkingdata=airbase:GetParkingSpotsTable()
if departure.parking then
---
-- User specified parking spots
---
-- Check if free?
local parking={}
for _,_spot in pairs(parkingdata) do
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
self:I(string.format("Checking spot %d", spot.TerminalID))
-- Check if spot if free?
if spot.Free then
self:I(string.format("Spot %d is FREE", spot.TerminalID))
-- Check if spot is in user table?
for _,_park in pairs(departure.parking) do
local park=_park --Wrapper.Airbase#AIRBASE.ParkingSpot
if park.TerminalID==spot.TerminalID then
self:I(string.format("Adding spot %d to parking", spot.TerminalID))
table.insert(parking, spot)
end
end
end
end
-- Only return data if sufficient spots available.
if #parking>=self.Nunits then
self:I(string.format("Found valid parking for %d spots", #parking))
return parking
end
else
---
-- Choose parking from what is free
---
local parking={}
for _,_spot in pairs(parkingdata) do
local spot=_spot --Wrapper.Airbase#AIRBASE.ParkingSpot
if spot.Free then
table.insert(parking, spot)
end
if #parking>=self.Nunits then
return parking
end
end
end
end
end
self:I(string.format("Could not find valid parking data"))
return nil
end
--- Get destination.
-- @param #RATCRAFT self
-- @param #RATCRAFT.Departure departure The chosen departure.
-- @return #RATCRAFT.Departure
function RATCRAFT:GetDestination(departure)
if departure==nil then
return nil
end
-- TODO: for commute, switch departure and destination.
-- Create copy of all destinations.
local destinations=UTILS.DeepCopy(self.destinations)
-- Try each destination in random order.
for i=1,#destinations do
-- Roll dice.
local r=math.random(#destinations)
-- Get random departure
local destination=destinations[r] --#RATCRAFT.Departure
-- Check if airbase or zone.
if destination.type==RATCRAFT.DeType.AIRBASE then
---
-- Airbase
---
-- Get airbase.
local airbase=AIRBASE:FindByName(destination.name)
local depart=AIRBASE:FindByName(departure.name)
if airbase then
-- TODO: check
-- coalition, terminal type, distance
-- Distance between departure and destination.
local distance=depart:GetCoordinate():Get2DDistance(airbase:GetCoordinate())
if distance then
self:I(string.format("Returning destination %s", destination.name))
return destination
else
self:I(string.format("Removing destination %s", destination.name))
-- Remove from table so it does not get selected again.
table.remove(destinations, r)
end
else
self:E("ERROR: Could not find airbase!")
end
else
---
-- Zone
---
-- Departure airbase.
local zone=ZONE:New(destination.name)
if zone then
-- TODO: check if in range.
return destination
else
self:E("ERROR: Could not find destination zone!")
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get number of alive groups
-- @param #RATCRAFT self
-- @return #number N live.
function RATCRAFT:_GetAliveGroups()
local n=0
for _,_flight in pairs(self.flights) do
local flight=_flight --Ops.FlightGroup#FLIGHTGROUP
if not flight:IsDead() then
n=n+1
end
end
return n
end
@@ -0,0 +1,757 @@
--- **Functional** - (R2.5) - Simple and Persistent Player Scoring.
--
--
-- Score!
--
--
-- ## Main Features:
--
-- * Good stuff.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Functional.SimpleScore
-- @image Functional_SimpleScore.png
--- SCORE class.
-- @type SCORE
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table menuadded Table of groups the menu was added for.
-- @field #boolean menudisabled If true, F10 menu for players is disabled.
-- @field #table playerscores Player score table.
-- @extends Core.Fsm#FSM
--- Goooooaaaallllll!
--
-- ===
--
-- ![Banner Image](..\Presentations\SCORE\SCORE_Main.png)
--
-- # The Scoring Concept
--
-- Score
--
-- # Basic Script
--
-- -- Create a new score object.
-- score=SCORE:New()
--
-- -- Start score.
-- SCORE:Start()
--
-- # Training Zones
--
-- Players are only protected if they are inside one of the training zones.
--
-- -- Create a new missile trainer object.
-- SCORE=SCORE:New()
--
-- -- Add training zones.
-- SCORE:AddSafeZone(ZONE:New("Training Zone Alpha")
-- SCORE:AddSafeZone(ZONE:New("Training Zone Bravo")
--
-- -- Start missile trainer.
-- SCORE:Start()
--
--
-- Todo!
--
--
-- @field #SCORE
SCORE = {
ClassName = "SCORE",
Debug = false,
lid = nil,
menuadded = {},
menudisabled = nil,
playerscores = nil,
}
--- Player data table holding all important parameters of each player.
-- @type SCORE.PlayerData
-- @field Wrapper.Unit#UNIT unit Aircraft of the player.
-- @field #string unitname Name of the unit.
-- @field Wrapper.Client#CLIENT client Client object of player.
-- @field #string callsign Callsign of player.
-- @field Wrapper.Group#GROUP group Aircraft group of player.
-- @field #string groupname Name of the the player aircraft group.
-- @field #string name Player name.
-- @field #number coalition Coalition number of player.
-- @field #table hit Table of hit units.
--- Hit unit data.
-- @type SCORE.HitData
-- @field #string unitname Name of the hit unit.
-- @field #string category Category of hit unit.
-- @field #number score Score
-- @field #boolean friendly If true, unit is friendly.
-- @field #boolean destroyed If true, unit was destroyed.
--- Create a new SCORE class object.
-- @param #SCORE self
-- @return #SCORE self.
function SCORE:New()
self.lid="SCORE | "
-- Inherit everthing from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #SCORE
-- Defaults:
self:SetDefaultMissileDestruction(true)
self:SetDefaultLaunchAlerts(true)
self:SetDefaultLaunchMarks(true)
self:SetExplosionPower()
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "Running") -- Start SCORE script.
self:AddTransition("*", "Status", "*") -- Status update.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the SCORE. Initializes parameters and starts event handlers.
-- @function [parent=#SCORE] Start
-- @param #SCORE self
--- Triggers the FSM event "Start" after a delay. Starts the SCORE. Initializes parameters and starts event handlers.
-- @function [parent=#SCORE] __Start
-- @param #SCORE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the SCORE and all its event handlers.
-- @param #SCORE self
--- Triggers the FSM event "Stop" after a delay. Stops the SCORE and all its event handlers.
-- @function [parent=#SCORE] __Stop
-- @param #SCORE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status".
-- @function [parent=#SCORE] Status
-- @param #SCORE self
--- Triggers the FSM event "Status" after a delay.
-- @function [parent=#SCORE] __Status
-- @param #SCORE self
-- @param #number delay Delay in seconds.
return self
end
--- On after Start event. Starts the missile trainer and adds event handlers.
-- @param #SCORE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SCORE:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting SCORE v%s", SCORE.version)
env.info(text)
-- Init player scores table.
self.playerscores={}
-- Event handlers
self:HandleEvent(EVENTS.Dead, self._EventDeadOrCrash)
self:HandleEvent(EVENTS.Crash, self._EventDeadOrCrash)
self:HandleEvent(EVENTS.Hit, self._EventHit)
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.PlayerLeaveUnit)
-- Handle events:
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.Shot)
if self.Debug then
self:TraceClass(self.ClassName)
self:TraceLevel(2)
end
self:__Status(-10)
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- SCORE event handler for event birth.
-- @param #SCORE self
-- @param Core.Event#EVENTDATA EventData
function SCORE:OnEventBirth(EventData)
self:F3({eventbirth = EventData})
-- Nil checks.
if EventData==nil then
self:E(self.lid.."ERROR: EventData=nil in event BIRTH!")
self:E(EventData)
return
end
if EventData.IniUnit==nil then
self:E(self.lid.."ERROR: EventData.IniUnit=nil in event BIRTH!")
self:E(EventData)
return
end
-- Player unit and name.
local _unitName=EventData.IniUnitName
local playerunit, playername=self:_GetPlayerUnitAndName(_unitName)
-- Debug info.
self:T(self.lid.."BIRTH: unit = "..tostring(EventData.IniUnitName))
self:T(self.lid.."BIRTH: group = "..tostring(EventData.IniGroupName))
self:T(self.lid.."BIRTH: player = "..tostring(playername))
-- Check if player entered.
if playerunit and playername then
local _uid=playerunit:GetID()
local _group=playerunit:GetGroup()
local _callsign=playerunit:GetCallsign()
-- Debug output.
local text=string.format("Pilot %s, callsign %s entered unit %s of group %s.", playername, _callsign, _unitName, _group:GetName())
self:T(self.lid..text)
MESSAGE:New(text, 5):ToAllIf(self.Debug)
-- Add F10 radio menu for player.
if not self.menudisabled then
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
end
-- Player data.
local playerData={} --#SCORE.PlayerData
-- Player unit, client and callsign.
playerData.unit = playerunit
playerData.unitname = _unitName
playerData.group = _group
playerData.groupname = _group:GetName()
playerData.name = playername
playerData.callsign = playerData.unit:GetCallsign()
playerData.client = CLIENT:FindByName(_unitName, nil, true)
playerData.coalition = _group:GetCoalition()
playerData.hit = playerData.hit or {}
-- Init player data.
self.players[playername]=playerData
end
end
--- Handles the OnHit event for the scoring.
-- @param #SCORE self
-- @param Core.Event#EVENTDATA Event
function SCORE:_EventOnHit(Event)
self:F( { Event } )
local InitUnit = nil
local InitUNIT = nil
local InitUnitName = ""
local InitGroup = nil
local InitGroupName = ""
local InitPlayerName = nil
local InitCoalition = nil
local InitCategory = nil
local InitType = nil
local InitUnitCoalition = nil
local InitUnitCategory = nil
local InitUnitType = nil
local TargetUnit = nil
local TargetUNIT = nil
local TargetUnitName = ""
local TargetGroup = nil
local TargetGroupName = ""
local TargetPlayerName = nil
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
InitUnit = Event.IniDCSUnit
InitUNIT = Event.IniUnit
InitUnitName = Event.IniDCSUnitName
InitGroup = Event.IniDCSGroup
InitGroupName = Event.IniDCSGroupName
InitPlayerName = Event.IniPlayerName
InitCoalition = Event.IniCoalition
InitCategory = Event.IniCategory
InitType = Event.IniTypeName
InitUnitCoalition = _SCORINGCoalition[InitCoalition]
InitUnitCategory = _SCORINGCategory[InitCategory]
InitUnitType = InitType
self:T( { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } )
end
if Event.TgtDCSUnit then
TargetUnit = Event.TgtDCSUnit
TargetUNIT = Event.TgtUnit
TargetUnitName = Event.TgtDCSUnitName
TargetGroup = Event.TgtDCSGroup
TargetGroupName = Event.TgtDCSGroupName
TargetPlayerName = Event.TgtPlayerName
TargetCoalition = Event.TgtCoalition
TargetCategory = Event.TgtCategory
TargetType = Event.TgtTypeName
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
end
-- Current time.
local Tnow=timer.getTime()
if InitPlayerName~=nil then -- It is a player that is hitting something
self:T("Hitting Something")
-- What is he hitting?
if TargetCategory then
-- A target got hit, score it.
-- Player contains the score data from self.Players[InitPlayerName]
local player=self.Players[InitPlayerName] --#SCORE.PlayerData
-- Hit table.
player.hit[TargetUnitName]=player.hit[TargetUnitName] or {}
local hit=player.hit[TargetUnitName] --#SCORE.HitData
hit.score = hit.score or 0
hit.penalty = hit.penalty or 0
hit.timeStamp = hit.timeStamp or 0
hit.unit = hit.unit or TargetUNIT
hit.threatLevel, hit.threatType = hit.unit:GetThreatLevel()
-- Only grant hit scores if there was more than one second between the last hit.
if Tnow-hit.timeStamp>1 then
-- Update timestamp
hit.timeStamp = Tnow
if TargetPlayerName~=nil then
-- It is a player hitting another player ...
hit.otherplayer=true
end
local Score = 0
if InitCoalition then -- A coalition object was hit.
if InitCoalition == TargetCoalition then
------------------
-- Hit Friendly --
------------------
hit.friendly=true
player.penalty = player.penalty-10
hit.penalty=hit.penalty-10
-- Check if target was another player.
if TargetPlayerName~=nil then -- It is a player hitting another player ...
else
end
-- Write penalty to CSV file.
--self:ScoreCSV( InitPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
----------------
-- Hit Enemey --
----------------
hit.friendly=true
player.score=player.score+1
hit.score=hit.score+1
--hit.scoreHit = hit.ScoreHit + 1
-- Check if it was a player.
if TargetPlayerName~=nil then -- It is a player hitting another player ...
else
end
-- Write score to CSV file. Score 1 PT.
end
else -- A scenery object was hit.
end
end
end
end
end
--- Track DEAD or CRASH events for the scoring.
-- @param #SCORING self
-- @param Core.Event#EVENTDATA Event
function SCORING:_EventOnDeadOrCrash( Event )
self:F( { Event } )
local TargetUnit = nil
local TargetGroup = nil
local TargetUnitName = ""
local TargetGroupName = ""
local TargetPlayerName = ""
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if Event.IniDCSUnit then
TargetUnit = Event.IniUnit
TargetUnitName = Event.IniDCSUnitName
TargetGroup = Event.IniDCSGroup
TargetGroupName = Event.IniDCSGroupName
TargetPlayerName = Event.IniPlayerName
TargetCoalition = Event.IniCoalition
TargetCategory = Event.IniCategory
TargetType = Event.IniTypeName
TargetUnitCoalition = _SCORINGCoalition[TargetCoalition]
TargetUnitCategory = _SCORINGCategory[TargetCategory]
TargetUnitType = TargetType
self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
-- Player contains the score and reference data for the player.
for PlayerName, _player in pairs(self.Players) do
local player=_player --#SCORE.PlayerData
if player and player.unit and player.unit:IsAlive() then
self:T( "Something got destroyed" )
-- Some variables
local InitUnitName = Player.UnitName
local InitUnitType = Player.UnitType
local InitCoalition = Player.UnitCoalition
local InitCategory = Player.UnitCategory
local InitUnitCoalition = _SCORINGCoalition[InitCoalition]
local InitUnitCategory = _SCORINGCategory[InitCategory]
self:T( { InitUnitName, InitUnitType, InitUnitCoalition, InitCoalition, InitUnitCategory, InitCategory } )
local Destroyed = false
local hit=player.hit[TargetUnitName] --#SCORE.HitData
-- What is the player destroying?
if hit and hit.timeStamp~=0 then -- Was there a hit for this unit for this player before registered?
local TargetThreatLevel = hit.threatLevel
local TargetThreatType = hit.threatType
Player.Destroy[TargetCategory] = Player.Destroy[TargetCategory] or {}
Player.Destroy[TargetCategory][TargetType] = Player.Destroy[TargetCategory][TargetType] or {}
local destroy={} --#SCORE.DestroyData
-- PlayerDestroy contains the destroy score data per category and target type of the player.
local TargetDestroy = Player.Destroy[TargetCategory][TargetType]
TargetDestroy.Score = TargetDestroy.Score or 0
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy or 0
TargetDestroy.Penalty = TargetDestroy.Penalty or 0
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy or 0
if TargetCoalition then
if InitCoalition == TargetCoalition then
---
-- Hit Friendly
---
local ThreatLevelTarget = TargetThreatLevel
local ThreatTypeTarget = TargetThreatType
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
local ThreatPenalty = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyPenalty / 10 )
self:F( { ThreatLevel = ThreatPenalty, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Penalty = Player.Penalty + ThreatPenalty
TargetDestroy.Penalty = TargetDestroy.Penalty + ThreatPenalty
TargetDestroy.PenaltyDestroy = TargetDestroy.PenaltyDestroy + 1
if Player.HitPlayers[TargetPlayerName] then
-- A player destroyed another friendly player.
else
-- A player destroyed another friendly target.
end
-- Write CSV penalty.
Destroyed = true
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_PENALTY", 1, ThreatPenalty, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
---
-- Hit Enemy
---
local ThreatLevelTarget = TargetThreatLevel
local ThreatTypeTarget = TargetThreatType
local ThreatLevelPlayer = Player.ThreatLevel / 10 + 1
local ThreatScore = math.ceil( ( ThreatLevelTarget / ThreatLevelPlayer ) * self.ScaleDestroyScore / 10 )
self:F( { ThreatLevel = ThreatScore, ThreatLevelTarget = ThreatLevelTarget, ThreatTypeTarget = ThreatTypeTarget, ThreatLevelPlayer = ThreatLevelPlayer } )
Player.Score = Player.Score + ThreatScore
TargetDestroy.Score = TargetDestroy.Score + ThreatScore
TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1
if Player.HitPlayers[TargetPlayerName] then
-- A player destroyed another enemy player.
else
-- A player destroyed another enemy target.
end
-- Write CSV.
Destroyed = true
--self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, ThreatScore, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
local UnitName = TargetUnit:GetName()
-- Scoring for special objects.
local Score = self.ScoringObjects[UnitName]
if Score then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Special target '" .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. " destroyed! " ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information
)
:ToAllIf( self:IfMessagesScore() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesScore() and self:IfMessagesToCoalition() )
-- Write CSV file.
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
Destroyed = true
end
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
self:F( { ScoringZone = ScoreZoneData } )
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
-- Check if target is in zone.
if ScoreZone:IsVec2InZone( TargetUnit:GetVec2() ) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Target destroyed in zone '" .. ScoreZone:GetName() .. "'." ..
"Player '" .. PlayerName .. "' receives an extra " .. Score .. " points! " ..
"Total: " .. Player.Score - Player.Penalty,
MESSAGE.Type.Information )
:ToAllIf( self:IfMessagesZone() and self:IfMessagesToAll() )
:ToCoalitionIf( InitCoalition, self:IfMessagesZone() and self:IfMessagesToCoalition() )
-- Write CSV.
self:ScoreCSV( PlayerName, TargetPlayerName, "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
Destroyed = true
end
end
end
else
---
-- Scenery Destroyed
---
-- Check if there are Zones where the destruction happened.
for ZoneName, ScoreZoneData in pairs(self.ScoringZones) do
local ScoreZone = ScoreZoneData.ScoreZone -- Core.Zone#ZONE_BASE
local Score = ScoreZoneData.Score
if ScoreZone:IsVec2InZone(TargetUnit:GetVec2()) then
Player.Score = Player.Score + Score
TargetDestroy.Score = TargetDestroy.Score + Score
-- Player destroyed scenery in scoring zone.
--Write CSV.
Destroyed = true
--self:ScoreCSV( PlayerName, "", "DESTROY_SCORE", 1, Score, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, "", "Scenery", TargetUnitType )
end
end
end
-- Delete now the hit cache if the target was destroyed.
-- Otherwise points will be granted every time a target gets killed by the players that hit that target.
-- This is only relevant for player to player destroys.
if Destroyed then
Player.Hit[TargetCategory][TargetUnitName].TimeStamp = 0
end
end
end
end
end
--- On before "Save" event. Checks if io and lfs are available.
-- @param #SCORE 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 the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
function SCORE:onbeforeSave(From, Event, To, path, filename)
-- Check io module is available.
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save player grades.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. Results will be saved in DCS installation root directory rather than your \"Saved Games/DCS\" folder.")
end
return true
end
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Save" event. Player data is saved to file.
-- @param #SCORE 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 the player grades. Default is "SCORE-<ALIAS>_LSOgrades.csv".
function SCORE:onafterSave(From, Event, To, path, filename)
--- Function that saves data to file
local function _savefile(filename, data)
local f = io.open(filename, "wb")
if f then
f:write(data)
f:close()
end
end
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set file name.
filename=filename or string.format("Score.csv")
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Header line
local scores="Name,Pass,Points Final,Points Pass,Grade,Details,Wire,Tgroove,Case,Wind,Modex,Airframe,Carrier Type,Carrier Name,Theatre,Mission Time,Mission Date,OS Date\n"
local scores="Name,Player Unit,Player Coalition,Player Category,Score Type,Target Category,Target Name, Target Coalition,Target Player,Target Score,Mission Date, Mission Time, OS Date"
-- Loop over all players.
local n=0
for playername,grades in pairs(self.playerscores) do
-- Loop over player grades table.
for i,_grade in pairs(grades) do
local grade=_grade --#SCORE.LSOgrade
-- Check some stuff that could be nil.
local wire="n/a"
if grade.wire and grade.wire<=4 then
wire=tostring(grade.wire)
end
local Tgroove="n/a"
if grade.Tgroove and grade.Tgroove<=360 and grade.case<3 then
Tgroove=tostring(UTILS.Round(grade.Tgroove, 1))
end
local finalscore="n/a"
if grade.finalscore then
finalscore=tostring(UTILS.Round(grade.finalscore, 1))
end
-- Compile grade line.
scores=scores..string.format("%s,%d,%s,%.1f,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
playername, i, finalscore, grade.points, grade.grade, grade.details, wire, Tgroove, grade.case,
grade.wind, grade.modex, grade.airframe, grade.carriertype, grade.carriername, grade.theatre, grade.mitime, grade.midate, grade.osdate)
n=n+1
end
end
-- Info
local text=string.format("Saving %d player LSO grades to file %s", n, filename)
self:I(self.lid..text)
-- Save file.
_savefile(filename, scores)
end
+33 -16
View File
@@ -284,9 +284,11 @@ function SCORING:New( GameName )
-- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked).
self:SetFratricide( self.ScaleDestroyPenalty * 3 )
self.penaltyonfratricide = true
-- Default penalty when a player changes coalition.
self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty )
self.penaltyoncoalitionchange = true
self:SetDisplayMessagePrefix()
@@ -582,6 +584,23 @@ function SCORING:SetFratricide( Fratricide )
return self
end
--- Decide if fratricide is leading to penalties (true) or not (false)
-- @param #SCORING self
-- @param #boolean OnOff Switch for Fratricide
-- @return #SCORING
function SCORING:SwitchFratricide( OnOff )
self.penaltyonfratricide = OnOff
return self
end
--- Decide if a change of coalition is leading to penalties (true) or not (false)
-- @param #SCORING self
-- @param #boolean OnOff Switch for Coalition Changes.
-- @return #SCORING
function SCORING:SwitchTreason( OnOff )
self.penaltyoncoalitionchange = OnOff
return self
end
--- When a player changes the coalition, he can receive a penalty score.
-- Use the method @{#SCORING.SetCoalitionChangePenalty}() to define the penalty when a player changes coalition.
@@ -647,14 +666,15 @@ function SCORING:_AddPlayerFromUnit( UnitData )
if not self.Players[PlayerName].UnitCoalition then
self.Players[PlayerName].UnitCoalition = UnitCoalition
else
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
-- TODO: switch for coalition changes, make penalty alterable
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."Penalty points added.",
MESSAGE.Type.Information
):ToAll()
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
end
end
@@ -666,10 +686,9 @@ function SCORING:_AddPlayerFromUnit( UnitData )
self.Players[PlayerName].UNIT = UnitData
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
self.Players[PlayerName].ThreatType = UnitThreatType
-- TODO: DCS bug concerning Units with skill level client don't get destroyed in multi player. This logic is deactivated until this bug gets fixed.
--[[
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 then
-- TODO: make fratricide switchable
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then
if self.Players[PlayerName].PenaltyWarning < 1 then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "': WARNING! If you continue to commit FRATRICIDE and have a PENALTY score higher than " .. self.Fratricide .. ", you will be COURT MARTIALED and DISMISSED from this mission! \nYour total penalty is: " .. self.Players[PlayerName].Penalty,
MESSAGE.Type.Information
@@ -678,14 +697,12 @@ function SCORING:_AddPlayerFromUnit( UnitData )
end
end
if self.Players[PlayerName].Penalty > self.Fratricide then
if self.Players[PlayerName].Penalty > self.Fratricide and self.penaltyonfratricide then
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' committed FRATRICIDE, he will be COURT MARTIALED and is DISMISSED from this mission!",
MESSAGE.Type.Information
):ToAll()
UnitData:GetGroup():Destroy()
end
--]]
end
end
@@ -1125,9 +1142,9 @@ 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
PlayerHit.Penalty = PlayerHit.Penalty + 10
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty
PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty
MESSAGE
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
@@ -1687,7 +1704,7 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
@@ -1743,7 +1760,7 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
self:F( { ReportMissions, ScoreMissions, PenaltyMissions } )
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + ScoreGoals + PenaltyMissions
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
PlayerMessage =
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
+348 -154
View File
@@ -1,56 +1,65 @@
--- **Functional** -- Make SAM sites execute evasive and defensive behaviour when being fired upon.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * When SAM sites are being fired upon, the SAMs will take evasive action will reposition themselves when possible.
-- * When SAM sites are being fired upon, the SAMs will take defensive action by shutting down their radars.
--
-- * SEAD calculates the time it takes for a HARM to reach the target - and will attempt to minimize the shut-down time.
-- * Detection and evasion of shots has a random component based on the skill level of the SAM groups.
--
-- ===
--
--
-- ## Missions:
--
--
-- [SEV - SEAD Evasion](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SEV%20-%20SEAD%20Evasion)
--
--
-- ===
--
--
-- ### Authors: **FlightControl**, **applevangelist**
--
-- Last Update: Aug 2021
--
--
-- Last Update: Feb 2022
--
-- ===
--
--
-- @module Functional.Sead
-- @image SEAD.JPG
---
---
-- @type SEAD
-- @extends Core.Base#BASE
--- Make SAM sites execute evasive and defensive behaviour when being fired upon.
--
--
-- This class is very easy to use. Just setup a SEAD object by using @{#SEAD.New}() and SAMs will evade and take defensive action when being fired upon.
-- Once a HARM attack is detected, SEAD will shut down the radars of the attacked SAM site and take evasive action by moving the SAM
-- vehicles around (*if* they are drivable, that is). There's a component of randomness in detection and evasion, which is based on the
-- skill set of the SAM set (the higher the skill, the more likely). When a missile is fired from far away, the SAM will stay active for a
-- period of time to stay defensive, before it takes evasive actions.
--
-- # Constructor:
--
--
-- Use the @{#SEAD.New}() constructor to create a new SEAD object.
--
--
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
--
--
-- @field #SEAD
SEAD = {
ClassName = "SEAD",
ClassName = "SEAD",
TargetSkill = {
Average = { Evade = 30, DelayOn = { 40, 60 } } ,
Good = { Evade = 20, DelayOn = { 30, 50 } } ,
High = { Evade = 15, DelayOn = { 20, 40 } } ,
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
Excellent = { Evade = 10, DelayOn = { 10, 30 } }
},
SEADGroupPrefixes = {},
SuppressedGroups = {},
EngagementRange = 75, -- default 75% engagement range Feature Request #1355
Padding = 10,
CallBack = nil,
UseCallBack = false,
debug = false,
}
--- Missile enumerators
@@ -68,8 +77,11 @@ SEAD = {
["X_25"] = "X_25",
["X_31"] = "X_31",
["Kh25"] = "Kh25",
["BGM_109"] = "BGM_109",
["AGM_154"] = "AGM_154",
["HY-2"] = "HY-2",
}
--- Missile enumerators - from DCS ME and Wikipedia
-- @field HarmData
SEAD.HarmData = {
@@ -77,7 +89,7 @@ SEAD = {
["AGM_88"] = { 150, 3},
["AGM_45"] = { 12, 2},
["AGM_122"] = { 16.5, 2.3},
["AGM_84"] = { 280, 0.85},
["AGM_84"] = { 280, 0.8},
["ALARM"] = { 45, 2},
["LD-10"] = { 60, 4},
["X_58"] = { 70, 4},
@@ -85,24 +97,27 @@ SEAD = {
["X_25"] = { 25, 0.76},
["X_31"] = {150, 3},
["Kh25"] = {25, 0.8},
["BGM_109"] = {460, 0.705}, --in-game ~465kn
["AGM_154"] = {130, 0.61},
["HY-2"] = {90,1},
}
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
-- Chances are big that the missile will miss.
-- @param #SEAD self
-- @param #table SEADGroupPrefixes Table of #string entries or single #string, which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken.
-- @param #number Padding (Optional) Extra number of seconds to add to radar switch-back-on time
-- @return SEAD
-- @return #SEAD self
-- @usage
-- -- CCCP SEAD Defenses
-- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes, Padding )
local self = BASE:Inherit( self, BASE:New() )
self:F( SEADGroupPrefixes )
local self = BASE:Inherit( self, FSM:New() )
self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@@ -110,25 +125,36 @@ function SEAD:New( SEADGroupPrefixes, Padding )
else
self.SEADGroupPrefixes[SEADGroupPrefixes] = SEADGroupPrefixes
end
local padding = Padding or 10
if padding < 10 then padding = 10 end
self.Padding = padding
self.UseEmissionsOnOff = true
self.debug = false
self.CallBack = nil
self.UseCallBack = false
self:HandleEvent( EVENTS.Shot, self.HandleEventShot )
self:I("*** SEAD - Started Version 0.3.1")
-- Start State.
self:SetStartState("Running")
self:AddTransition("*", "ManageEvasion", "*")
self:AddTransition("*", "CalculateHitZone", "*")
self:I("*** SEAD - Started Version 0.4.3")
return self
end
--- Update the active SEAD Set
--- Update the active SEAD Set (while running)
-- @param #SEAD self
-- @param #table SEADGroupPrefixes The prefixes to add, note: can also be a single #string
-- @return #SEAD self
function SEAD:UpdateSet( SEADGroupPrefixes )
self:T( SEADGroupPrefixes )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
@@ -142,8 +168,8 @@ end
--- Sets the engagement range of the SAMs. Defaults to 75% to make it more deadly. Feature Request #1355
-- @param #SEAD self
-- @param #number range Set the engagement range in percent, e.g. 50
-- @return self
-- @param #number range Set the engagement range in percent, e.g. 55 (default 75)
-- @return #SEAD self
function SEAD:SetEngagementRange(range)
self:T( { range } )
range = range or 75
@@ -157,7 +183,8 @@ end
--- Set the padding in seconds, which extends the radar off time calculated by SEAD
-- @param #SEAD self
-- @param #number Padding Extra number of seconds to add for the switch-on
-- @param #number Padding Extra number of seconds to add for the switch-on (default 10 seconds)
-- @return #SEAD self
function SEAD:SetPadding(Padding)
self:T( { Padding } )
local padding = Padding or 10
@@ -166,59 +193,264 @@ function SEAD:SetPadding(Padding)
return self
end
--- Check if a known HARM was fired
-- @param #SEAD self
-- @param #string WeaponName
-- @return #boolean Returns true for a match
-- @return #string name Name of hit in table
function SEAD:_CheckHarms(WeaponName)
self:T( { WeaponName } )
local hit = false
local name = ""
for _,_name in pairs (SEAD.Harms) do
if string.find(WeaponName,_name,1) then
hit = true
name = _name
break
end
end
return hit, name
end
--- Set SEAD to use emissions on/off in addition to alarm state.
-- @param #SEAD self
-- @param #boolean Switch True for on, false for off.
-- @return #SEAD self
function SEAD:SwitchEmissions(Switch)
self:T({Switch})
self.UseEmissionsOnOff = Switch
return self
end
--- (Internal) Return distance in meters between two coordinates or -1 on error.
-- @param #SEAD self
-- @param Core.Point#COORDINATE _point1 Coordinate one
-- @param Core.Point#COORDINATE _point2 Coordinate two
-- @return #number Distance in meters
function SEAD:_GetDistance(_point1, _point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2)
--self:T({dist1=distance1, dist2=distance2})
if distance1 and type(distance1) == "number" then
return distance1
elseif distance2 and type(distance2) == "number" then
return distance2
else
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return -1
--- Add an object to call back when going evasive.
-- @param #SEAD self
-- @param #table Object The object to call. Needs to have object functions as follows:
-- `:SeadSuppressionPlanned(Group, Name, SuppressionStartTime, SuppressionEndTime)`
-- `:SeadSuppressionStart(Group, Name)`,
-- `:SeadSuppressionEnd(Group, Name)`,
-- @return #SEAD self
function SEAD:AddCallBack(Object)
self:T({Class=Object.ClassName})
self.CallBack = Object
self.UseCallBack = true
return self
end
--- (Internal) Check if a known HARM was fired
-- @param #SEAD self
-- @param #string WeaponName
-- @return #boolean Returns true for a match
-- @return #string name Name of hit in table
function SEAD:_CheckHarms(WeaponName)
self:T( { WeaponName } )
local hit = false
local name = ""
for _,_name in pairs (SEAD.Harms) do
if string.find(WeaponName,_name,1,true) then
hit = true
name = _name
break
end
end
return hit, name
end
--- (Internal) Return distance in meters between two coordinates or -1 on error.
-- @param #SEAD self
-- @param Core.Point#COORDINATE _point1 Coordinate one
-- @param Core.Point#COORDINATE _point2 Coordinate two
-- @return #number Distance in meters
function SEAD:_GetDistance(_point1, _point2)
self:T("_GetDistance")
if _point1 and _point2 then
local distance1 = _point1:Get2DDistance(_point2)
local distance2 = _point1:DistanceFromPointVec2(_point2)
--self:T({dist1=distance1, dist2=distance2})
if distance1 and type(distance1) == "number" then
return distance1
elseif distance2 and type(distance2) == "number" then
return distance2
else
self:E("******Cannot calculate distance!")
self:E("*****Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
else
self:E("******Cannot calculate distance!")
self:E({_point1,_point2})
return -1
end
end
--- (Internal) Calculate hit zone of an AGM-88
-- @param #SEAD self
-- @param #table SEADWeapon DCS.Weapon object
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
-- @param #number height Height when the missile was fired
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
-- @param #string SEADWeaponName Weapon Name
-- @return #SEAD self
function SEAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup,SEADWeaponName)
self:T("**** Calculating hit zone for " .. (SEADWeaponName or "None"))
if SEADWeapon and SEADWeapon:isExist() then
--local pos = SEADWeapon:getPoint()
-- postion and height
local position = SEADWeapon:getPosition()
local mheight = height
-- heading
local wph = math.atan2(position.x.z, position.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
-- velocity
local wpndata = SEAD.HarmData["AGM_88"]
if string.find(SEADWeaponName,"154",1) then
wpndata = SEAD.HarmData["AGM_154"]
end
local mveloc = math.floor(wpndata[2] * 340.29)
local c1 = (2*mheight*9.81)/(mveloc^2)
local c2 = (mveloc^2) / 9.81
local Ropt = c2 * math.sqrt(c1+1)
if height <= 5000 then
Ropt = Ropt * 0.72
elseif height <= 7500 then
Ropt = Ropt * 0.82
elseif height <= 10000 then
Ropt = Ropt * 0.87
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
-- look at a couple of zones across the trajectory
for n=1,3 do
local dist = Ropt - ((n-1)*20000)
local predpos= pos0:Translate(dist,wph)
if predpos then
--- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
-- @see SEAD
-- @param #SEAD
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterZones({targetzone}):FilterOnce()
local tgtcoord = targetzone:GetRandomPointVec2()
--if tgtcoord and tgtcoord.ClassName == "COORDINATE" then
--local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtgrp = seadset:GetRandom()
local _targetgroup = nil
local _targetgroupname = "none"
local _targetskill = "Random"
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
self:ManageEvasion(_targetskill,_targetgroup,pos0,"AGM_88",SEADGroup, 20)
end
--end
end
end
end
return self
end
--- (Internal) Handle Evasion
-- @param #SEAD self
-- @param #string _targetskill
-- @param Wrapper.Group#GROUP _targetgroup
-- @param Core.Point#COORDINATE SEADPlanePos
-- @param #string SEADWeaponName
-- @param Wrapper.Group#GROUP SEADGroup Attacker Group
-- @param #number timeoffset Offset for tti calc
-- @return #SEAD self
function SEAD:onafterManageEvasion(From,Event,To,_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup,timeoffset)
local timeoffset = timeoffset or 0
if _targetskill == "Random" then -- when skill is random, choose a skill
local Skills = { "Average", "Good", "High", "Excellent" }
_targetskill = Skills[ math.random(1,4) ]
end
--self:T( _targetskill )
if self.TargetSkill[_targetskill] then
local _evade = math.random (1,100) -- random number for chance of evading action
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T("*** SEAD - Evading")
-- calculate distance of attacker
local _targetpos = _targetgroup:GetCoordinate()
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
-- weapon speed
local hit, data = self:_CheckHarms(SEADWeaponName)
local wpnspeed = 666 -- ;)
local reach = 10
if hit then
local wpndata = SEAD.HarmData[data]
reach = wpndata[1] * 1,1
local mach = wpndata[2]
wpnspeed = math.floor(mach * 340.29)
end
-- time to impact
local _tti = math.floor(_distance / wpnspeed) - timeoffset -- estimated impact time
if _distance > 0 then
_distance = math.floor(_distance / 1000) -- km
else
_distance = 0
end
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
if reach >= _distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
local name = args[2] -- #string Group Name
local attacker = args[3] -- Wrapper.Group#GROUP
if self.UseEmissionsOnOff then
grp:EnableEmission(false)
end
grp:OptionAlarmStateGreen() -- needed else we cannot move around
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionStart(grp,name,attacker)
end
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
local name = args[2] -- #string Group Nam
if self.UseEmissionsOnOff then
grp:EnableEmission(true)
end
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[name] = false
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionEnd(grp,name)
end
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if delay > _tti then delay = delay / 2 end -- speed up
if _tti > 600 then delay = _tti - 90 end -- shot from afar, 600 is default shorad ontime
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
local _targetgroupname = _targetgroup:GetName()
if not self.SuppressedGroups[_targetgroupname] then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname, SEADGroup},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname] = true
if self.UseCallBack then
local object = self.CallBack
object:SeadSuppressionPlanned(_targetgroup,_targetgroupname,SuppressionStartTime,SuppressionEndTime, SEADGroup)
end
end
end
end
end
return self
end
--- (Internal) Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
-- @param #SEAD self
-- @param Core.Event#EVENTDATA EventData
-- @return #SEAD self
function SEAD:HandleEventShot( EventData )
self:T( { EventData.id } )
local SEADPlane = EventData.IniUnit -- Wrapper.Unit#UNIT
local SEADGroup = EventData.IniGroup -- Wrapper.Group#GROUP
local SEADPlanePos = SEADPlane:GetCoordinate() -- Core.Point#COORDINATE
local SEADUnit = EventData.IniDCSUnit
local SEADUnitName = EventData.IniDCSUnitName
@@ -227,100 +459,62 @@ function SEAD:HandleEventShot( EventData )
self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName)
--self:T({ SEADWeapon })
if self:_CheckHarms(SEADWeaponName) then
self:T( '*** SEAD - Weapon Match' )
local _targetskill = "Random"
local _targetgroupname = "none"
local _target = EventData.Weapon:getTarget() -- Identify target
local _targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
if not _target or self.debug then -- AGM-88 or 154 w/o target data
self:E("***** SEAD - No target data for " .. (SEADWeaponName or "None"))
if string.find(SEADWeaponName,"AGM_88",1,true) or string.find(SEADWeaponName,"AGM_154",1,true) then
self:I("**** Tracking AGM-88/154 with no target data.")
local pos0 = SEADPlane:GetCoordinate()
local fheight = SEADPlane:GetHeight()
self:__CalculateHitZone(20,SEADWeapon,pos0,fheight,SEADGroup,SEADWeaponName)
end
return self
end
local targetcat = _target:getCategory() -- Identify category
local _targetUnit = nil -- Wrapper.Unit#UNIT
local _targetgroup = nil -- Wrapper.Group#GROUP
if _targetUnit and _targetUnit:IsAlive() then
_targetgroup = _targetUnit:GetGroup()
_targetgroupname = _targetgroup:GetName() -- group name
local _targetUnitName = _targetUnit:GetName()
_targetUnit:GetSkill()
_targetskill = _targetUnit:GetSkill()
self:T(string.format("*** Targetcat = %d",targetcat))
if targetcat == Object.Category.UNIT then -- UNIT
self:T("*** Target Category UNIT")
_targetUnit = UNIT:Find(_target) -- Wrapper.Unit#UNIT
if _targetUnit and _targetUnit:IsAlive() then
_targetgroup = _targetUnit:GetGroup()
_targetgroupname = _targetgroup:GetName() -- group name
local _targetUnitName = _targetUnit:GetName()
_targetUnit:GetSkill()
_targetskill = _targetUnit:GetSkill()
end
elseif targetcat == Object.Category.STATIC then
self:T("*** Target Category STATIC")
local seadset = SET_GROUP:New():FilterPrefixes(self.SEADGroupPrefixes):FilterOnce()
local targetpoint = _target:getPoint() or {x=0,y=0,z=0}
local tgtcoord = COORDINATE:NewFromVec3(targetpoint)
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
end
end
-- see if we are shot at
local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
self:T( SEADGroupPrefix )
if string.find( _targetgroupname, SEADGroupPrefix, 1, true ) then
self:T("Target = ".. _targetgroupname .. " | Prefix = " .. SEADGroupPrefix )
if string.find( _targetgroupname, SEADGroupPrefix,1,true ) then
SEADGroupFound = true
self:T( '*** SEAD - Group Match Found' )
break
end
end
end
if SEADGroupFound == true then -- yes we are being attacked
if _targetskill == "Random" then -- when skill is random, choose a skill
local Skills = { "Average", "Good", "High", "Excellent" }
_targetskill = Skills[ math.random(1,4) ]
end
--self:T( _targetskill )
if self.TargetSkill[_targetskill] then
local _evade = math.random (1,100) -- random number for chance of evading action
if (_evade > self.TargetSkill[_targetskill].Evade) then
self:T("*** SEAD - Evading")
-- calculate distance of attacker
local _targetpos = _targetgroup:GetCoordinate()
local _distance = self:_GetDistance(SEADPlanePos, _targetpos)
-- weapon speed
local hit, data = self:_CheckHarms(SEADWeaponName)
local wpnspeed = 666 -- ;)
local reach = 10
if hit then
local wpndata = SEAD.HarmData[data]
reach = wpndata[1] * 1,1
local mach = wpndata[2]
wpnspeed = math.floor(mach * 340.29)
end
-- time to impact
local _tti = math.floor(_distance / wpnspeed) -- estimated impact time
if _distance > 0 then
_distance = math.floor(_distance / 1000) -- km
else
_distance = 0
end
self:T( string.format("*** SEAD - target skill %s, distance %dkm, reach %dkm, tti %dsec", _targetskill, _distance,reach,_tti ))
if reach >= _distance then
self:T("*** SEAD - Shot in Reach")
local function SuppressionStart(args)
self:T(string.format("*** SEAD - %s Radar Off & Relocating",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
grp:OptionAlarmStateGreen()
grp:RelocateGroundRandomInRadius(20,300,false,false,"Diamond")
end
local function SuppressionStop(args)
self:T(string.format("*** SEAD - %s Radar On",args[2]))
local grp = args[1] -- Wrapper.Group#GROUP
grp:OptionAlarmStateRed()
grp:OptionEngageRange(self.EngagementRange)
self.SuppressedGroups[args[2]] = false
end
-- randomize switch-on time
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if delay > _tti then delay = delay / 2 end -- speed up
if _tti > (3*delay) then delay = (_tti / 2) * 0.9 end -- shot from afar
local SuppressionStartTime = timer.getTime() + delay
local SuppressionEndTime = timer.getTime() + _tti + self.Padding
if not self.SuppressedGroups[_targetgroupname] then
self:T(string.format("*** SEAD - %s | Parameters TTI %ds | Switch-Off in %ds",_targetgroupname,_tti,delay))
timer.scheduleFunction(SuppressionStart,{_targetgroup,_targetgroupname},SuppressionStartTime)
timer.scheduleFunction(SuppressionStop,{_targetgroup,_targetgroupname},SuppressionEndTime)
self.SuppressedGroups[_targetgroupname] = true
end
end
end
end
self:ManageEvasion(_targetskill,_targetgroup,SEADPlanePos,SEADWeaponName,SEADGroup)
end
end
return self
end
+130 -11
View File
@@ -18,7 +18,7 @@
-- @module Functional.Shorad
-- @image Functional.Shorad.jpg
--
-- Date: July 2021
-- Date: Nov 2021
-------------------------------------------------------------------------
--- **SHORAD** class, extends Core.Base#BASE
@@ -41,6 +41,7 @@
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
-- @extends Core.Base#BASE
--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie)
--
-- Simple Class for a more intelligent Short Range Air Defense System
@@ -131,6 +132,7 @@ do
["Kh29"] = "Kh29",
["Kh31"] = "Kh31",
["Kh66"] = "Kh66",
--["BGM_109"] = "BGM_109",
}
--- Instantiates a new SHORAD object
@@ -138,13 +140,13 @@ do
-- @param #string Name Name of this SHORAD
-- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP
-- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend
-- @param #number Radius Defense radius in meters, used to switch on groups
-- @param #number Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
-- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call
-- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral"
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
-- @retunr #SHORAD self
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, FSM:New() )
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
@@ -162,11 +164,17 @@ do
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
self:I("*** SHORAD - Started Version 0.2.8")
self:I("*** SHORAD - Started Version 0.3.1")
-- Set the string id for output to DCS.log file.
self.lid=string.format("SHORAD %s | ", self.name)
self:_InitState()
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
-- Start State.
self:SetStartState("Running")
self:AddTransition("*", "WakeUpShorad", "*")
self:AddTransition("*", "CalculateHitZone", "*")
return self
end
@@ -310,7 +318,7 @@ do
local hit = false
if self.DefendHarms then
for _,_name in pairs (SHORAD.Harms) do
if string.find(WeaponName,_name,1) then hit = true end
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
@@ -326,7 +334,7 @@ do
local hit = false
if self.DefendMavs then
for _,_name in pairs (SHORAD.Mavs) do
if string.find(WeaponName,_name,1) then hit = true end
if string.find(WeaponName,_name,1,true) then hit = true end
end
end
return hit
@@ -367,7 +375,7 @@ do
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
end
@@ -388,7 +396,7 @@ do
local returnname = false
for _,_groups in pairs (shoradset) do
local groupname = _groups:GetName()
if string.find(groupname, tgtgrp, 1) then
if string.find(groupname, tgtgrp, 1, true) then
returnname = true
end
end
@@ -400,6 +408,7 @@ do
-- @return #boolean Returns true for a detection, else false
function SHORAD:_ShotIsDetected()
self:T(self.lid .. " _ShotIsDetected")
if self.debug then return true end
local IsDetected = false
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
local ActualDetection = math.random(1,100) -- value for this shot
@@ -423,7 +432,7 @@ do
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
-- mymantis:AddShorad(myshorad,720)
-- mymantis:Start()
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
self:T(self.lid .. " WakeUpShorad")
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
local targetcat = TargetCat or Object.Category.UNIT
@@ -477,6 +486,76 @@ do
return self
end
--- (Internal) Calculate hit zone of an AGM-88
-- @param #SHORAD self
-- @param #table SEADWeapon DCS.Weapon object
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
-- @param #number height Height when the missile was fired
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
-- @return #SHORAD self
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
self:T("**** Calculating hit zone")
if SEADWeapon and SEADWeapon:isExist() then
--local pos = SEADWeapon:getPoint()
-- postion and height
local position = SEADWeapon:getPosition()
local mheight = height
-- heading
local wph = math.atan2(position.x.z, position.x.x)
if wph < 0 then
wph=wph+2*math.pi
end
wph=math.deg(wph)
-- velocity
local wpndata = SEAD.HarmData["AGM_88"]
local mveloc = math.floor(wpndata[2] * 340.29)
local c1 = (2*mheight*9.81)/(mveloc^2)
local c2 = (mveloc^2) / 9.81
local Ropt = c2 * math.sqrt(c1+1)
if height <= 5000 then
Ropt = Ropt * 0.72
elseif height <= 7500 then
Ropt = Ropt * 0.82
elseif height <= 10000 then
Ropt = Ropt * 0.87
elseif height <= 12500 then
Ropt = Ropt * 0.98
end
-- look at a couple of zones across the trajectory
for n=1,3 do
local dist = Ropt - ((n-1)*20000)
local predpos= pos0:Translate(dist,wph)
if predpos then
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
if self.debug then
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
end
local seadset = self.Groupset
local tgtcoord = targetzone:GetRandomPointVec2()
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
local _targetgroup = nil
local _targetgroupname = "none"
local _targetskill = "Random"
if tgtgrp and tgtgrp:IsAlive() then
_targetgroup = tgtgrp
_targetgroupname = tgtgrp:GetName() -- group name
_targetskill = tgtgrp:GetUnit(1):GetSkill()
self:T("*** Found Target = ".. _targetgroupname)
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
end
end
end
end
return self
end
--- Main function - work on the EventData
-- @param #SHORAD self
-- @param Core.Event#EVENTDATA EventData The event details table data set
@@ -504,13 +583,48 @@ do
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
-- get target data
local targetdata = EventData.Weapon:getTarget() -- Identify target
-- Is there target data?
if not targetdata or self.debug then
if string.find(ShootingWeaponName,"AGM_88",1,true) then
self:I("**** Tracking AGM-88 with no target data.")
local pos0 = EventData.IniUnit:GetCoordinate()
local fheight = EventData.IniUnit:GetHeight()
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
end
return self
end
local targetcat = targetdata:getCategory() -- Identify category
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
self:T({targetdata})
local targetunit = nil
if targetcat == Object.Category.UNIT then -- UNIT
targetunit = UNIT:Find(targetdata)
elseif targetcat == Object.Category.STATIC then -- STATIC
targetunit = STATIC:Find(targetdata)
--self:T("Static Target Data")
--self:T({targetdata:isExist()})
--self:T({targetdata:getPoint()})
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
--tgtcoord:MarkToAll("Missile Target",true)
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord1 = tgtgrp1:GetCoordinate()
--tgtcoord1:MarkToAll("Close target SAM",true)
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
local tgtcoord2 = tgtgrp2:GetCoordinate()
--tgtcoord2:MarkToAll("Close target SHORAD",true)
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
if dist1 < dist2 then
targetunit = tgtgrp1
targetcat = Object.Category.UNIT
else
targetunit = tgtgrp2
targetcat = Object.Category.UNIT
end
end
--local targetunitname = Unit.getName(targetdata) -- Unit name
if targetunit and targetunit:IsAlive() then
@@ -519,7 +633,11 @@ do
local targetgroup = nil
local targetgroupname = "none"
if targetcat == Object.Category.UNIT then
targetgroup = targetunit:GetGroup()
if targetunit.ClassName == "UNIT" then
targetgroup = targetunit:GetGroup()
elseif targetunit.ClassName == "GROUP" then
targetgroup = targetunit
end
targetgroupname = targetgroup:GetName() -- group name
elseif targetcat == Object.Category.STATIC then
targetgroup = targetunit
@@ -540,6 +658,7 @@ do
end
end
end
return self
end
--
end
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -33,13 +33,13 @@ if io then
BASE:I("- io available")
else
BASE:I("- io NOT available! Some functions may not work.")
__na=true
__na=true
end
if lfs then
BASE:I("- lfs available")
else
BASE:I("- lfs NOT available! Some functions may not work.")
__na=true
__na=true
end
if __na then
BASE:I("Check <DCS install folder>/Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)")
+28 -1
View File
@@ -4,6 +4,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/Templates.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/STTS.lua' )
__Moose.Include( 'Scripts/Moose/Utilities/FiFo.lua' )
__Moose.Include( 'Scripts/Moose/Core/Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/Beacon.lua' )
@@ -27,6 +28,9 @@ __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Timer.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@@ -39,6 +43,7 @@ __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/DCSTask.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
@@ -68,13 +73,35 @@ __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Fox.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' )
__Moose.Include( 'Scripts/Moose/Functional/Autolase.lua' )
__Moose.Include( 'Scripts/Moose/Functional/AICSAR.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Target.lua' )
__Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Cohort.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Platoon.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Legion.lua' )
__Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Brigade.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Commander.lua' )
__Moose.Include( 'Scripts/Moose/Ops/OpsTransport.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' )
__Moose.Include( 'Scripts/Moose/Ops/CTLD.lua' )
__Moose.Include( 'Scripts/Moose/Ops/OpsZone.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Chief.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )
+2 -2
View File
@@ -1459,8 +1459,8 @@ function ATIS:onafterBroadcast(From, Event, To)
-- Convert to °F.
if self.TDegF then
temperature=UTILS.CelciusToFarenheit(temperature)
dewpoint=UTILS.CelciusToFarenheit(dewpoint)
temperature=UTILS.CelsiusToFahrenheit(temperature)
dewpoint=UTILS.CelsiusToFahrenheit(dewpoint)
end
local TEMPERATURE=string.format("%d", math.abs(temperature))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+600
View File
@@ -0,0 +1,600 @@
--- **Ops** - Brigade Warehouse.
--
-- **Main Features:**
--
-- * Manage platoons
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
-- * Define rearming zones
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Brigade).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Brigade
-- @image OPS_Brigade_.png
--- BRIGADE class.
-- @type BRIGADE
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
-- @extends Ops.Legion#LEGION
--- *I am not afraid of an Army of lions lead by a sheep; I am afraid of sheep lead by a lion* -- Alexander the Great
--
-- ===
--
-- # The BRIGADE Concept
--
-- A BRIGADE consists of one or multiple PLATOONs. These platoons "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
--
--
-- @field #BRIGADE
BRIGADE = {
ClassName = "BRIGADE",
verbose = 0,
rearmingZones = {},
refuellingZones = {},
}
--- Supply Zone.
-- @type BRIGADE.SupplyZone
-- @field Core.Zone#ZONE zone The zone.
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
-- @field #boolean markerOn If `true`, marker is on.
-- @field Wrapper.Marker#MARKER marker F10 marker.
--- BRIGADE class version.
-- @field #string version
BRIGADE.version="0.1.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Spawn when hosting warehouse is a ship or oil rig or gas platform.
-- TODO: Rearming zones.
-- TODO: Retreat zones.
-- DONE: Add weapon range.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new BRIGADE class object.
-- @param #BRIGADE self
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
-- @param #string BrigadeName Name of the brigade.
-- @return #BRIGADE self
function BRIGADE:New(WarehouseName, BrigadeName)
-- Inherit everything from LEGION class.
local self=BASE:Inherit(self, LEGION:New(WarehouseName, BrigadeName)) -- #BRIGADE
-- Nil check.
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
return nil
end
-- Set some string id for output to DCS.log file.
self.lid=string.format("BRIGADE %s | ", self.alias)
-- Defaults
self:SetRetreatZones()
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG).
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the BRIGADE. Initializes parameters and starts event handlers.
-- @function [parent=#BRIGADE] Start
-- @param #BRIGADE self
--- Triggers the FSM event "Start" after a delay. Starts the BRIGADE. Initializes parameters and starts event handlers.
-- @function [parent=#BRIGADE] __Start
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the BRIGADE and all its event handlers.
-- @param #BRIGADE self
--- Triggers the FSM event "Stop" after a delay. Stops the BRIGADE and all its event handlers.
-- @function [parent=#BRIGADE] __Stop
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "ArmyOnMission".
-- @function [parent=#BRIGADE] ArmyOnMission
-- @param #BRIGADE self
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "ArmyOnMission" after a delay.
-- @function [parent=#BRIGADE] __ArmyOnMission
-- @param #BRIGADE self
-- @param #number delay Delay in seconds.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- On after "ArmyOnMission" event.
-- @function [parent=#BRIGADE] OnAfterArmyOnMission
-- @param #BRIGADE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup The ARMYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a platoon to the brigade.
-- @param #BRIGADE self
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
-- @return #BRIGADE self
function BRIGADE:AddPlatoon(Platoon)
-- Add platoon to brigade.
table.insert(self.cohorts, Platoon)
-- Add assets to platoon.
self:AddAssetToPlatoon(Platoon, Platoon.Ngroups)
-- Set brigade of platoon.
Platoon:SetBrigade(self)
-- Start platoon.
if Platoon:IsStopped() then
Platoon:Start()
end
return self
end
--- Add asset group(s) to platoon.
-- @param #BRIGADE self
-- @param Ops.Platoon#PLATOON Platoon The platoon object.
-- @param #number Nassets Number of asset groups to add.
-- @return #BRIGADE self
function BRIGADE:AddAssetToPlatoon(Platoon, Nassets)
if Platoon then
-- Get the template group of the platoon.
local Group=GROUP:FindByName(Platoon.templatename)
if Group then
-- Debug text.
local text=string.format("Adding asset %s to platoon %s", Group:GetName(), Platoon.name)
self:T(self.lid..text)
-- Add assets to airwing warehouse.
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Platoon.skill, Platoon.livery, Platoon.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Platoon does not exit!")
end
return self
end
--- Define a set of retreat zones.
-- @param #BRIGADE self
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
-- @return #BRIGADE self
function BRIGADE:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
--- Add a retreat zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
-- @return #BRIGADE self
function BRIGADE:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
--- Get retreat zones.
-- @param #BRIGADE self
-- @return Core.Set#SET_ZONE Set of retreat zones.
function BRIGADE:GetRetreatZones()
return self.retreatZones
end
--- Add a rearming zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
-- @return #BRIGADE.SupplyZone The rearming zone data.
function BRIGADE:AddRearmingZone(RearmingZone)
local rearmingzone={} --#BRIGADE.SupplyZone
rearmingzone.zone=RearmingZone
rearmingzone.mission=nil
rearmingzone.marker=MARKER:New(rearmingzone.zone:GetCoordinate(), "Rearming Zone"):ToCoalition(self:GetCoalition())
table.insert(self.rearmingZones, rearmingzone)
return rearmingzone
end
--- Add a refuelling zone.
-- @param #BRIGADE self
-- @param Core.Zone#ZONE RefuellingZone Refuelling zone.
-- @return #BRIGADE.SupplyZone The refuelling zone data.
function BRIGADE:AddRefuellingZone(RefuellingZone)
local supplyzone={} --#BRIGADE.SupplyZone
supplyzone.zone=RefuellingZone
supplyzone.mission=nil
supplyzone.marker=MARKER:New(supplyzone.zone:GetCoordinate(), "Refuelling Zone"):ToCoalition(self:GetCoalition())
table.insert(self.refuellingZones, supplyzone)
return supplyzone
end
--- Get platoon by name.
-- @param #BRIGADE self
-- @param #string PlatoonName Name of the platoon.
-- @return Ops.Platoon#PLATOON The Platoon object.
function BRIGADE:GetPlatoon(PlatoonName)
local platoon=self:_GetCohort(PlatoonName)
return platoon
end
--- Get platoon of an asset.
-- @param #BRIGADE self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
-- @return Ops.Platoon#PLATOON The platoon object.
function BRIGADE:GetPlatoonOfAsset(Asset)
local platoon=self:GetPlatoon(Asset.squadname)
return platoon
end
--- Remove asset from platoon.
-- @param #BRIGADE self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The platoon asset.
function BRIGADE:RemoveAssetFromPlatoon(Asset)
local platoon=self:GetPlatoonOfAsset(Asset)
if platoon then
platoon:DelAsset(Asset)
end
end
--- [ GROUND ] Function to load back an asset in the field that has been filed before.
-- @param #BRIGADE self
-- @param #string Templatename e.g."1 PzDv LogRg I\_AID-976" - that's the alias (name) of an platoon spawned as `"platoon - alias"_AID-"asset-ID"`
-- @param Core.Point#COORDINATE Position where to spawn the platoon
-- @return #BRIGADE self
-- @usage
-- Prerequisites:
-- Save the assets spawned by BRIGADE/CHIEF regularly (~every 5 mins) into a file, e.g. like this:
--
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- local BlueSaveOps = SET_GROUP:New():FilterCoalitions("blue"):FilterPrefixes("AID"):FilterCategoryGround():FilterOnce()
-- UTILS.SaveSetOfGroups(BlueSaveOps,Path,BlueOpsFilename)
--
-- where Path and Filename are strings, as chosen by you.
-- You can then load back the assets at the start of your next mission run. Be aware that it takes a couple of seconds for the
-- platoon data to arrive in brigade, so make this an action after ~20 seconds, e.g. like so:
--
-- function LoadBackAssets()
-- local Path = FilePath or "C:\\Users\\<yourname>\\Saved Games\\DCS\\Missions\\" -- example path
-- local BlueOpsFilename = BlueFileName or "ExamplePlatoonSave.csv" -- example filename
-- if UTILS.CheckFileExists(Path,BlueOpsFilename) then
-- local loadback = UTILS.LoadSetOfGroups(Path,BlueOpsFilename,false)
-- for _,_platoondata in pairs (loadback) do
-- local groupname = _platoondata.groupname -- #string
-- local coordinate = _platoondata.coordinate -- Core.Point#COORDINATE
-- Your_Brigade:LoadBackAssetInPosition(groupname,coordinate)
-- end
-- end
-- end
--
-- local AssetLoader = TIMER:New(LoadBackAssets)
-- AssetLoader:Start(20)
--
-- The assets loaded back into the mission will be considered for AUFTRAG type missions from CHIEF and BRIGADE.
function BRIGADE:LoadBackAssetInPosition(Templatename,Position)
self:T(self.lid .. "LoadBackAssetInPosition: " .. tostring(Templatename))
-- get Platoon alias from Templatename
local nametbl = UTILS.Split(Templatename,"_")
local name = nametbl[1]
self:T(string.format("*** Target Platoon = %s ***",name))
-- find a matching asset table from BRIGADE
local cohorts = self.cohorts or {}
local thisasset = nil --Functional.Warehouse#WAREHOUSE.Assetitem
local found = false
for _,_cohort in pairs(cohorts) do
local asset = _cohort:GetName()
self:T(string.format("*** Looking at Platoon = %s ***",asset))
if asset == name then
self:T("**** Found Platoon ****")
local cohassets = _cohort.assets or {}
for _,_zug in pairs (cohassets) do
local zug = _zug -- Functional.Warehouse#WAREHOUSE.Assetitem
if zug.assignment == name and zug.requested == false then
self:T("**** Found Asset ****")
found = true
thisasset = zug --Functional.Warehouse#WAREHOUSE.Assetitem
break
end
end
end
end
if found then
-- prep asset
thisasset.rid = thisasset.uid
thisasset.requested = false
thisasset.score=100
thisasset.missionTask="CAS"
thisasset.spawned = true
local template = thisasset.templatename
local alias = thisasset.spawngroupname
-- Spawn group
local spawnasset = SPAWN:NewWithAlias(template,alias)
:InitDelayOff()
:SpawnFromCoordinate(Position)
-- build a new self request
local request = {} --Functional.Warehouse#WAREHOUSE.Pendingitem
request.assignment = name
request.warehouse = self
request.assets = {thisasset}
request.ntransporthome = 0
request.ndelivered = 0
request.ntransport = 0
request.cargoattribute = thisasset.attribute
request.category = thisasset.category
request.cargoassets = {thisasset}
request.assetdesc = WAREHOUSE.Descriptor.ASSETLIST
request.cargocategory = thisasset.category
request.toself = true
request.transporttype = WAREHOUSE.TransportType.SELFPROPELLED
request.assetproblem = {}
request.born = true
request.prio = 50
request.uid = thisasset.uid
request.airbase = nil
request.timestamp = timer.getAbsTime()
request.assetdescval = {thisasset}
request.nasset = 1
request.cargogroupset = SET_GROUP:New()
request.cargogroupset:AddGroup(spawnasset)
request.iscargo = true
-- Call Brigade self
self:__AssetSpawned(2, spawnasset, thisasset, request)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start BRIGADE FSM.
-- @param #BRIGADE self
function BRIGADE:onafterStart(From, Event, To)
-- Start parent Warehouse.
self:GetParent(self, BRIGADE).onafterStart(self, From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting BRIGADE v%s", BRIGADE.version))
end
--- Update status.
-- @param #BRIGADE self
function BRIGADE:onafterStatus(From, Event, To)
-- Status of parent Warehouse.
self:GetParent(self).onafterStatus(self, From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
----------------
-- Transport ---
----------------
self:CheckTransportQueue()
--------------
-- Mission ---
--------------
-- Check if any missions should be cancelled.
self:CheckMissionQueue()
---------------------
-- Rearming Zones ---
---------------------
for _,_rearmingzone in pairs(self.rearmingZones) do
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
if (not rearmingzone.mission) or rearmingzone.mission:IsOver() then
rearmingzone.mission=AUFTRAG:NewAMMOSUPPLY(rearmingzone.zone)
self:AddMission(rearmingzone.mission)
end
end
-----------------------
-- Refuelling Zones ---
-----------------------
-- Check refuelling zones.
for _,_supplyzone in pairs(self.refuellingZones) do
local supplyzone=_supplyzone --#BRIGADE.SupplyZone
-- Check if mission is nil or over.
if (not supplyzone.mission) or supplyzone.mission:IsOver() then
supplyzone.mission=AUFTRAG:NewFUELSUPPLY(supplyzone.zone)
self:AddMission(supplyzone.mission)
end
end
-----------
-- Info ---
-----------
-- General info:
if self.verbose>=1 then
-- Count missions not over yet.
local Nmissions=self:CountMissionsInQueue()
-- Asset count.
local Npq, Np, Nq=self:CountAssetsOnMission()
-- Asset string.
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
-- Output.
local text=string.format("%s: Missions=%d, Platoons=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
self:I(self.lid..text)
end
------------------
-- Mission Info --
------------------
if self.verbose>=2 then
local text=string.format("Missions Total=%d:", #self.missionqueue)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
end
self:I(self.lid..text)
end
--------------------
-- Transport Info --
--------------------
if self.verbose>=2 then
local text=string.format("Transports Total=%d:", #self.transportqueue)
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
end
self:I(self.lid..text)
end
-------------------
-- Platoon Info --
-------------------
if self.verbose>=3 then
local text="Platoons:"
for i,_platoon in pairs(self.cohorts) do
local platoon=_platoon --Ops.Platoon#PLATOON
local callsign=platoon.callsignName and UTILS.GetCallsignName(platoon.callsignName) or "N/A"
local modex=platoon.modex and platoon.modex or -1
local skill=platoon.skill and tostring(platoon.skill) or "N/A"
-- Platoon text.
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", platoon.name, platoon:GetState(), platoon.aircrafttype, platoon:CountAssets(true), #platoon.assets, callsign, modex, skill)
end
self:I(self.lid..text)
end
-------------------
-- Rearming Info --
-------------------
if self.verbose>=4 then
local text="Rearming Zones:"
for i,_rearmingzone in pairs(self.rearmingZones) do
local rearmingzone=_rearmingzone --#BRIGADE.SupplyZone
-- Info text.
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", rearmingzone.zone:GetName(), rearmingzone.mission:GetState(), rearmingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
-------------------
-- Refuelling Info --
-------------------
if self.verbose>=4 then
local text="Refuelling Zones:"
for i,_refuellingzone in pairs(self.refuellingZones) do
local refuellingzone=_refuellingzone --#BRIGADE.SupplyZone
-- Info text.
text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups())
end
self:I(self.lid..text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ArmyOnMission".
-- @param #BRIGADE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
function BRIGADE:onafterArmyOnMission(From, Event, To, ArmyGroup, Mission)
-- Debug info.
self:T(self.lid..string.format("Group %s on %s mission %s", ArmyGroup:GetName(), Mission:GetType(), Mission:GetName()))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+220 -55
View File
@@ -6,6 +6,14 @@
--
-- ===
--
-- ## Missions:--- **Ops** -- Combat Search and Rescue.
--
-- ===
--
-- **CSAR** - MOOSE based Helicopter CSAR Operations.
--
-- ===
--
-- ## Missions:
--
-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR)
@@ -22,7 +30,7 @@
-- @module Ops.CSAR
-- @image OPS_CSAR.jpg
-- Date: Oct 2021
-- Date: Feb 2022
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -45,6 +53,7 @@
-- * Object oriented refactoring of Ciribob\'s fantastic CSAR script.
-- * No need for extra MIST loading.
-- * Additional events to tailor your mission.
-- * Optional SpawnCASEVAC to create casualties without beacon (e.g. handling dead ground vehicles and create CASVAC requests).
--
-- ## 0. Prerequisites
--
@@ -105,7 +114,7 @@
-- self.countryblue= country.id.USA
-- self.countryred = country.id.RUSSIA
-- self.countryneutral = country.id.UN_PEACEKEEPERS
--
--
-- ## 2.1 Experimental Features
--
-- WARNING - Here\'ll be dragons!
@@ -115,7 +124,11 @@
-- self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!)
-- self.SRSchannel = 300 -- radio channel
-- self.SRSModulation = radio.modulation.AM -- modulation
--
-- self.SRSport = 5002 -- and SRS port
-- --
-- self.csarUsePara = false -- If set to true, will use the LandingAfterEjection Event instead of Ejection --shagrat
-- self.wetfeettemplate = "man in floating thingy" -- if you use a mod to have a pilot in a rescue float, put the template name in here for wet feet spawns. Note: in conjunction with csarUsePara this might create dual ejected pilots in edge cases.
--
-- ## 3. Results
--
-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object:
@@ -175,6 +188,8 @@
-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition
-- my_csar:SpawnCSARAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true )
--
-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat
-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE)
--
-- @field #CSAR
CSAR = {
@@ -222,8 +237,9 @@ CSAR = {
-- @field #number frequency Frequency of the NDB.
-- @field #string player Player name if applicable.
-- @field Wrapper.Group#GROUP group Spawned group object.
-- @field #number timestamp Timestamp for approach process
-- @field #boolean alive Group is alive or dead/rescued
-- @field #number timestamp Timestamp for approach process.
-- @field #boolean alive Group is alive or dead/rescued.
-- @field #boolean wetfeet Group is spawned over (deep) water.
--- All slot / Limit settings
-- @type CSAR.AircraftType
@@ -238,11 +254,13 @@ CSAR.AircraftType["Mi-8MTV2"] = 12
CSAR.AircraftType["Mi-8MT"] = 12
CSAR.AircraftType["Mi-24P"] = 8
CSAR.AircraftType["Mi-24V"] = 8
CSAR.AircraftType["Bell-47"] = 2
CSAR.AircraftType["Bell-47"] = 2
CSAR.AircraftType["UH-60L"] = 10
CSAR.AircraftType["AH-64D_BLK_II"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="0.1.11r2"
CSAR.version="1.0.4e"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -250,7 +268,7 @@ CSAR.version="0.1.11r2"
-- DONE: SRS Integration (to be tested)
-- TODO: Maybe - add option to smoke/flare closest MASH
-- TODO: shagrat Add cargoWeight to helicopter when pilot boarded
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -351,6 +369,7 @@ function CSAR:New(Coalition, Template, Alias)
self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter
self.loadtimemax = 135 -- seconds
self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK!
self.beaconRefresher = 29 -- seconds
self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase
self.FARPRescueDistance = 1000 -- you need to be this close to a FARP or Airport for the pilot to be rescued.
self.max_units = 6 --max number of pilots that can be carried
@@ -380,7 +399,14 @@ function CSAR:New(Coalition, Template, Alias)
self.countryblue= country.id.USA
self.countryred = country.id.RUSSIA
self.countryneutral = country.id.UN_PEACEKEEPERS
-- added 0.1.3
self.csarUsePara = false -- shagrat set to true, will use the LandingAfterEjection Event instead of Ejection
-- added 0.1.4
self.wetfeettemplate = nil
self.usewetfeet = false
-- WARNING - here\'ll be dragons
-- for this to work you need to de-sanitize your mission environment in <DCS root>\Scripts\MissionScripting.lua
-- needs SRS => 1.9.6 to work (works on the *server* side)
@@ -388,6 +414,7 @@ function CSAR:New(Coalition, Template, Alias)
self.SRSPath = "E:\\Progra~1\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!)
self.SRSchannel = 300 -- radio channel
self.SRSModulation = radio.modulation.AM -- modulation
self.SRSport = 5002 -- port
------------------------
--- Pseudo Functions ---
@@ -492,8 +519,9 @@ end
-- @param #string Typename Typename of unit.
-- @param #number Frequency Frequency of the NDB in Hz
-- @param #string Playername Name of Player (if applicable)
-- @param #boolean Wetfeet Ejected over water
-- @return #CSAR self.
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername)
function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername,Wetfeet)
self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername})
-- create new entry
@@ -509,6 +537,7 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript
DownedPilot.group = Group
DownedPilot.timestamp = 0
DownedPilot.alive = true
DownedPilot.wetfeet = Wetfeet or false
-- Add Pilot
local PilotTable = self.downedPilots
@@ -558,17 +587,23 @@ end
-- @param #number country Country for template.
-- @param Core.Point#COORDINATE point Coordinate to spawn at.
-- @param #number frequency Frequency of the pilot's beacon
-- @param #boolean wetfeet Spawn is over water
-- @return Wrapper.Group#GROUP group The #GROUP object.
-- @return #string alias The alias name.
function CSAR:_SpawnPilotInField(country,point,frequency)
self:T({country,point,frequency})
function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
self:T({country,point,frequency,tostring(wetfeet)})
local freq = frequency or 1000
local freq = freq / 1000 -- kHz
for i=1,10 do
math.random(i,10000)
end
if point:IsSurfaceTypeWater() then point.y = 0 end
if point:IsSurfaceTypeWater() or wetfeet then
point.y = 0
end
local template = self.template
if self.usewetfeet and wetfeet then
template = self.wetfeettemplate
end
local alias = string.format("Pilot %.2fkHz-%d", freq, math.random(1,99))
local coalition = self.coalition
local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA?
@@ -634,21 +669,31 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description})
local template = self.template
local wetfeet = false
local surface = _point:GetSurfaceType()
if surface == land.SurfaceType.WATER then
wetfeet = true
end
if not _freq then
_freq = self:_GenerateADFFrequency()
if not _freq then _freq = 333000 end --noob catch
end
local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq)
local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point,_freq,wetfeet)
local _typeName = _typeName or "Pilot"
if not noMessage then
if _freq ~= 0 then --shagrat different CASEVAC msg
self:_DisplayToAllSAR("MAYDAY MAYDAY! " .. _typeName .. " is down. ", self.coalition, self.messageTime)
else
self:_DisplayToAllSAR("Troops In Contact. " .. _typeName .. " requests CASEVAC. ", self.coalition, self.messageTime)
end
end
if _freq then
if (_freq and _freq ~= 0) then --shagrat only add beacon if _freq is NOT 0
self:_AddBeaconToGroup(_spawnedGroup, _freq)
end
@@ -657,25 +702,33 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla
local _text = _description
if not forcedesc then
if _playerName ~= nil then
_text = "Pilot " .. _playerName
elseif _unitName ~= nil then
_text = "AI Pilot of " .. _unitName
if _freq ~= 0 then --shagrat
_text = "Pilot " .. _playerName
else
_text = "TIC - " .. _playerName
end
elseif _unitName ~= nil then
if _freq ~= 0 then --shagrat
_text = "AI Pilot of " .. _unitName
else
_text = "TIC - " .. _unitName
end
end
end
self:T({_spawnedGroup, _alias})
local _GroupName = _spawnedGroup:GetName() or _alias
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName)
self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName,wetfeet)
self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage)
self:_InitSARForPilot(_spawnedGroup, _unitName, _freq, noMessage) --shagrat use unitName to have the aircraft callsign / descriptive "name" etc.
return self
end
--- (Internal) 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 _zone Name of the zone.
-- @param #string _zone Name of the zone. Can also be passed as a (normal, round) ZONE object.
-- @param #number _coalition Coalition.
-- @param #string _description (optional) Description.
-- @param #boolean _randomPoint (optional) Random yes or no.
@@ -686,7 +739,16 @@ end
function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage, unitname, typename, forcedesc)
self:T(self.lid .. " _SpawnCsarAtZone")
local freq = self:_GenerateADFFrequency()
local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position
local _triggerZone = nil
if type(_zone) == "string" then
_triggerZone = ZONE:New(_zone) -- trigger to use as reference position
elseif type(_zone) == "table" and _zone.ClassName then
if string.find(_zone.ClassName, "ZONE",1) then
_triggerZone = _zone -- is already a zone
end
end
if _triggerZone == nil then
self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10)
return
@@ -720,7 +782,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 Zone Name of the zone.
-- @param #string Zone Name of the zone. Can also be passed as a (normal, round) ZONE object.
-- @param #number Coalition Coalition.
-- @param #string Description (optional) Description.
-- @param #boolean RandomPoint (optional) Random yes or no.
@@ -737,6 +799,58 @@ function CSAR:SpawnCSARAtZone(Zone, Coalition, Description, RandomPoint, Nomessa
return self
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 #number _coalition Coalition.
-- @param #string _description (optional) Description.
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
-- @param #string unitname (optional) Name of the lost unit.
-- @param #string typename (optional) Type of plane.
-- @param #boolean forcedesc (optional) Force to use the description passed only for the pilot track entry. Use to have fully custom names.
function CSAR:_SpawnCASEVAC( _Point, _coalition, _description, _nomessage, unitname, typename, forcedesc) --shagrat added internal Function _SpawnCASEVAC
self:T(self.lid .. " _SpawnCASEVAC")
local _description = _description or "CASEVAC"
local unitname = unitname or "CASEVAC"
local typename = typename or "Ground Commander"
local pos = {}
pos = _Point
local _country = 0
if _coalition == coalition.side.BLUE then
_country = self.countryblue
elseif _coalition == coalition.side.RED then
_country = self.countryred
else
_country = self.countryneutral
end
--shagrat set frequency to 0 as "flag" for no beacon
self:_AddCsar(_coalition, _country, pos, typename, unitname, _description, 0, _nomessage, _description, forcedesc)
return self
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 #number Coalition Coalition.
-- @param #string Description (optional) Description.
-- @param #boolean addBeacon (optional) yes or no.
-- @param #boolean Nomessage (optional) If true, don\'t send a message to SAR.
-- @param #string Unitname (optional) Name of the lost unit.
-- @param #string Typename (optional) Type of plane.
-- @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 )
function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
return self
end --shagrat end added CASEVAC
--- (Internal) Event handler.
-- @param #CSAR self
function CSAR:_EventHandler(EventData)
@@ -745,6 +859,11 @@ function CSAR:_EventHandler(EventData)
local _event = EventData -- Core.Event#EVENTDATA
-- no Player
if self.enableForAI == false and _event.IniPlayerName == nil then
return
end
-- no event
if _event == nil or _event.initiator == nil then
return false
@@ -824,19 +943,15 @@ function CSAR:_EventHandler(EventData)
local _unit = _event.IniUnit
local _unitname = _event.IniUnitName
local _group = _event.IniGroup
if _unit == nil then
return -- error!
end
local _coalition = _unit:GetCoalition()
local _coalition = _unit:GetCoalition()
if _coalition ~= self.coalition then
return --ignore!
end
if self.enableForAI == false and _event.IniPlayerName == nil then
return
end
if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then
self:T(self.lid .. " Pilot has not taken off, ignore")
@@ -851,12 +966,40 @@ function CSAR:_EventHandler(EventData)
if self.limitmaxdownedpilots and self:_ReachedPilotLimit() then
return
end
-- all checks passed, get going.
-- TODO: Over water check --- EVENTS.LandingAfterEjection NOT triggered by DCS, so handle csarUsePara = true case
-- might create dual pilots in edge cases
local wetfeet = false
local surface = _unit:GetCoordinate():GetSurfaceType()
if surface == land.SurfaceType.WATER then
wetfeet = true
end
-- all checks passed, get going.
if self.csarUsePara == false or (self.csarUsePara and wetfeet ) then --shagrat check parameter LandingAfterEjection, if true don't spawn a Pilot from EJECTION event, wait for the Chute to land
local _freq = self:_GenerateADFFrequency()
self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none")
return true
end
---- shagrat on event LANDING_AFTER_EJECTION spawn pilot at parachute location
elseif (_event.id == EVENTS.LandingAfterEjection and self.csarUsePara == true) then
self:I({EVENT=_event})
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
local _unitname = "Aircraft" --_event.initiator:getName() or "Aircraft" --shagrat Optional use of Object name which is unfortunately 'f15_Pilot_Parachute'
local _typename = "Ejected Pilot" --_event.Initiator.getTypeName() or "Ejected Pilot"
local _country = _event.initiator:getCountry()
local _coalition = coalition.getCountryCoalition( _country )
if _coalition == self.coalition then
local _freq = self:_GenerateADFFrequency()
self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, "none")
return true
self:I({coalition=_coalition,country= _country, coord=_LandingPos, name=_unitname, player=_event.IniPlayerName, freq=_freq})
self:_AddCsar(_coalition, _country, _LandingPos, nil, _unitname, _event.IniPlayerName, _freq, false, "none")--shagrat add CSAR at Parachute location.
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
end
return true
elseif _event.id == EVENTS.Land then
self:T(self.lid .. " Landing")
@@ -921,8 +1064,13 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage)
local _leadername = _leader:GetName()
if not _nomessage then
local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk)
if _freq ~= 0 then --shagrat
local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute'
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
else --shagrat CASEVAC msg
local _text = string.format("Pickup Zone at %s.", _coordinatesText )
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
end
end
for _,_heliName in pairs(self.csarUnits) do
@@ -1026,9 +1174,9 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname)
local dist = UTILS.MetersToNM(self.autosmokedistance)
disttext = string.format("%.0fnm",dist)
end
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nI'll pop a smoke when you are %s away.\nLand or hover by the smoke.", _heliName, _pilotName, disttext), self.messageTime,false,true)
else
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true)
self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Finally, that is music in my ears!\nRequest a flare or smoke if you need.", _heliName, _pilotName), self.messageTime,false,true)
end
--mark as shown for THIS heli and THIS group
self.heliVisibleMessage[_lookupKeyHeli] = true
@@ -1060,7 +1208,7 @@ function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader)
if _lastSmoke == nil or timer.getTime() > _lastSmoke then
local _smokecolor = self.smokecolor
local _smokecoord = _woundedLeader:GetCoordinate()
local _smokecoord = _woundedLeader:GetCoordinate():Translate( 6, math.random( 1, 360) ) --shagrat place smoke at a random 6 m distance, so smoke does not obscure the pilot
_smokecoord:Smoke(_smokecolor)
self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time
end
@@ -1187,7 +1335,8 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG
_time = self.landedStatus[_lookupKeyHeli] - 10
self.landedStatus[_lookupKeyHeli] = _time
end
if _time <= 0 or _distance < self.loadDistance then
--if _time <= 0 or _distance < self.loadDistance then
if _distance < self.loadDistance + 5 or _distance <= 13 then
if self.pilotmustopendoors and not self:_IsLoadingDoorOpen(_heliName) then
self:_DisplayMessageToSAR(_heliUnit, "Open the door to let me in!", self.messageTime, true)
return true
@@ -1360,12 +1509,13 @@ end
-- @param #number _time Message show duration.
-- @param #boolean _clear (optional) Clear screen.
-- @param #boolean _speak (optional) Speak message via SRS.
function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak)
-- @param #boolean _override (optional) Override message suppression
function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak, _override)
self:T(self.lid .. " _DisplayMessageToSAR")
local group = _unit:GetGroup()
local _clear = _clear or nil
local _time = _time or self.messageTime
if not self.suppressmessages then
if _override or not self.suppressmessages then
local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group)
end
-- integrate SRS
@@ -1375,6 +1525,7 @@ function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak)
local modulation = self.SRSModulation
local channel = self.SRSchannel
local msrs = MSRS:New(path,channel,modulation)
msrs:SetPort(self.SRSport)
msrs:PlaySoundText(srstext, 2)
end
return self
@@ -1435,7 +1586,11 @@ function CSAR:_DisplayActiveSAR(_unitName)
else
distancetext = string.format("%.1fkm", _distance/1000.0)
end
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
if _value.frequency == 0 then--shagrat insert CASEVAC without Frequency
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %s ", _value.desc, _coordinatesText, distancetext) })
else
table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) })
end
end
end
@@ -1449,7 +1604,7 @@ function CSAR:_DisplayActiveSAR(_unitName)
_msg = _msg .. "\n" .. _line.msg
end
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2, false, false, true)
return self
end
@@ -1507,7 +1662,7 @@ function CSAR:_SignalFlare(_unitName)
local _closest = self:_GetClosestDownedPilot(_heli)
local smokedist = 8000
if self.approachdist_far > smokedist then smokedist = self.approachdist_far end
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
local _distance = 0
@@ -1517,7 +1672,7 @@ function CSAR:_SignalFlare(_unitName)
_distance = string.format("%.1fkm",_closest.distance)
end
local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
local _coord = _closest.pilot:GetCoordinate()
_coord:FlareRed(_clockDir)
@@ -1528,7 +1683,7 @@ function CSAR:_SignalFlare(_unitName)
else
_distance = string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime)
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true)
end
return self
end
@@ -1562,16 +1717,16 @@ function CSAR:_Reqsmoke( _unitName )
local smokedist = 8000
if smokedist < self.approachdist_far then smokedist = self.approachdist_far end
local _closest = self:_GetClosestDownedPilot(_heli)
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < smokedist then
if _closest ~= nil and _closest.pilot ~= nil and _closest.distance > 0 and _closest.distance < smokedist then
local _clockDir = self:_GetClockDirection(_heli, _closest.pilot)
local _distance = 0
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 smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true)
local _msg = string.format("%s - Popping smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance)
self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true, true)
local _coord = _closest.pilot:GetCoordinate()
local color = self.smokecolor
_coord:Smoke(color)
@@ -1582,7 +1737,7 @@ function CSAR:_Reqsmoke( _unitName )
else
_distance = string.format("%.1fkm",smokedist/1000)
end
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime)
self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",_distance), self.messageTime, false, false, true)
end
return self
end
@@ -1654,13 +1809,13 @@ function CSAR:_CheckOnboard(_unitName)
--list onboard pilots
local _inTransit = self.inTransitGroups[_unitName]
if _inTransit == nil then
self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime)
self:_DisplayMessageToSAR(_unit, "No Rescued Pilots onboard", self.messageTime, false, false, true)
else
local _text = "Onboard - RTB to FARP/Airfield or MASH: "
for _, _onboard in pairs(self.inTransitGroups[_unitName]) do
_text = _text .. "\n" .. _onboard.desc
end
self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2)
self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2, false, false, true)
end
return self
end
@@ -1876,6 +2031,7 @@ function CSAR:onafterStart(From, Event, To)
self:HandleEvent(EVENTS.Takeoff, self._EventHandler)
self:HandleEvent(EVENTS.Land, self._EventHandler)
self:HandleEvent(EVENTS.Ejection, self._EventHandler)
self:HandleEvent(EVENTS.LandingAfterEjection, self._EventHandler) --shagrat
self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler)
self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler)
self:HandleEvent(EVENTS.PilotDead, self._EventHandler)
@@ -1886,6 +2042,9 @@ function CSAR:onafterStart(From, Event, To)
self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart()
end
self.mash = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(self.mashprefix):FilterStart() -- currently only GROUP objects, maybe support STATICs also?
if self.wetfeettemplate then
self.usewetfeet = true
end
self:__Status(-10)
return self
end
@@ -1919,7 +2078,12 @@ function CSAR:onbeforeStatus(From, Event, To)
self:T({From, Event, To})
-- housekeeping
self:_AddMedevacMenuItem()
self:_RefreshRadioBeacons()
if not self.BeaconTimer or (self.BeaconTimer and not self.BeaconTimer:IsRunning()) then
self.BeaconTimer = TIMER:New(self._RefreshRadioBeacons,self)
self.BeaconTimer:Start(2,self.beaconRefresher)
end
self:_CheckDownedPilotTable()
for _,_sar in pairs (self.csarUnits) do
local PilotTable = self.downedPilots
@@ -1986,6 +2150,7 @@ function CSAR:onafterStop(From, Event, To)
self:UnHandleEvent(EVENTS.Takeoff)
self:UnHandleEvent(EVENTS.Land)
self:UnHandleEvent(EVENTS.Ejection)
self:UnHandleEvent(EVENTS.LandingAfterEjection) -- shagrat
self:UnHandleEvent(EVENTS.PlayerEnterUnit)
self:UnHandleEvent(EVENTS.PlayerEnterAircraft)
self:UnHandleEvent(EVENTS.PilotDead)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+416
View File
@@ -0,0 +1,416 @@
--- **Ops** - Fleet Warehouse.
--
-- **Main Features:**
--
-- * Manage flotillas
-- * Carry out ARTY and PATROLZONE missions (AUFTRAG)
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Fleet).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Fleet
-- @image OPS_Fleet.png
--- FLEET class.
-- @type FLEET
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity of output.
-- @field Core.Set#SET_ZONE retreatZones Retreat zone set.
-- @field #boolean pathfinding Set pathfinding on for all spawned navy groups.
-- @extends Ops.Legion#LEGION
--- *A fleet of British ships at war are the best negotiators.* -- Horatio Nelson
--
-- ===
--
-- # The FLEET Concept
--
-- A FLEET consists of one or multiple FLOTILLAs. These flotillas "live" in a WAREHOUSE that has a phyiscal struction (STATIC or UNIT) and can be captured or destroyed.
--
-- # Basic Setup
--
-- A new `FLEET` object can be created with the @{#FLEET.New}(`WarehouseName`, `FleetName`) function, where `WarehouseName` is the name of the static or unit object hosting the fleet
-- and `FleetName` is the name you want to give the fleet. This must be *unique*!
--
-- myFleet=FLEET:New("myWarehouseName", "1st Fleet")
-- myFleet:SetPortZone(ZonePort1stFleet)
-- myFleet:Start()
--
-- A fleet needs a *port zone*, which is set via the @{#FLEET.SetPortZone}(`PortZone`) function. This is the zone where the naval assets are spawned and return to.
--
-- Finally, the fleet needs to be started using the @{#FLEET.Start}() function. If the fleet is not started, it will not process any requests.
--
-- ## Adding Flotillas
--
-- Flotillas can be added via the @{#FLEET.AddFlotilla}(`Flotilla`) function. See @{Ops.Flotilla#FLOTILLA} for how to create a flotilla.
--
-- myFleet:AddFlotilla(FlotillaTiconderoga)
-- myFleet:AddFlotilla(FlotillaPerry)
--
--
--
-- @field #FLEET
FLEET = {
ClassName = "FLEET",
verbose = 0,
pathfinding = false,
}
--- Supply Zone.
-- @type FLEET.SupplyZone
-- @field Core.Zone#ZONE zone The zone.
-- @field Ops.Auftrag#AUFTRAG mission Mission assigned to supply ammo or fuel.
-- @field #boolean markerOn If `true`, marker is on.
-- @field Wrapper.Marker#MARKER marker F10 marker.
--- FLEET class version.
-- @field #string version
FLEET.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Add routes?
-- DONE: Add weapon range.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLEET class object.
-- @param #FLEET self
-- @param #string WarehouseName Name of the warehouse STATIC or UNIT object representing the warehouse.
-- @param #string FleetName Name of the fleet.
-- @return #FLEET self
function FLEET:New(WarehouseName, FleetName)
-- Inherit everything from LEGION class.
local self=BASE:Inherit(self, LEGION:New(WarehouseName, FleetName)) -- #FLEET
-- Nil check.
if not self then
BASE:E(string.format("ERROR: Could not find warehouse %s!", WarehouseName))
return nil
end
-- Set some string id for output to DCS.log file.
self.lid=string.format("FLEET %s | ", self.alias)
-- Defaults
self:SetRetreatZones()
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "NavyOnMission", "*") -- An NAVYGROUP was send on a Mission (AUFTRAG).
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the FLEET. Initializes parameters and starts event handlers.
-- @function [parent=#FLEET] Start
-- @param #FLEET self
--- Triggers the FSM event "Start" after a delay. Starts the FLEET. Initializes parameters and starts event handlers.
-- @function [parent=#FLEET] __Start
-- @param #FLEET self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the FLEET and all its event handlers.
-- @param #FLEET self
--- Triggers the FSM event "Stop" after a delay. Stops the FLEET and all its event handlers.
-- @function [parent=#FLEET] __Stop
-- @param #FLEET self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "NavyOnMission".
-- @function [parent=#FLEET] NavyOnMission
-- @param #FLEET self
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- Triggers the FSM event "NavyOnMission" after a delay.
-- @function [parent=#FLEET] __NavyOnMission
-- @param #FLEET self
-- @param #number delay Delay in seconds.
-- @param Ops.NavyGroup#NAVYGROUP ArmyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
--- On after "NavyOnMission" event.
-- @function [parent=#FLEET] OnAfterNavyOnMission
-- @param #FLEET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.NavyGroup#NAVYGROUP NavyGroup The NAVYGROUP on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a flotilla to the fleet.
-- @param #FLEET self
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
-- @return #FLEET self
function FLEET:AddFlotilla(Flotilla)
-- Add flotilla to fleet.
table.insert(self.cohorts, Flotilla)
-- Add assets to flotilla.
self:AddAssetToFlotilla(Flotilla, Flotilla.Ngroups)
-- Set fleet of flotilla.
Flotilla:SetFleet(self)
-- Start flotilla.
if Flotilla:IsStopped() then
Flotilla:Start()
end
return self
end
--- Add asset group(s) to flotilla.
-- @param #FLEET self
-- @param Ops.Flotilla#FLOTILLA Flotilla The flotilla object.
-- @param #number Nassets Number of asset groups to add.
-- @return #FLEET self
function FLEET:AddAssetToFlotilla(Flotilla, Nassets)
if Flotilla then
-- Get the template group of the flotilla.
local Group=GROUP:FindByName(Flotilla.templatename)
if Group then
-- Debug text.
local text=string.format("Adding asset %s to flotilla %s", Group:GetName(), Flotilla.name)
self:T(self.lid..text)
-- Add assets to airwing warehouse.
self:AddAsset(Group, Nassets, nil, nil, nil, nil, Flotilla.skill, Flotilla.livery, Flotilla.name)
else
self:E(self.lid.."ERROR: Group does not exist!")
end
else
self:E(self.lid.."ERROR: Flotilla does not exit!")
end
return self
end
--- Set pathfinding for all spawned naval groups.
-- @param #FLEET self
-- @param #boolean Switch If `true`, pathfinding is used.
-- @return #FLEET self
function FLEET:SetPathfinding(Switch)
self.pathfinding=Switch
return self
end
--- Define a set of retreat zones.
-- @param #FLEET self
-- @param Core.Set#SET_ZONE RetreatZoneSet Set of retreat zones.
-- @return #FLEET self
function FLEET:SetRetreatZones(RetreatZoneSet)
self.retreatZones=RetreatZoneSet or SET_ZONE:New()
return self
end
--- Add a retreat zone.
-- @param #FLEET self
-- @param Core.Zone#ZONE RetreatZone Retreat zone.
-- @return #FLEET self
function FLEET:AddRetreatZone(RetreatZone)
self.retreatZones:AddZone(RetreatZone)
return self
end
--- Get retreat zones.
-- @param #FLEET self
-- @return Core.Set#SET_ZONE Set of retreat zones.
function FLEET:GetRetreatZones()
return self.retreatZones
end
--- Get flotilla by name.
-- @param #FLEET self
-- @param #string FlotillaName Name of the flotilla.
-- @return Ops.Flotilla#FLOTILLA The Flotilla object.
function FLEET:GetFlotilla(FlotillaName)
local flotilla=self:_GetCohort(FlotillaName)
return flotilla
end
--- Get flotilla of an asset.
-- @param #FLEET self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
-- @return Ops.Flotilla#FLOTILLA The flotilla object.
function FLEET:GetFlotillaOfAsset(Asset)
local flotilla=self:GetFlotilla(Asset.squadname)
return flotilla
end
--- Remove asset from flotilla.
-- @param #FLEET self
-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The flotilla asset.
function FLEET:RemoveAssetFromFlotilla(Asset)
local flotilla=self:GetFlotillaOfAsset(Asset)
if flotilla then
flotilla:DelAsset(Asset)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start FLEET FSM.
-- @param #FLEET self
function FLEET:onafterStart(From, Event, To)
-- Start parent Warehouse.
self:GetParent(self, FLEET).onafterStart(self, From, Event, To)
-- Info.
self:I(self.lid..string.format("Starting FLEET v%s", FLEET.version))
end
--- Update status.
-- @param #FLEET self
function FLEET:onafterStatus(From, Event, To)
-- Status of parent Warehouse.
self:GetParent(self).onafterStatus(self, From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
----------------
-- Transport ---
----------------
self:CheckTransportQueue()
--------------
-- Mission ---
--------------
-- Check if any missions should be cancelled.
self:CheckMissionQueue()
-----------
-- Info ---
-----------
-- General info:
if self.verbose>=1 then
-- Count missions not over yet.
local Nmissions=self:CountMissionsInQueue()
-- Asset count.
local Npq, Np, Nq=self:CountAssetsOnMission()
-- Asset string.
local assets=string.format("%d [OnMission: Total=%d, Active=%d, Queued=%d]", self:CountAssets(), Npq, Np, Nq)
-- Output.
local text=string.format("%s: Missions=%d, Flotillas=%d, Assets=%s", fsmstate, Nmissions, #self.cohorts, assets)
self:I(self.lid..text)
end
------------------
-- Mission Info --
------------------
if self.verbose>=2 then
local text=string.format("Missions Total=%d:", #self.missionqueue)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end
local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.Nassets or 0)
local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage())
text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target)
end
self:I(self.lid..text)
end
--------------------
-- Transport Info --
--------------------
if self.verbose>=2 then
local text=string.format("Transports Total=%d:", #self.transportqueue)
for i,_transport in pairs(self.transportqueue) do
local transport=_transport --Ops.OpsTransport#OPSTRANSPORT
local prio=string.format("%d/%s", transport.prio, tostring(transport.importance)) ; if transport.urgent then prio=prio.." (!)" end
local carriers=string.format("Ncargo=%d/%d, Ncarriers=%d", transport.Ncargo, transport.Ndelivered, transport.Ncarrier)
text=text..string.format("\n[%d] UID=%d: Status=%s, Prio=%s, Cargo: %s", i, transport.uid, transport:GetState(), prio, carriers)
end
self:I(self.lid..text)
end
-------------------
-- Flotilla Info --
-------------------
if self.verbose>=3 then
local text="Flotillas:"
for i,_flotilla in pairs(self.cohorts) do
local flotilla=_flotilla --Ops.Flotilla#FLOTILLA
local callsign=flotilla.callsignName and UTILS.GetCallsignName(flotilla.callsignName) or "N/A"
local modex=flotilla.modex and flotilla.modex or -1
local skill=flotilla.skill and tostring(flotilla.skill) or "N/A"
-- Flotilla text.
text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", flotilla.name, flotilla:GetState(), flotilla.aircrafttype, flotilla:CountAssets(true), #flotilla.assets, callsign, modex, skill)
end
self:I(self.lid..text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "NavyOnMission".
-- @param #FLEET self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.ArmyGroup#ARMYGROUP ArmyGroup Ops army group on mission.
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
function FLEET:onafterNavyOnMission(From, Event, To, NavyGroup, Mission)
-- Debug info.
self:T(self.lid..string.format("Group %s on %s mission %s", NavyGroup:GetName(), Mission:GetType(), Mission:GetName()))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
--- **Ops** - Flotilla is a small naval group belonging to a fleet.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all flotilla members.
-- * Define mission types, this flotilla can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause flotilla operations.
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Flotilla
-- @image OPS_Flotilla.png
--- FLOTILLA class.
-- @type FLOTILLA
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
-- @extends Ops.Cohort#COHORT
--- *No captain can do very wrong if he places his ship alongside that of an enemy.* -- Horation Nelson
--
-- ===
--
-- # The FLOTILLA Concept
--
-- A FLOTILLA is an essential part of a FLEET.
--
--
--
-- @field #FLOTILLA
FLOTILLA = {
ClassName = "FLOTILLA",
verbose = 0,
weaponData = {},
}
--- FLOTILLA class version.
-- @field #string version
FLOTILLA.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new FLOTILLA object and start the FSM.
-- @param #FLOTILLA self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this flotilla. Default 3.
-- @param #string FlotillaName Name of the flotilla, e.g. "VFA-37".
-- @return #FLOTILLA self
function FLOTILLA:New(TemplateGroupName, Ngroups, FlotillaName)
-- Inherit everything from COHORT class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, FlotillaName)) -- #FLOTILLA
-- All flotillas get mission type Nothing.
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
-- Get initial ammo.
self.ammo=self:_CheckAmmo()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Flotilla specific user functions.
--- Set fleet of this flotilla.
-- @param #FLOTILLA self
-- @param Ops.Fleet#FLEET Fleet The fleet.
-- @return #FLOTILLA self
function FLOTILLA:SetFleet(Fleet)
self.legion=Fleet
return self
end
--- Get fleet of this flotilla.
-- @param #FLOTILLA self
-- @return Ops.Fleet#FLEET The fleet.
function FLOTILLA:GetFleet()
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #FLOTILLA self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLOTILLA:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
self:I(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #FLOTILLA self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function FLOTILLA:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:T(self.lid..text)
-- Weapon data info.
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData) do
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+170
View File
@@ -0,0 +1,170 @@
--- **Ops** - Brigade Platoon.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all platoon members.
-- * Define modex and callsigns.
-- * Define mission types, this platoon can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause platoon operations.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Platoon
-- @image OPS_Platoon.png
--- PLATOON class.
-- @type PLATOON
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field Ops.OpsGroup#OPSGROUP.WeaponData weaponData Weapon data table with key=BitType.
-- @extends Ops.Cohort#COHORT
--- *Some cool cohort quote* -- Known Author
--
-- ===
--
-- # The PLATOON Concept
--
-- A PLATOON is essential part of an BRIGADE.
--
--
--
-- @field #PLATOON
PLATOON = {
ClassName = "PLATOON",
verbose = 0,
weaponData = {},
}
--- PLATOON class version.
-- @field #string version
PLATOON.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new PLATOON object and start the FSM.
-- @param #PLATOON self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this platoon. Default 3.
-- @param #string PlatoonName Name of the platoon, e.g. "VFA-37".
-- @return #PLATOON self
function PLATOON:New(TemplateGroupName, Ngroups, PlatoonName)
-- Inherit everything from COHORT class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, PlatoonName)) -- #PLATOON
-- All platoons get mission type Nothing.
self:AddMissionCapability(AUFTRAG.Type.NOTHING, 50)
-- Get ammo.
self.ammo=self:_CheckAmmo()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Platoon specific user functions.
--- Set brigade of this platoon.
-- @param #PLATOON self
-- @param Ops.Brigade#BRIGADE Brigade The brigade.
-- @return #PLATOON self
function PLATOON:SetBrigade(Brigade)
self.legion=Brigade
return self
end
--- Get brigade of this platoon.
-- @param #PLATOON self
-- @return Ops.Brigade#BRIGADE The brigade.
function PLATOON:GetBrigade()
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #PLATOON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function PLATOON:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
self:I(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #PLATOON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function PLATOON:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:T(self.lid..text)
-- Weapon data info.
if self.verbose>=3 and self.weaponData then
local text="Weapon Data:"
for bit,_weapondata in pairs(self.weaponData) do
local weapondata=_weapondata --Ops.OpsGroup#OPSGROUP.WeaponData
text=text..string.format("\n- Bit=%s: Rmin=%.1f km, Rmax=%.1f km", bit, weapondata.RangeMin/1000, weapondata.RangeMax/1000)
end
self:I(self.lid..text)
end
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Misc functions.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -31,7 +31,7 @@
-- @field #string tankergroupname Name of the late activated tanker template group.
-- @field Wrapper.Group#GROUP tanker Tanker group.
-- @field Wrapper.Airbase#AIRBASE airbase The home airbase object of the tanker. Normally the aircraft carrier.
-- @field Core.Radio#BEACON beacon Tanker TACAN beacon.
-- @field Core.Beacon#BEACON beacon Tanker TACAN beacon.
-- @field #number TACANchannel TACAN channel. Default 1.
-- @field #string TACANmode TACAN mode, i.e. "X" or "Y". Default "Y". Use only "Y" for AA TACAN stations!
-- @field #string TACANmorse TACAN morse code. Three letters identifying the TACAN station. Default "TKR".
@@ -784,10 +784,11 @@ end
-- @param #RECOVERYTANKER self
-- @param #number channel TACAN channel. Default 1.
-- @param #string morse TACAN morse code identifier. Three letters. Default "TKR".
-- @param #string mode TACAN mode, which can be either "Y" (default) or "X".
-- @return #RECOVERYTANKER self
function RECOVERYTANKER:SetTACAN(channel, morse)
function RECOVERYTANKER:SetTACAN(channel, morse, mode)
self.TACANchannel=channel or 1
self.TACANmode="Y"
self.TACANmode=mode or "Y"
self.TACANmorse=morse or "TKR"
self.TACANon=true
return self
@@ -1625,7 +1626,6 @@ function RECOVERYTANKER:_ActivateTACAN(delay)
if delay and delay>0 then
-- Schedule TACAN activation.
--SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay)
self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self)
else
+1 -1
View File
@@ -802,7 +802,7 @@ function RESCUEHELO:_OnEventCrashOrEject(EventData)
-- Debug.
local text=string.format("Unit %s crashed or ejected.", unitname)
MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug)
self:I(self.lid..text)
self:T(self.lid..text)
-- Get coordinate of unit.
local coord=unit:GetCoordinate()
+911
View File
@@ -0,0 +1,911 @@
--- **Ops** - (R2.5) - Manage behavior of the Carrier Strike Group.
--
-- **Main Features:**
--
-- * Nice stuff.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Skipper
-- @image OPS_Skipper.png
--- SKIPPER class.
-- @type SKIPPER
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string sid Class id string for output to DCS log file.
-- @field #string carriername The name of the carrier unit.
-- @field Wrapper.Group#GROUP group The carrier strike group.
-- @field #table menu Table of menu items.
-- @field Functional.Warehouse#WAREHOUSE warehouse The warehouse of the carrier.
-- @field Functional.Artillery#ARTY arty The artillery object of the carrier.
-- @field #table intruders Table of intruders, i.e. groups inside the CCA. Each element is of type #SKIPPPER.Intruder.
-- @field Ops.CarrierAirWing#CVW cvw Carrier Air Wing.
-- @extends Ops.Airboss#AIRBOSS
--- Be surprised!
--
-- ===
--
-- ![Banner Image](..\Presentations\SKIPPER\Skipper_Main.jpg)
--
-- # The SKIPPER Concept
--
--
--
-- @field #SKIPPER
SKIPPER = {
ClassName = "SKIPPER",
sid = nil,
carriername = nil,
group = nil,
menu = nil,
warehouse = nil,
arty = nil,
intruders = {},
cvw = nil,
}
--- Intruder.
-- @type SKIPPER.Intruder
-- @field Wrapper.Group#GROUP group Intruder group object.
-- @field #string groupname Name of the intruder group.
-- @field #number time0 Abs. mission time first detected inside CCA.
-- @field #number dist0 Distance first detected inside CCA.
-- @field DCS#Coalition.Side coalition Coalition side.
-- @field #number threadlevel Thread level of intruder.
-- @field #string threadtext Thread text.
-- @field #number category Group category.
-- @field #string categoryname Group category name.
-- @field #string typename Type name of group.
-- @field #table menu The defence menu entries.
--- FlightControl class version.
-- @field #string version
SKIPPER.version="0.0.5"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new SKIPPER class object for a specific aircraft carrier unit.
-- @param #SKIPPER self
-- @param #string carriername Name of the carrier.
-- @param #string alias Skipper alias.
-- @return #SKIPPER self
function SKIPPER:New(carriername, alias)
-- Inherit everything from FSM class.
--local self=BASE:Inherit(self, FSM:New()) -- #SKIPPER
local self=BASE:Inherit(self, AIRBOSS:New(carriername, alias)) -- #SKIPPER
if not self.carrier then
BASE:E(string.format("ERROR: Could not find carrier %s!", carriername))
return nil
end
--self.alias=alias or carriername
--self:
self.carriername=carriername
self.group=self.carrier:GetGroup()
self.arty=ARTY:New(self.group, carriername, self.alias)
self.warehouse=WAREHOUSE:New(carriername, self.alias)
local skipper=self
--- Function for WAREHOUSE self request events.
function self.warehouse:OnAfterSelfRequest(From,Event,To,groupset,request)
env.info("FF Warehous self request!")
skipper:_SelfRequest(self, groupset, request)
end
-- Set some string id for output to DCS.log file.
self.sid=string.format("SKIPPER %s |", self.carriername)
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "SkipperStatus", "*") -- Skipper status update.
self:AddTransition("*", "IntruderAlert", "*") -- New intruder detected.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the SKIPPER. Initializes parameters and starts event handlers.
-- @function [parent=#SKIPPER] Start
-- @param #SKIPPER self
--- Triggers the FSM event "Start" after a delay. Starts the SKIPPER. Initializes parameters and starts event handlers.
-- @function [parent=#SKIPPER] __Start
-- @param #SKIPPER self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the SKIPPER and all its event handlers.
-- @param #SKIPPER self
--- Triggers the FSM event "Stop" after a delay. Stops the SKIPPER and all its event handlers.
-- @function [parent=#SKIPPER] __Stop
-- @param #SKIPPER self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "SkipperStatus".
-- @function [parent=#SKIPPER] SkipperStatus
-- @param #SKIPPER self
--- Triggers the FSM event "SkipperStatus" after a delay.
-- @function [parent=#SKIPPER] __SkipperStatus
-- @param #SKIPPER self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "IntruderAlert".
-- @function [parent=#SKIPPER] IntruderAlert
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder Intruder data table.
--- Triggers the FSM delayed event "IntruderAlert".
-- @function [parent=#SKIPPER] __IntruderAlert
-- @param #SKIPPER self
-- @param #number delay Delay in seconds before the function is called.
-- @param #SKIPPER.Intruder Intruder data table.
--- On after "IntruderAlert" event user function. Called when a missile was launched.
-- @function [parent=#SKIPPER] OnAfterIntruderAlert
-- @param #SKIPPER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #SKIPPER.Intruder Intruder data table.
-- Debug trace.
if true then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
self.arty:GetAmmo(true)
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Optimized F10 radio menu for a single carrier. The menu entries will be stored directly under F10 Other/Skipper/ and not F10 Other/Skipper/"Carrier Alias"/.
-- **WARNING**: If you use this with two SKIPPER objects/carriers, the radio menu will be screwed up!
-- @param #SKIPPER self
-- @param #boolean switch If true or nil single menu is enabled. If false, menu is for multiple carriers in the mission.
-- @return #SKIPPER self
function SKIPPER:SetMenuSingleCarrier(switch)
if switch==true or switch==nil then
self.menusingle=true
else
self.menusingle=false
end
return self
end
--- Get carrier coalition.
-- @param #SKIPPER self
-- @return #number Coalition side of carrier.
function SKIPPER:GetCoalition()
return self.carrier:GetCoalition()
end
--- Get carrier coordinate.
-- @param #SKIPPER self
-- @return Core.Point#COORDINATE Carrier coordinate.
function SKIPPER:GetCoordinate()
return self.carrier:GetCoordinate()
end
--- Get WAREHOUSE object associated with the carrier.
-- @param #SKIPPER self
-- @return Functional.Warehouse#WAREHOUSE Warehouse object.
function SKIPPER:GetWarehouseCarrier()
return self.warehouse
end
--- Get ARTY object associated with the carrier strike group.
-- @param #SKIPPER self
-- @return Functional.Artillery#ARTY Arty object.
function SKIPPER:GetArty()
return self.arty
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Start SKIPPER FSM. Handle events.
-- @param #SKIPPER self
function SKIPPER:onafterStart(From, Event, To)
-- Start parent airboss.
self:GetParent(self).onafterStart(self, From, Event, To)
-- Info.
self:I(self.sid..string.format("Starting SKIPPER v%s for carrier %s on map %s.", SKIPPER.version, self.carriername, self.theatre))
-- Start ARTY.
self.arty:Start()
-- Start Warehouse.
self.warehouse:Start()
-- Add F10 radio menu.
self:_SetMenuCoalition()
-- Handle events.
self:HandleEvent(EVENTS.Birth)
self:HandleEvent(EVENTS.EngineStartup)
self:HandleEvent(EVENTS.Takeoff)
self:HandleEvent(EVENTS.Land)
self:HandleEvent(EVENTS.EngineShutdown)
self:HandleEvent(EVENTS.Crash)
-- Init status updates.
self:__SkipperStatus(-1)
end
--- Update status.
-- @param #SKIPPER self
function SKIPPER:onafterSkipperStatus(From, Event, To)
local fsmstate=self:GetState()
-- Check zone for flights inbound.
self:_CheckIntruder()
-- Info text.
local text=string.format("State %s", fsmstate)
self:I(self.sid..text)
self:__SkipperStatus(-30)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- CCA Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Scan carrier zone for (new) units.
-- @param #SKIPPER self
function SKIPPER:_CheckIntruder()
-- Carrier position.
local coord=self:GetCoordinate()
-- Scan radius = radius of the CCA.
local RCCZ=self.zoneCCA:GetRadius()
-- Debug info.
self:T(self.sid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ)))
-- Scan units in carrier zone.
local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false)
-- Make a table with all groups currently in the CCA zone.
local insideCCA={}
for _,_unit in pairs(unitscan) do
local unit=_unit --Wrapper.Unit#UNIT
-- Necessary conditions to be met:
local airborne=unit:IsAir()
local inzone=unit:IsInZone(self.zoneCCA)
local friendly=self:GetCoalition()==unit:GetCoalition()
-- Check if this an aircraft and that it is airborne and closing in.
if inzone then
local group=unit:GetGroup()
local groupname=group:GetName()
if insideCCA[groupname]==nil and groupname~=self.group:GetName() then
insideCCA[groupname]=group
end
end
end
-- Intruder menu needs update.
local updateintrudermenu=false
-- Find out if any known intruder is not in the CCA any more.
for i=#self.intruders,1,-1 do
local intruder=self.intruders[i] --#SKIPPER.Intruder
-- Loop over current groups in CCA.
local gotit=false
for groupname,_group in pairs(insideCCA) do
local group=_group --Wrapper.Group#GROUP
if groupname==intruder.groupname then
gotit=true
end
end
-- Intruder is gone!
if not gotit then
-- Remove from table.
table.remove(self.intruders, i)
-- Menu update required.
updateintrudermenu=true
end
end
for groupname,_group in pairs(insideCCA) do
local group=_group --Wrapper.Group#GROUP
-- Find out if any known intruder is not in the CCA any more.
local gotit=false
for i=1,#self.intruders do
local intruder=self.intruders[i] --#SKIPPER.Intruder
if groupname==intruder.groupname then
gotit=true
end
end
if not gotit then
-- Create a new intruder table.
local intruder={} --#SKIPPER.Intruder
intruder.coalition=group:GetCoalition()
intruder.group=group
intruder.threadlevel, intruder.threadtext=group:GetUnit(1):GetThreatLevel()
intruder.time0=timer.getAbsTime()
intruder.dist0=self:GetCoordinate():Get2DDistance(group:GetCoordinate())
intruder.groupname=groupname
intruder.typename=group:GetTypeName()
intruder.category=group:GetCategory()
intruder.categoryname=group:GetCategoryName()
-- Add intruder to list.
table.insert(self.intruders, intruder)
-- Trigger alert!
self:IntruderAlert(intruder)
-- Menu update required.
updateintrudermenu=true
end
end
-- Update intruder menu if necessary.
if updateintrudermenu then
self:_SortIntruders()
self:_UpdateIntruderMenu()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Menu Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Patrol carrier.
-- @param #SKIPPER self
-- @return #SKIPPER self
function SKIPPER:_SetMenuCoalition()
-- Get coalition.
local Coalition=self:GetCoalition()
-- Init menu table.
self.menu=self.menu or {}
-- Init menu coalition table.
self.menu[Coalition]=self.menu[Coalition] or {}
-- Shortcut.
local menu=self.menu[Coalition]
if self.menusingle then
-- F10/Skipper/...
if not menu.Skipper then
menu.Skipper=MENU_COALITION:New(Coalition, "Skipper")
end
else
-- F10/Skipper/<Carrier Alias>/...
if not menu.Root then
menu.Root=MENU_COALITION:New(Coalition, "Skipper")
end
menu.Skipper=MENU_COALITION:New(Coalition, self.alias, menu.Root)
end
------------------
-- Defence Menu --
------------------
menu.SetSpeed = MENU_COALITION:New(Coalition, "Set Speed", menu.Skipper)
menu.SetSpeed_00 = MENU_COALITION_COMMAND:New(Coalition, "Hold Position", menu.SetSpeed, self._SetSpeed, self, 0)
menu.SetSpeed_05 = MENU_COALITION_COMMAND:New(Coalition, "5 knots", menu.SetSpeed, self._SetSpeed, self, 5)
menu.SetSpeed_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetSpeed, self._SetSpeed, self, 10)
menu.SetSpeed_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetSpeed, self._SetSpeed, self, 15)
menu.SetSpeed_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetSpeed, self._SetSpeed, self, 20)
menu.SetSpeed_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetSpeed, self._SetSpeed, self, 25)
menu.SetSpeed_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetSpeed, self._SetSpeed, self, 30)
menu.SetSpeed_99 = MENU_COALITION_COMMAND:New(Coalition, "Restore Route", menu.SetSpeed, self.CarrierResume, self)
menu.Recovery=MENU_COALITION:New(Coalition, "Recovery", menu.Skipper)
-- Set wind on deck.
menu.SetWoD = MENU_COALITION:New(Coalition, "Wind on Deck", menu.Recovery)
menu.SetWoD_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetWoD, self._SetWoD, self, 10)
menu.SetWoD_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetWoD, self._SetWoD, self, 15)
menu.SetWoD_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetWoD, self._SetWoD, self, 20)
menu.SetWoD_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetWoD, self._SetWoD, self, 25)
menu.SetWoD_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetWoD, self._SetWoD, self, 30)
-- Set Duration.
menu.SetRtime = MENU_COALITION:New(Coalition, "Duration", menu.Recovery)
menu.SetRtime_15 = MENU_COALITION_COMMAND:New(Coalition, "15 min", menu.SetRtime, self._SetRtime, self, 15)
menu.SetRtime_30 = MENU_COALITION_COMMAND:New(Coalition, "30 min", menu.SetRtime, self._SetRtime, self, 30)
menu.SetRtime_45 = MENU_COALITION_COMMAND:New(Coalition, "45 min", menu.SetRtime, self._SetRtime, self, 45)
menu.SetRtime_60 = MENU_COALITION_COMMAND:New(Coalition, "60 min", menu.SetRtime, self._SetRtime, self, 60)
menu.SetRtime_90 = MENU_COALITION_COMMAND:New(Coalition, "90 min", menu.SetRtime, self._SetRtime, self, 90)
-- Start/Stop.
menu.SetUturn = MENU_COALITION_COMMAND:New(Coalition, "U-turn On/Off", menu.Recovery, self._SetUturn, self)
menu.CaseI = MENU_COALITION_COMMAND:New(Coalition, "Start CASE I", menu.Recovery, self._StartCaseX, self, 1)
menu.CaseII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE II", menu.Recovery, self._StartCaseX, self, 2)
menu.CaseIII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE III", menu.Recovery, self._StartCaseX, self, 3)
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Pause/Unpause", menu.Recovery, self._Rpause, self)
menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Stop Recovery", menu.Recovery, self._Rstop, self)
------------------
-- Defence Menu --
------------------
menu.Defence = MENU_COALITION:New(Coalition, "Defence", menu.Skipper)
menu.Defence_SetROE = MENU_COALITION:New(Coalition, "Set ROE", menu.Defence)
menu.Defence_SetROE_Hold = MENU_COALITION_COMMAND:New(Coalition, "Weapon Hold", menu.Defence_SetROE, self._SetROE, self, "Hold")
menu.Defence_SetROE_Free = MENU_COALITION_COMMAND:New(Coalition, "Weapon Free", menu.Defence_SetROE, self._SetROE, self, "Free")
menu.Defence_SetROE_Return = MENU_COALITION_COMMAND:New(Coalition, "Return Fire", menu.Defence_SetROE, self._SetROE, self, "Return")
menu.Defence_Intruders = MENU_COALITION:New(Coalition, "Intruders", menu.Defence)
menu.Defence_Ammo = MENU_COALITION_COMMAND:New(Coalition, "Report Ammo", menu.Defence, self.arty.GetAmmo, self.arty, true)
menu.Defence_IntruderR = MENU_COALITION_COMMAND:New(Coalition, "Report Intruders", menu.Defence, self._ListIntruders, self)
menu.Defence_LaunchCAP = MENU_COALITION_COMMAND:New(Coalition, "Lauch CAP", menu.Defence, self.LaunchCAP, self)
--------------------
-- Warehouse Menu --
--------------------
if self.warehouse then
menu.Warehouse=MENU_COALITION:New(Coalition, "Warehouse", menu.Skipper)
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Reports On/Off", menu.Warehouse, self.WarehouseReportsToggle, self)
menu.Warehouse_Assets = MENU_COALITION_COMMAND:New(Coalition, "Report Assets", menu.Warehouse, self.ReportWarehouseStock, self)
end
end
--- Warehouse reports on/off.
-- @param #SKIPPER self
function SKIPPER:WarehouseReportsToggle()
self.warehouse.Report=not self.warehouse.Report
MESSAGE:New(string.format("Warehouse reports are now %s", tostring(self.warehouse.Report)), 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
end
--- Report warehouse stock.
-- @param #SKIPPER self
function SKIPPER:ReportWarehouseStock()
local text=self.warehouse:_GetStockAssetsText(false)
MESSAGE:New(text, 10, "SKIPPER", true):ToCoalition(self:GetCoalition())
end
--- Handle self requests from carrier warehouse.
-- @param #SKIPPER self
-- @param Functional.Warehouse#WAREHOUSE warehouse The (carrier) warehouse object.
-- @param Core.Set#SET_GROUP groupset Group set.
-- @param Functional.Warehouse#WAREHOUSE.Pendingitem request Request.
function SKIPPER:_SelfRequest(warehouse, groupset, request)
local assignment=request.assignment
env.info(string.format("FF warehouse self request assignment %s", tostring(assignment)))
end
--- Launch a Intercept flight.
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder intruder Intruder group.
function SKIPPER:LaunchIntercept(intruder)
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", false)
if n>0 then
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ASSIGNMENT, "Intercept", 1, nil, nil, nil, "Intercept")
local capcoord=intruder.group:GetCoordinate()
capcoord:MarkToAll("Intruder coord")
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
local groupset=Groupset --Core.Set#SET_GROUP
for _,_group in pairs(groupset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
if request.assignment=="Intercept" then
-- Task orbit.
local tasks={}
for _,unit in pairs(intruder.group:GetUnits()) do
tasks[#tasks+1]=group:TaskAttackUnit(unit)
end
local speed=group:GetSpeedMax()
local altitude=intruder.group:GetAltitude()
-- Create waypoints.
local wp={}
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
wp[2]=self:GetCoordinate():SetAltitude(altitude):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, speed, {tasks}, "Attack Intruder")
-- Start uncontrolled group.
group:StartUncontrolled()
--airboss:SetExcludeAI()
-- Route group
group:Route(wp)
end
end
end
else
MESSAGE:New("No INTERCEPT fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
end
end
--- Launch a CAP flight.
-- @param #SKIPPER self
function SKIPPER:LaunchCAP()
--local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, WAREHOUSE.Attribute.AIR_FIGHTER, false)
local n=self.warehouse:GetNumberOfAssets(WAREHOUSE.Descriptor.ASSIGNMENT, "CAP", false)
if n>0 then
self.warehouse:AddRequest(self.warehouse, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_FIGHTER, 1, nil, nil, nil, "CAP")
local capcoord=self.zoneCCA:GetRandomCoordinate(self.zoneCCA:GetRadius()/2)
capcoord:MarkToAll("CAP coord")
function self.warehouse.OnAfterSelfRequest(warehouse, From, Event, To, Groupset, Request)
local request=Request --Functional.Warehouse#WAREHOUSE.Pendingitem
local groupset=Groupset --Core.Set#SET_GROUP
for _,_group in pairs(groupset:GetSet()) do
local group=_group --Wrapper.Group#GROUP
if request.assignment=="CAP" then
local alt=UTILS.FeetToMeters(20000)
-- Task orbit.
local taskOrbit=group:TaskOrbit(capcoord, alt, UTILS.KnotsToMps(400), capcoord:Translate(UTILS.NMToMeters(15), 0))
local wp={}
wp[1]=self:GetCoordinate():WaypointAirTakeOffParking()
wp[2]=self:GetCoordinate():SetAltitude(alt):WaypointAirTurningPoint(COORDINATE.WaypointAltType.BARO, UTILS.KnotsToKmph(350), {taskOrbit}, "CAP")
-- Start uncontrolled group.
group:StartUncontrolled()
--airboss:SetExcludeAI()
-- Route group.
group:Route(wp)
end
end
end
else
MESSAGE:New("No CAP fighters currently available", 5, "SKIPPER"):ToCoalition(self:GetCoalition())
end
end
--- Enage an intruder by ARTY.
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder intruder The intruder data.
-- @param #number weapontype Type of weapon.
function SKIPPER:EngageArty(intruder, weapontype)
weapontype=weapontype or ARTY.WeaponType.Auto
if intruder.group and intruder.group:IsAlive() then
-- Current position.
local coord=intruder.group:GetCoordinate()
if intruder.category==Group.Category.GROUND or intruder.category==Group.Category.TRAIN then
self.arty:AssignTargetCoord(coord, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
elseif intruder.category==Group.Category.SHIP then
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
self.arty:AssignAttackGroup(intruder.group, 50, 100, 5, 1, nil, weapontype, intruder.groupname, false)
end
end
end
--- Sort intruders table.
-- @param #SKIPPER self
function SKIPPER:_SortIntruders()
-- Sort potential section members wrt to distance to lead.
local function _sort(_a,_b)
local a=_a --#SKIPPER.Intruder
local b=_b --#SKIPPER.Intruder
return a.threadlevel>b.threadlevel
end
table.sort(self.intruders, _sort)
end
--- Add sub menu for this intruder.
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder intruder The intruder data.
-- @param #boolean removed If true, an intruder was removed. If false or nil, a new intruder was added.
function SKIPPER:_UpdateIntruderMenu()
for _,_intruder in ipairs(self.intruders) do
local intruder=_intruder --#SKIPPER.Intruder
self:_AddIntruderMenu(intruder)
end
end
--- Add sub menu for this intruder.
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder intruder The intruder data.
function SKIPPER:_AddIntruderMenu(intruder)
local Coalition=self:GetCoalition()
local root=self.menu[Coalition].Defence_Intruders
local menu=MENU_COALITION:New(Coalition, intruder.typename, root)
if intruder.category==Group.Category.GROUND then
MENU_COALITION_COMMAND:New(Coalition, "Engage Shells", menu, self.EngageArty, self, intruder, ARTY.WeaponType.Cannon)
MENU_COALITION_COMMAND:New(Coalition, "Engage Cruise M", menu, self.EngageArty, self, intruder, ARTY.WeaponType.CruiseMissile)
elseif intruder.category==Group.Category.SHIP then
MENU_COALITION_COMMAND:New(Coalition, "Engage", menu, self.EngageArty, self, intruder)
elseif intruder.category==Group.Category.AIRPLANE or intruder.category==Group.Category.HELICOPTER then
MENU_COALITION_COMMAND:New(Coalition, "Engage A2A", menu, self.LaunchIntercept, self, intruder)
end
-- Set menu.
intruder.menu=menu
end
--- Remove sub menu for this intruder.
-- @param #SKIPPER self
-- @param #SKIPPER.Intruder intruder The intruder data.
function SKIPPER:_RemoveIntruderMenu(intruder)
if intruder.menu then
local menu=intruder.menu --Core.Menu#MENU_COALITION
menu:Remove()
intruder.menu=nil
end
end
--- Intruders.
-- @param #SKIPPER self
function SKIPPER:_ListIntruders()
local text="Current Intruders:"
for i,_intruder in pairs(self.intruders) do
local intruder=_intruder --#SKIPPER.Intruder
text=text..string.format("\n[%d] %s*%d, %s [%d/10]", i, intruder.typename, #intruder.group:GetUnits(), intruder.categoryname, intruder.threadlevel)
end
if #self.intruders==0 then
text=text.." none."
end
MESSAGE:New(text, 10, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Start Case X recovery.
-- @param #SKIPPER self
-- @param #number case Recovery case (1,2,3).
function SKIPPER:_StartCaseX(case)
--[[
self.skipperTime=self.skipperTime or 30
self.skipperSpeed=self.skipperSpeed or 25
if self.skipperUturn==nil then
self.skipperUturn=false
end
]]
-- Inform player.
local text=string.format("Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn))
if self:IsRecovering() then
text="negative, carrier is already recovering."
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
return
end
-- Recovery staring in 5 min for 30 min.
local t0=timer.getAbsTime()+5*60
local t9=t0+self.skipperTime*60
local C0=UTILS.SecondsToClock(t0)
local C9=UTILS.SecondsToClock(t9)
-- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on.
self:AddRecoveryWindow(C0, C9, case, 30, true, self.skipperSpeed, self.skipperUturn)
MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Toggle recovery U-turn option.
-- @param #SKIPPER self
function SKIPPER:_SetUturn()
self.skipperUturn=not self.skipperUturn
MESSAGE:New(string.format("Recovery U-turn is now %s.", tostring(self.skipperUturn)), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Set manual recovery duration.
-- @param #SKIPPER self
-- @param #number time Duration in minutes.
function SKIPPER:_SetRtime(time)
self.skipperTime=time
MESSAGE:New(string.format("Recovery duration set to %d min.", time), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Set wind on deck for manual recovery start.
-- @param #SKIPPER self
-- @param #number speed Speed in knots.
function SKIPPER:_SetWoD(speed)
self.skipperSpeed=speed
MESSAGE:New(string.format("Wind on Deck set to %d knots.", speed), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Set new speed for all waypoints.
-- @param #SKIPPER self
-- @param #number speed Speed in knots.
function SKIPPER:_SetSpeed(speed)
-- Loop over waypoints.
for n=1,#self.waypoints do
local coord=self.waypoints[n] --Core.Point#COORDINATE
coord.Velocity=UTILS.KnotsToMps(speed)
end
self:_PatrolRoute()
end
--- Set rules of engagement.
-- @param #SKIPPER self
-- @param #string roe "Hold", "Free", "Return".
function SKIPPER:_SetROE(roe)
if roe=="Hold" then
self.group:OptionROEHoldFire()
elseif roe=="Free" then
self.group:OptionROEOpenFire()
elseif roe=="Return" then
self.group:OptionROEReturnFire()
end
MESSAGE:New(string.format("ROE set to %s", roe), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Set alarm state. (Not useful/working for ships.)
-- @param #SKIPPER self
-- @param #string state "Green", "Red", "Auto".
function SKIPPER:_SetALS(state)
if state=="Green" then
self.group:OptionAlarmStateGreen()
elseif state=="Red" then
self.group:OptionAlarmStateRed()
elseif state=="Auto" then
self.group:OptionAlarmStateAuto()
end
MESSAGE:New(string.format("Alarm state set to %s", state), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Function to stop the carrier.
-- @param #SKIPPER self
function SKIPPER:CarrierHold()
env.info("Carrier Hold!")
-- Get current position.
local pos=self.group:GetCoordinate()
-- Create a new waypoint.
local wp=pos:WaypointGround(0)
-- Create new route consisting of only this position ==> Stop!
self.group:Route({wp})
MESSAGE:New(string.format("Carrier is holding current position."), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
--- Function to stop the carrier.
-- @param #SKIPPER self
function SKIPPER:CarrierResume()
env.info("Carrier Resume Route!")
self:_InitWaypoints()
local nextWP,n=self:_GetNextWaypoint()
self:_PatrolRoute(n)
MESSAGE:New(string.format("Carrier is resuming route to waypoint #%d.", n), 5, self.ClassName):ToCoalition(self:GetCoalition())
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+321
View File
@@ -0,0 +1,321 @@
--- **Ops** - Airwing Squadron.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all squadron members.
-- * Define modex and callsigns.
-- * Define mission types, this squadron can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause squadron operations.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Squadron
-- @image OPS_Squadron.png
--- SQUADRON class.
-- @type SQUADRON
-- @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 #string name Name of the squadron.
-- @field #string templatename Name of the template group.
-- @field #string aircrafttype Type of the airframe the squadron is using.
-- @field Wrapper.Group#GROUP templategroup Template group.
-- @field #number ngrouping User defined number of units in the asset group.
-- @field #table assets Squadron assets.
-- @field #table missiontypes Capabilities (mission types and performances) of the squadron.
-- @field #number fuellow Low fuel threshold.
-- @field #boolean fuellowRefuel If `true`, flight tries to refuel at the nearest tanker.
-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight.
-- @field #number repairtime Time in seconds for each
-- @field #string livery Livery of the squadron.
-- @field #number skill Skill of squadron members.
-- @field #number modex Modex.
-- @field #number modexcounter Counter to incease modex number for assets.
-- @field #string callsignName Callsign name.
-- @field #number callsigncounter Counter to increase callsign names for new assets.
-- @field #number Ngroups Number of asset flight groups this squadron has.
-- @field #number engageRange Mission range in meters.
-- @field #string attribute Generalized attribute of the squadron template group.
-- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil.
-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probe=1). Default nil.
-- @field #table tacanChannel List of TACAN channels available to the squadron.
-- @field #number radioFreq Radio frequency in MHz the squad uses.
-- @field #number radioModu Radio modulation the squad uses.
-- @field #string takeoffType Take of type.
-- @field #table parkingIDs Parking IDs for this squadron.
-- @field #boolean despawnAfterLanding Aircraft are despawned after landing.
-- @field #boolean despawnAfterHolding Aircraft are despawned after holding.
-- @extends Ops.Cohort#COHORT
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance* -- Adolf Galland
--
-- ===
--
-- # The SQUADRON Concept
--
-- A SQUADRON is essential part of an @{Ops.Airwing#AIRWING} and consists of **one** type of aircraft.
--
--
--
-- @field #SQUADRON
SQUADRON = {
ClassName = "SQUADRON",
verbose = 0,
modex = nil,
modexcounter = 0,
callsignName = nil,
callsigncounter= 11,
tankerSystem = nil,
refuelSystem = nil,
}
--- SQUADRON class version.
-- @field #string version
SQUADRON.version="0.8.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DONE: Parking spots for squadrons?
-- DONE: Engage radius.
-- DONE: Modex.
-- DONE: Call signs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new SQUADRON object and start the FSM.
-- @param #SQUADRON self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this squadron. Default 3.
-- @param #string SquadronName Name of the squadron, e.g. "VFA-37".
-- @return #SQUADRON self
function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, COHORT:New(TemplateGroupName, Ngroups, SquadronName)) -- #SQUADRON
-- Everyone can ORBIT.
self:AddMissionCapability(AUFTRAG.Type.ORBIT)
-- Refueling system.
self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable())
self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker())
------------------------
--- Pseudo Functions ---
------------------------
-- See COHORT class
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set number of units in groups.
-- @param #SQUADRON self
-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2.
-- @return #SQUADRON self
function SQUADRON:SetGrouping(nunits)
self.ngrouping=nunits or 2
if self.ngrouping<1 then self.ngrouping=1 end
if self.ngrouping>4 then self.ngrouping=4 end
return self
end
--- Set valid parking spot IDs. Assets of this squad are only allowed to be spawned at these parking spots. **Note** that the IDs are different from the ones displayed in the mission editor!
-- @param #SQUADRON self
-- @param #table ParkingIDs Table of parking ID numbers or a single `#number`.
-- @return #SQUADRON self
function SQUADRON:SetParkingIDs(ParkingIDs)
if type(ParkingIDs)~="table" then
ParkingIDs={ParkingIDs}
end
self.parkingIDs=ParkingIDs
return self
end
--- Set takeoff type. All assets of this squadron will be spawned with cold (default) or hot engines.
-- Spawning on runways is not supported.
-- @param #SQUADRON self
-- @param #string TakeoffType Take off type: "Cold" (default) or "Hot" with engines on or "Air" for spawning in air.
-- @return #SQUADRON self
function SQUADRON:SetTakeoffType(TakeoffType)
TakeoffType=TakeoffType or "Cold"
if TakeoffType:lower()=="hot" then
self.takeoffType=COORDINATE.WaypointType.TakeOffParkingHot
elseif TakeoffType:lower()=="cold" then
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
elseif TakeoffType:lower()=="air" then
self.takeoffType=COORDINATE.WaypointType.TurningPoint
else
self.takeoffType=COORDINATE.WaypointType.TakeOffParking
end
return self
end
--- Set takeoff type cold (default). All assets of this squadron will be spawned with engines off (cold).
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffCold()
self:SetTakeoffType("Cold")
return self
end
--- Set takeoff type hot. All assets of this squadron will be spawned with engines on (hot).
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffHot()
self:SetTakeoffType("Hot")
return self
end
--- Set takeoff type air. All assets of this squadron will be spawned in air above the airbase.
-- @param #SQUADRON self
-- @return #SQUADRON self
function SQUADRON:SetTakeoffAir()
self:SetTakeoffType("Air")
return self
end
--- Set despawn after landing. Aircraft will be despawned after the landing event.
-- Can help to avoid DCS AI taxiing issues.
-- @param #SQUADRON self
-- @param #boolean Switch If `true` (default), activate despawn after landing.
-- @return #SQUADRON self
function SQUADRON:SetDespawnAfterLanding(Switch)
if Switch then
self.despawnAfterLanding=Switch
else
self.despawnAfterLanding=true
end
return self
end
--- Set despawn after holding. Aircraft will be despawned when they arrive at their holding position at the airbase.
-- Can help to avoid DCS AI taxiing issues.
-- @param #SQUADRON self
-- @param #boolean Switch If `true` (default), activate despawn after holding.
-- @return #SQUADRON self
function SQUADRON:SetDespawnAfterHolding(Switch)
if Switch then
self.despawnAfterHolding=Switch
else
self.despawnAfterHolding=true
end
return self
end
--- Set low fuel threshold.
-- @param #SQUADRON self
-- @param #number LowFuel Low fuel threshold in percent. Default 25.
-- @return #SQUADRON self
function SQUADRON:SetFuelLowThreshold(LowFuel)
self.fuellow=LowFuel or 25
return self
end
--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker.
-- @param #SQUADRON self
-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off.
-- @return #SQUADRON self
function SQUADRON:SetFuelLowRefuel(switch)
if switch==false then
self.fuellowRefuel=false
else
self.fuellowRefuel=true
end
return self
end
--- Set airwing.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING Airwing The airwing.
-- @return #SQUADRON self
function SQUADRON:SetAirwing(Airwing)
self.legion=Airwing
return self
end
--- Get airwing.
-- @param #SQUADRON self
-- @return Ops.AirWing#AIRWING The airwing.
function SQUADRON:GetAirwing(Airwing)
return self.legion
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting SQUADRON", self.name)
self:T(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStatus(From, Event, To)
if self.verbose>=1 then
-- FSM state.
local fsmstate=self:GetState()
local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A"
local modex=self.modex and self.modex or -1
local skill=self.skill and tostring(self.skill) or "N/A"
local NassetsTot=#self.assets
local NassetsInS=self:CountAssets(true)
local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0
if self.legion then
NassetsQP, NassetsP, NassetsQ=self.legion:CountAssetsOnMission(nil, self)
end
-- Short info.
local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]",
fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ)
self:I(self.lid..text)
-- Check if group has detected any units.
self:_CheckAssetStatus()
end
if not self:IsStopped() then
self:__Status(-60)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,755 @@
--- **Ops** - Commander Air Wing.
--
-- **Main Features:**
--
-- * Stuff
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.WingCommander
-- @image OPS_WingCommander.png
--- WINGCOMMANDER class.
-- @type WINGCOMMANDER
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #table airwings Table of airwings.
-- @field #table missionqueue Mission queue.
-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory.
-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected.
-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged.
-- @field #string Defcon Defence condition.
-- @extends Ops.Intelligence#INTEL
--- Be surprised!
--
-- ===
--
-- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg)
--
-- # The WINGCOMMANDER Concept
--
--
--
-- @field #WINGCOMMANDER
WINGCOMMANDER = {
ClassName = "WINGCOMMANDER",
Debug = nil,
lid = nil,
airwings = {},
missionqueue = {},
borderzoneset = nil,
yellowzoneset = nil,
engagezoneset = nil,
}
--- Contact details.
-- @type WINGCOMMANDER.Contact
-- @field Ops.Auftrag#AUFTRAG mission The assigned mission.
-- @extends Ops.Intelligence#INTEL.DetectedItem
--- Defence condition.
-- @type WINGCOMMANDER.DEFCON
-- @field #string GREEN No enemy activities detected.
-- @field #string YELLOW Enemy near our border.
-- @field #string RED Enemy within our border.
WINGCOMMANDER.DEFCON = {
GREEN="Green",
YELLOW="Yellow",
RED="Red",
}
--- WINGCOMMANDER class version.
-- @field #string version
WINGCOMMANDER.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Define A2A and A2G parameters.
-- TODO: Improve airwing selection. Mostly done!
-- DONE: Add/remove spawned flightgroups to detection set.
-- DONE: Borderzones.
-- NOGO: Maybe it's possible to preselect the assets for the mission.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new WINGCOMMANDER object and start the FSM.
-- @param #WINGCOMMANDER self
-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set.
-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral".
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:New(AgentSet, Coalition)
AgentSet=AgentSet or SET_GROUP:New()
-- Inherit everything from INTEL class.
local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#WINGCOMMANDER
-- Set some string id for output to DCS.log file.
--self.lid=string.format("WINGCOMMANDER | ")
self:SetBorderZones()
self:SetYellowZones()
self:SetThreatLevelRange()
self.Defcon=WINGCOMMANDER.DEFCON.GREEN
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING.
self:AddTransition("*", "CancelMission", "*") -- Cancel mission.
self:AddTransition("*", "Defcon", "*") -- Cancel mission.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
-- @function [parent=#WINGCOMMANDER] Start
-- @param #WINGCOMMANDER self
--- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers.
-- @function [parent=#WINGCOMMANDER] __Start
-- @param #WINGCOMMANDER self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers.
-- @param #WINGCOMMANDER self
--- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers.
-- @function [parent=#WINGCOMMANDER] __Stop
-- @param #WINGCOMMANDER self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status".
-- @function [parent=#WINGCOMMANDER] Status
-- @param #WINGCOMMANDER self
--- Triggers the FSM event "SkipperStatus" after a delay.
-- @function [parent=#WINGCOMMANDER] __Status
-- @param #WINGCOMMANDER self
-- @param #number delay Delay in seconds.
-- Debug trace.
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self.Debug=true
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway.
-- @param #WINGCOMMANDER self
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetAirToAny()
self:SetFilterCategory({})
return self
end
--- Set this to be an air-to-air dispatcher.
-- @param #WINGCOMMANDER self
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetAirToAir()
self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER})
return self
end
--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units
-- @param #WINGCOMMANDER self
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetAirToGround()
self:SetFilterCategory({Unit.Category.GROUND_UNIT})
return self
end
--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units.
-- @param #WINGCOMMANDER self
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetAirToSea()
self:SetFilterCategory({Unit.Category.SHIP})
return self
end
--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups.
-- @param #WINGCOMMANDER self
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetAirToSurface()
self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP})
return self
end
--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat.
-- Targets with threat level 0 are usually harmless.
-- @param #WINGCOMMANDER self
-- @param #number ThreatLevelMin Min threat level. Default 1.
-- @param #number ThreatLevelMax Max threat level. Default 10.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax)
self.threatLevelMin=ThreatLevelMin or 1
self.threatLevelMax=ThreatLevelMax or 10
return self
end
--- Set defence condition.
-- @param #WINGCOMMANDER self
-- @param #string Defcon Defence condition. See @{#WINGCOMMANDER.DEFCON}, e.g. `WINGCOMMANDER.DEFCON.RED`.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetDefcon(Defcon)
self.Defcon=Defcon
--self:Defcon(Defcon)
return self
end
--- Add an airwing to the wingcommander.
-- @param #WINGCOMMANDER self
-- @param Ops.AirWing#AIRWING Airwing The airwing to add.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:AddAirwing(Airwing)
-- This airwing is managed by this wing commander.
Airwing.wingcommander=self
table.insert(self.airwings, Airwing)
return self
end
--- Add mission to mission queue.
-- @param #WINGCOMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:AddMission(Mission)
Mission.wingcommander=self
table.insert(self.missionqueue, Mission)
return self
end
--- Remove mission from queue.
-- @param #WINGCOMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:RemoveMission(Mission)
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission.auftragsnummer==Mission.auftragsnummer then
self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status))
table.remove(self.missionqueue, i)
break
end
end
return self
end
--- Set border zone set.
-- @param #WINGCOMMANDER self
-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetBorderZones(BorderZoneSet)
-- Border zones.
self.borderzoneset=BorderZoneSet or SET_ZONE:New()
return self
end
--- Add a zone defining your territory.
-- @param #WINGCOMMANDER self
-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:AddBorderZone(BorderZone)
-- Add a border zone.
self.borderzoneset:AddZone(BorderZone)
-- Set accept zone.
--self:AddAcceptZone(BorderZone)
return self
end
--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW.
-- @param #WINGCOMMANDER self
-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:SetYellowZones(YellowZoneSet)
-- Border zones.
self.yellowzoneset=YellowZoneSet or SET_ZONE:New()
return self
end
--- Add a zone defining an area outside your territory that is monitored for enemy activity.
-- @param #WINGCOMMANDER self
-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory.
-- @return #WINGCOMMANDER self
function WINGCOMMANDER:AddYellowZone(YellowZone)
-- Add a border zone.
self.yellowzoneset:AddZone(YellowZone)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #WINGCOMMANDER self
-- @param Wrapper.Group#GROUP Group Flight group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function WINGCOMMANDER:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting Wing Commander")
self:I(self.lid..text)
-- Start parent INTEL.
self:GetParent(self).onafterStart(self, From, Event, To)
-- Start attached airwings.
for _,_airwing in pairs(self.airwings) do
local airwing=_airwing --Ops.AirWing#AIRWING
if airwing:GetState()=="NotReadyYet" then
airwing:Start()
end
end
end
--- On after "Status" event.
-- @param #WINGCOMMANDER self
-- @param Wrapper.Group#GROUP Group Flight group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function WINGCOMMANDER:onafterStatus(From, Event, To)
-- Start parent INTEL.
self:GetParent(self).onafterStatus(self, From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
-- Clean up missions where the contact was lost.
for _,_contact in pairs(self.ContactsLost) do
local contact=_contact --#WINGCOMMANDER.Contact
if contact.mission and contact.mission:IsNotOver() then
local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name)
MESSAGE:New(text, 120, "WINGCOMMANDER"):ToAll()
self:I(self.lid..text)
-- Cancel this mission.
contact.mission:Cancel()
end
end
-- Create missions for all new contacts.
local Nred=0
local Nyellow=0
local Nengage=0
for _,_contact in pairs(self.Contacts) do
local contact=_contact --#WINGCOMMANDER.Contact
local group=contact.group --Wrapper.Group#GROUP
local inred=self:CheckGroupInBorder(group)
if inred then
Nred=Nred+1
end
local inyellow=self:CheckGroupInYellow(group)
if inyellow then
Nyellow=Nyellow+1
end
-- Is this a threat?
local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax
local redalert=true
if self.borderzoneset:Count()>0 then
redalert=inred
end
if redalert and threat and not contact.mission then
-- Create a mission based on group category.
local mission=AUFTRAG:NewAUTO(group)
-- Add mission to queue.
if mission then
--TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max.
mission.nassets=1
-- Missons are repeated max 3 times on failure.
mission.missionRepeatMax=3
-- Set mission contact.
contact.mission=mission
-- Add mission to queue.
self:AddMission(mission)
end
end
end
-- Set defcon.
-- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones.
if Nred>0 then
self:SetDefcon(WINGCOMMANDER.DEFCON.RED)
elseif Nyellow>0 then
self:SetDefcon(WINGCOMMANDER.DEFCON.YELLOW)
else
self:SetDefcon(WINGCOMMANDER.DEFCON.GREEN)
end
-- Check mission queue and assign one PLANNED mission.
self:CheckMissionQueue()
local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred)
self:I(self.lid..text)
-- Infor about contacts.
if #self.Contacts>0 then
local text="Contacts:"
for i,_contact in pairs(self.Contacts) do
local contact=_contact --#WINGCOMMANDER.Contact
local mtext="N/A"
if contact.mission then
mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper())
end
text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext)
end
self:I(self.lid..text)
end
-- Mission queue.
if #self.missionqueue>0 then
local text="Mission queue:"
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local target=mission:GetTargetName() or "unknown"
text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target)
end
self:I(self.lid..text)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "MissionAssign" event. Mission is added to the AIRWING mission queue.
-- @param #WINGCOMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.AirWing#AIRWING Airwing The AIRWING.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function WINGCOMMANDER:onafterMissionAssign(From, Event, To, Airwing, Mission)
self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias))
Airwing:AddMission(Mission)
end
--- On after "CancelMission" event.
-- @param #WINGCOMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission)
self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status))
if Mission.status==AUFTRAG.Status.PLANNED then
-- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue.
self:RemoveMission(Mission)
else
-- Airwing will cancel mission.
if Mission.airwing then
Mission.airwing:MissionCancel(Mission)
end
end
end
--- On before "Defcon" event.
-- @param #WINGCOMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Defcon New defence condition.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function WINGCOMMANDER:onbeforeDefcon(From, Event, To, Defcon)
local gotit=false
for _,defcon in pairs(WINGCOMMANDER.DEFCON) do
if defcon==Defcon then
gotit=true
end
end
if not gotit then
self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon)))
return false
end
-- Defcon did not change.
if Defcon==self.Defcon then
self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon)))
return false
end
return true
end
--- On after "Defcon" event.
-- @param #WINGCOMMANDER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Defcon New defence condition.
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
function WINGCOMMANDER:onafterDefcon(From, Event, To, Defcon)
self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon))
-- Set new defcon.
self.Defcon=Defcon
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Resources
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check mission queue and assign ONE planned mission.
-- @param #WINGCOMMANDER self
function WINGCOMMANDER:CheckMissionQueue()
-- TODO: Sort mission queue. wrt what? Threat level?
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
-- We look for PLANNED missions.
if mission.status==AUFTRAG.Status.PLANNED then
---
-- PLANNNED Mission
---
local airwing=self:GetAirwingForMission(mission)
if airwing then
-- Add mission to airwing.
self:MissionAssign(airwing, mission)
return
end
else
---
-- Missions NOT in PLANNED state
---
end
end
end
--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets.
-- @param #WINGCOMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return Ops.AirWing#AIRWING The airwing best for this mission.
function WINGCOMMANDER:GetAirwingForMission(Mission)
-- Table of airwings that can do the mission.
local airwings={}
-- Loop over all airwings.
for _,_airwing in pairs(self.airwings) do
local airwing=_airwing --Ops.AirWing#AIRWING
-- Check if airwing can do this mission.
local can,assets=airwing:CanMission(Mission)
-- Can it?
if can then
-- Get coordinate of the target.
local coord=Mission:GetTargetCoordinate()
if coord then
-- Distance from airwing to target.
local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate()))
-- Add airwing to table of airwings that can.
table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets})
end
end
end
-- Can anyone?
if #airwings>0 then
--- Something like:
-- * Closest airwing that can should be first prio.
-- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job.
local function score(a)
local d=math.round(a.dist/10)
end
-- Sort table wrt distance and number of assets.
-- Distances within 10 NM are equal and the airwing with more assets is preferred.
local function sortdist(a,b)
local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6
local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6
return ad<bd or (ad==bd and a.nassets>b.nassets)
end
table.sort(airwings, sortdist)
-- This is the closest airwing to the target.
local airwing=airwings[1].airwing --Ops.AirWing#AIRWING
return airwing
end
return nil
end
--- Check if group is inside our border.
-- @param #WINGCOMMANDER self
-- @param Wrapper.Group#GROUP group The group.
-- @return #boolean If true, group is in any zone.
function WINGCOMMANDER:CheckGroupInBorder(group)
local inside=self:CheckGroupInZones(group, self.borderzoneset)
return inside
end
--- Check if group is near our border (yellow zone).
-- @param #WINGCOMMANDER self
-- @param Wrapper.Group#GROUP group The group.
-- @return #boolean If true, group is in any zone.
function WINGCOMMANDER:CheckGroupInYellow(group)
-- Check inside yellow but not inside our border.
local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset)
return inside
end
--- Check if group is inside a zone.
-- @param #WINGCOMMANDER self
-- @param Wrapper.Group#GROUP group The group.
-- @param Core.Set#SET_ZONE zoneset Set of zones.
-- @return #boolean If true, group is in any zone.
function WINGCOMMANDER:CheckGroupInZones(group, zoneset)
for _,_zone in pairs(zoneset.Set or {}) do
local zone=_zone --Core.Zone#ZONE
if group:IsPartlyOrCompletelyInZone(zone) then
return true
end
end
return false
end
--- Check resources.
-- @param #WINGCOMMANDER self
-- @return #table
function WINGCOMMANDER:CheckResources()
local capabilities={}
for _,MissionType in pairs(AUFTRAG.Type) do
capabilities[MissionType]=0
for _,_airwing in pairs(self.airwings) do
local airwing=_airwing --Ops.AirWing#AIRWING
-- Get Number of assets that can do this type of missions.
local _,assets=airwing:CanMission(MissionType)
-- Add up airwing resources.
capabilities[MissionType]=capabilities[MissionType]+#assets
end
end
return capabilities
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+59 -59
View File
@@ -1,28 +1,28 @@
--- **Sound** - Radio transmissions.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * Provide radio functionality to broadcast radio transmissions.
--
--
-- What are radio communications in DCS?
--
--
-- * Radio transmissions consist of **sound files** that are broadcasted on a specific **frequency** (e.g. 115MHz) and **modulation** (e.g. AM),
-- * They can be **subtitled** for a specific **duration**, the **power** in Watts of the transmiter's antenna can be set, and the transmission can be **looped**.
--
--
-- How to supply DCS my own Sound Files?
--
--
-- * Your sound files need to be encoded in **.ogg** or .wav,
-- * Your sound files should be **as tiny as possible**. It is suggested you encode in .ogg with low bitrate and sampling settings,
-- * They need to be added in .\l10n\DEFAULT\ in you .miz file (wich can be decompressed like a .zip file),
-- * For simplicity sake, you can **let DCS' Mission Editor add the file** itself, by creating a new Trigger with the action "Sound to Country", and choosing your sound file and a country you don't use in your mission.
--
--
-- Due to weird DCS quirks, **radio communications behave differently** if sent by a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or by any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * If the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, DCS will set the power of the transmission automatically,
-- * If the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}, the transmisison can't be subtitled or looped.
--
--
-- Note that obviously, the **frequency** and the **modulation** of the transmission are important only if the players are piloting an **Advanced System Modelling** enabled aircraft,
-- like the A10C or the Mirage 2000C. They will **hear the transmission** if they are tuned on the **right frequency and modulation** (and if they are close enough - more on that below).
-- If an FC3 aircraft is used, it will **hear every communication, whatever the frequency and the modulation** is set to. The same is true for TACAN beacons. If your aircraft isn't compatible,
@@ -37,41 +37,41 @@
--- *It's not true I had nothing on, I had the radio on.* -- Marilyn Monroe
--
--
-- # RADIO usage
--
--
-- There are 3 steps to a successful radio transmission.
--
--
-- * First, you need to **"add a @{#RADIO} object** to your @{Wrapper.Positionable#POSITIONABLE}. This is done using the @{Wrapper.Positionable#POSITIONABLE.GetRadio}() function,
-- * Then, you will **set the relevant parameters** to the transmission (see below),
-- * When done, you can actually **broadcast the transmission** (i.e. play the sound) with the @{RADIO.Broadcast}() function.
--
--
-- Methods to set relevant parameters for both a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP} or any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * @{#RADIO.SetFileName}() : Sets the file name of your sound file (e.g. "Noise.ogg"),
-- * @{#RADIO.SetFrequency}() : Sets the frequency of your transmission.
-- * @{#RADIO.SetModulation}() : Sets the modulation of your transmission.
-- * @{#RADIO.SetLoop}() : Choose if you want the transmission to be looped. If you need your transmission to be looped, you might need a @{#BEACON} instead...
--
--
-- Additional Methods to set relevant parameters if the transmitter is a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}
--
--
-- * @{#RADIO.SetSubtitle}() : Set both the subtitle and its duration,
-- * @{#RADIO.NewUnitTransmission}() : Shortcut to set all the relevant parameters in one method call
--
--
-- Additional Methods to set relevant parameters if the transmitter is any other @{Wrapper.Positionable#POSITIONABLE}
--
--
-- * @{#RADIO.SetPower}() : Sets the power of the antenna in Watts
-- * @{#RADIO.NewGenericTransmission}() : Shortcut to set all the relevant parameters in one method call
--
--
-- What is this power thing?
--
--
-- * If your transmission is sent by a @{Wrapper.Positionable#POSITIONABLE} other than a @{Wrapper.Unit#UNIT} or a @{Wrapper.Group#GROUP}, you can set the power of the antenna,
-- * Otherwise, DCS sets it automatically, depending on what's available on your Unit,
-- * If the player gets **too far** from the transmitter, or if the antenna is **too weak**, the transmission will **fade** and **become noisyer**,
-- * This an automated DCS calculation you have no say on,
-- * For reference, a standard VOR station has a 100 W antenna, a standard AA TACAN has a 120 W antenna, and civilian ATC's antenna usually range between 300 and 500 W,
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- * Note that if the transmission has a subtitle, it will be readable, regardless of the quality of the transmission.
--
-- @type RADIO
-- @field Wrapper.Controllable#CONTROLLABLE Positionable The @{#CONTROLLABLE} that will transmit the radio calls.
-- @field #string FileName Name of the sound file played.
@@ -105,12 +105,12 @@ function RADIO:New(Positionable)
-- Inherit base
local self = BASE:Inherit( self, BASE:New() ) -- Core.Radio#RADIO
self:F(Positionable)
if Positionable:GetPointVec2() then -- It's stupid, but the only way I found to make sure positionable is valid
self.Positionable = Positionable
return self
end
self:E({error="The passed positionable is invalid, no RADIO created!", positionable=Positionable})
return nil
end
@@ -137,19 +137,19 @@ end
-- @return #RADIO self
function RADIO:SetFileName(FileName)
self:F2(FileName)
if type(FileName) == "string" then
if FileName:find(".ogg") or FileName:find(".wav") then
if not FileName:find("l10n/DEFAULT/") then
FileName = "l10n/DEFAULT/" .. FileName
end
self.FileName = FileName
return self
end
end
self:E({"File name invalid. Maybe something wrong with the extension?", FileName})
return self
end
@@ -161,34 +161,34 @@ end
-- @return #RADIO self
function RADIO:SetFrequency(Frequency)
self:F2(Frequency)
if type(Frequency) == "number" then
-- If frequency is in range
-- if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
--if (Frequency >= 30 and Frequency <= 87.995) or (Frequency >= 108 and Frequency <= 173.995) or (Frequency >= 225 and Frequency <= 399.975) then
-- Convert frequency from MHz to Hz
self.Frequency = Frequency * 1000000
-- If the RADIO is attached to a UNIT or a GROUP, we need to send the DCS Command "SetFrequency" to change the UNIT or GROUP frequency
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
local commandSetFrequency={
id = "SetFrequency",
params = {
frequency = self.Frequency,
modulation = self.Modulation,
}
}
}
self:T2(commandSetFrequency)
self.Positionable:SetCommand(commandSetFrequency)
end
return self
-- end
--end
end
self:E({"Frequency is not a number. Frequency unchanged.", Frequency})
return self
end
@@ -215,13 +215,13 @@ end
-- @return #RADIO self
function RADIO:SetPower(Power)
self:F2(Power)
if type(Power) == "number" then
self.Power = math.floor(math.abs(Power)) --TODO Find what is the maximum power allowed by DCS and limit power to that
else
self:E({"Power is invalid. Power unchanged.", self.Power})
end
return self
end
@@ -249,7 +249,7 @@ end
-- -- create the broadcaster and attaches it a RADIO
-- local MyUnit = UNIT:FindByName("MyUnit")
-- local MyUnitRadio = MyUnit:GetRadio()
--
--
-- -- add a subtitle for the next transmission, which will be up for 10s
-- MyUnitRadio:SetSubtitle("My Subtitle, 10)
function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
@@ -264,14 +264,14 @@ function RADIO:SetSubtitle(Subtitle, SubtitleDuration)
self.SubtitleDuration = SubtitleDuration
else
self.SubtitleDuration = 0
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
self:E({"SubtitleDuration is invalid. SubtitleDuration reset.", self.SubtitleDuration})
end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is anything but a UNIT or a GROUP,
-- but it will work with a UNIT or a GROUP anyway.
-- but it will work with a UNIT or a GROUP anyway.
-- Only the #RADIO and the Filename are mandatory
-- @param #RADIO self
-- @param #string FileName Name of the sound file that will be transmitted.
@@ -281,20 +281,20 @@ end
-- @return #RADIO self
function RADIO:NewGenericTransmission(FileName, Frequency, Modulation, Power, Loop)
self:F({FileName, Frequency, Modulation, Power})
self:SetFileName(FileName)
if Frequency then self:SetFrequency(Frequency) end
if Modulation then self:SetModulation(Modulation) end
if Power then self:SetPower(Power) end
if Loop then self:SetLoop(Loop) end
return self
end
--- Create a new transmission, that is to say, populate the RADIO with relevant data
-- In this function the data is especially relevant if the broadcaster is a UNIT or a GROUP,
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- but it will work for any @{Wrapper.Positionable#POSITIONABLE}.
-- Only the RADIO and the Filename are mandatory.
-- @param #RADIO self
-- @param #string FileName Name of sound file.
@@ -316,20 +316,20 @@ function RADIO:NewUnitTransmission(FileName, Subtitle, SubtitleDuration, Frequen
end
-- Set frequency.
if Frequency then
if Frequency then
self:SetFrequency(Frequency)
end
-- Set subtitle.
if Subtitle then
self:SetSubtitle(Subtitle, SubtitleDuration or 0)
end
-- Set Looping.
if Loop then
if Loop then
self:SetLoop(Loop)
end
return self
end
@@ -346,7 +346,7 @@ end
-- @return #RADIO self
function RADIO:Broadcast(viatrigger)
self:F({viatrigger=viatrigger})
-- If the POSITIONABLE is actually a UNIT or a GROUP, use the more complicated DCS command system.
if (self.Positionable.ClassName=="UNIT" or self.Positionable.ClassName=="GROUP") and (not viatrigger) then
self:T("Broadcasting from a UNIT or a GROUP")
@@ -359,7 +359,7 @@ function RADIO:Broadcast(viatrigger)
subtitle = self.Subtitle,
loop = self.Loop,
}}
self:T3(commandTransmitMessage)
self.Positionable:SetCommand(commandTransmitMessage)
else
@@ -368,7 +368,7 @@ function RADIO:Broadcast(viatrigger)
self:T("Broadcasting from a POSITIONABLE")
trigger.action.radioTransmission(self.FileName, self.Positionable:GetPositionVec3(), self.Modulation, self.Loop, self.Frequency, self.Power, tostring(self.ID))
end
return self
end
@@ -380,11 +380,11 @@ end
-- @return #RADIO self
function RADIO:StopBroadcast()
self:F()
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
-- If the POSITIONABLE is a UNIT or a GROUP, stop the transmission with the DCS "StopTransmission" command
if self.Positionable.ClassName == "UNIT" or self.Positionable.ClassName == "GROUP" then
local commandStopTransmission={id="StopTransmission", params={}}
self.Positionable:SetCommand(commandStopTransmission)
else
-- Else, we use the appropriate singleton funciton
+66 -65
View File
@@ -1,9 +1,9 @@
--- **Core** - Makes the radio talk.
--
--
-- ===
--
--
-- ## Features:
--
--
-- * Send text strings using a vocabulary that is converted in spoken language.
-- * Possiblity to implement multiple language.
--
@@ -15,10 +15,10 @@
-- @image Core_Radio.JPG
--- Makes the radio speak.
--
--
-- # RADIOSPEECH usage
--
--
--
--
-- @type RADIOSPEECH
-- @extends Core.RadioQueue#RADIOQUEUE
RADIOSPEECH = {
@@ -59,24 +59,24 @@ RADIOSPEECH.Vocabulary.EN = {
["70"] = { "70", 0.48 },
["80"] = { "80", 0.26 },
["90"] = { "90", 0.36 },
["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 },
["100"] = { "100", 0.55 },
["200"] = { "200", 0.55 },
["300"] = { "300", 0.61 },
["400"] = { "400", 0.60 },
["500"] = { "500", 0.61 },
["600"] = { "600", 0.65 },
["700"] = { "700", 0.70 },
["800"] = { "800", 0.54 },
["900"] = { "900", 0.60 },
["1000"] = { "1000", 0.60 },
["2000"] = { "2000", 0.61 },
["3000"] = { "3000", 0.64 },
["4000"] = { "4000", 0.62 },
["5000"] = { "5000", 0.69 },
["6000"] = { "6000", 0.69 },
["7000"] = { "7000", 0.75 },
["8000"] = { "8000", 0.59 },
["9000"] = { "9000", 0.65 },
["chevy"] = { "chevy", 0.35 },
["colt"] = { "colt", 0.35 },
@@ -94,10 +94,10 @@ RADIOSPEECH.Vocabulary.EN = {
["meters"] = { "meters", 0.41 },
["mi"] = { "miles", 0.45 },
["feet"] = { "feet", 0.29 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["returning to base"] = { "returning_to_base", 0.85 },
["on route to ground target"] = { "on_route_to_ground_target", 1.05 },
@@ -143,24 +143,24 @@ RADIOSPEECH.Vocabulary.RU = {
["70"] = { "70", 0.68 },
["80"] = { "80", 0.84 },
["90"] = { "90", 0.71 },
["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["100"] = { "100", 0.35 },
["200"] = { "200", 0.59 },
["300"] = { "300", 0.53 },
["400"] = { "400", 0.70 },
["500"] = { "500", 0.50 },
["600"] = { "600", 0.58 },
["700"] = { "700", 0.64 },
["800"] = { "800", 0.77 },
["900"] = { "900", 0.75 },
["1000"] = { "1000", 0.87 },
["2000"] = { "2000", 0.83 },
["3000"] = { "3000", 0.84 },
["4000"] = { "4000", 1.00 },
["5000"] = { "5000", 0.77 },
["6000"] = { "6000", 0.90 },
["7000"] = { "7000", 0.77 },
["8000"] = { "8000", 0.92 },
["9000"] = { "9000", 0.87 },
["градусы"] = { "degrees", 0.5 },
["километры"] = { "kilometers", 0.65 },
@@ -170,10 +170,11 @@ RADIOSPEECH.Vocabulary.RU = {
["метров"] = { "meters", 0.41 },
["m"] = { "meters", 0.41 },
["ноги"] = { "feet", 0.37 },
["br"] = { "br", 1.1 },
["bra"] = { "bra", 0.3 },
["возвращение на базу"] = { "returning_to_base", 1.40 },
["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 },
["перехват боги"] = { "intercepting_bogeys", 1.22 },
@@ -199,11 +200,11 @@ function RADIOSPEECH:New(frequency, modulation)
-- Inherit base
local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH
self.Language = "EN"
self:BuildTree()
return self
end
@@ -261,7 +262,7 @@ end
function RADIOSPEECH:BuildTree()
self.Speech = {}
for Language, Sentences in pairs( self.Vocabulary ) do
self:I( { Language = Language, Sentences = Sentences })
self.Speech[Language] = {}
@@ -270,7 +271,7 @@ function RADIOSPEECH:BuildTree()
self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data )
end
end
self:I( { Speech = self.Speech } )
return self
@@ -289,7 +290,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" )
self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } )
if Word then
if Word ~= "" and tonumber(Word) == nil then
@@ -301,7 +302,7 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
if Speech[Word].Next == nil then
self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } )
self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" )
else
else
if RemainderSentence and RemainderSentence ~= "" then
return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language )
end
@@ -309,11 +310,11 @@ function RADIOSPEECH:SpeakWords( Sentence, Speech, Language )
end
return RemainderSentence
end
return OriginalSentence
return OriginalSentence
else
return ""
end
end
end
--- Speak a sentence.
@@ -332,7 +333,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
if Digits then
if Digits ~= "" and tonumber( Digits ) ~= nil then
-- Construct numbers
local Number = tonumber( Digits )
local Multiple = nil
@@ -356,7 +357,7 @@ function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge )
end
return RemainderSentence
end
return OriginalSentence
return OriginalSentence
else
return ""
end
@@ -373,26 +374,26 @@ function RADIOSPEECH:Speak( Sentence, Language )
self:I( { Sentence, Language } )
local Language = Language or "EN"
self:I( { Language = Language } )
-- If there is no node for Speech, then we start at the first nodes of the language.
local Speech = self.Speech[Language]
self:I( { Speech = Speech, Language = Language } )
self:NewTransmission( "_In.wav", 0.52, Language .. "/" )
repeat
Sentence = self:SpeakWords( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
Sentence = self:SpeakDigits( Sentence, Speech, Language )
self:I( { Sentence = Sentence } )
-- Sentence = self:SpeakSymbols( Sentence, Speech )
--
-- self:I( { Sentence = Sentence } )
+73 -8
View File
@@ -45,6 +45,7 @@
-- @field Core.Point#COORDINATE coordinate Coordinate from where the transmission is send.
-- @field #string path Path to the SRS exe. This includes the final slash "/".
-- @field #string google Full path google credentials JSON file, e.g. "C:\Users\username\Downloads\service-account-file.json".
-- @field #string Label Label showing up on the SRS radio overlay. Default is "ROBOT". No spaces allowed.
-- @extends Core.Base#BASE
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -88,15 +89,32 @@
--
-- Use a specific "culture" with the @{#MSRS.SetCulture} function, e.g. `:SetCulture("en-US")` or `:SetCulture("de-DE")`.
--
-- ## Set Google
--
-- Use Google's text-to-speech engine with the @{#MSRS.SetGoogle} function, e.g. ':SetGoogle()'.
-- By enabling this it also allows you to utilize SSML in your text for added flexibilty.
-- For more information on setting up a cloud account, visit: https://cloud.google.com/text-to-speech
-- Google's supported SSML reference: https://cloud.google.com/text-to-speech/docs/ssml
--
-- ## Set Voice
--
-- Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
-- Note that this must be installed on your windows system.
-- If enabling SetGoogle(), you can use voices provided by Google
-- Google's supported voices: https://cloud.google.com/text-to-speech/docs/voices
--
-- ## Set Coordinate
--
-- Use @{#MSRS.SetCoordinate} to define the origin from where the transmission is broadcasted.
--
-- ## Set SRS Port
--
-- Use @{#MSRS.SetPort} to define the SRS port. Defaults to 5002.
--
-- ## Set SRS Volume
--
-- Use @{#MSRS.SetVolume} to define the SRS volume. Defaults to 1.0. Allowed values are between 0.0 and 1.0, from silent to loudest.
--
-- @field #MSRS
MSRS = {
ClassName = "MSRS",
@@ -112,11 +130,12 @@ MSRS = {
volume = 1,
speed = 1,
coordinate = nil,
Label = "ROBOT",
}
--- MSRS class version.
-- @field #string version
MSRS.version="0.0.3"
MSRS.version="0.0.5"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -135,8 +154,9 @@ MSRS.version="0.0.3"
-- @param #string PathToSRS Path to the directory, where SRS is located.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. Can also be given as a #table of multiple frequencies.
-- @param #number Modulation Radio modulation: 0=AM (default), 1=FM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. Can also be given as a #table of multiple modulations.
-- @param #number Volume Volume - 1.0 is max, 0.0 is silence
-- @return #MSRS self
function MSRS:New(PathToSRS, Frequency, Modulation)
function MSRS:New(PathToSRS, Frequency, Modulation, Volume)
-- Defaults.
Frequency =Frequency or 143
@@ -151,6 +171,13 @@ function MSRS:New(PathToSRS, Frequency, Modulation)
self:SetModulations(Modulation)
self:SetGender()
self:SetCoalition()
self:SetLabel()
self:SetVolume()
self.lid = string.format("%s-%s | ",self.name,self.version)
if not io or not os then
self:E(self.lid.."***** ERROR - io or os NOT desanitized! MSRS will not work!")
end
return self
end
@@ -193,12 +220,47 @@ function MSRS:GetPath()
return self.path
end
--- Set SRS volume.
-- @param #MSRS self
-- @param #number Volume Volume - 1.0 is max, 0.0 is silence
-- @return #MSRS self
function MSRS:SetVolume(Volume)
local volume = Volume or 1
if volume > 1 then volume = 1 elseif volume < 0 then volume = 0 end
self.volume = volume
return self
end
--- Get SRS volume.
-- @param #MSRS self
-- @return #number Volume Volume - 1.0 is max, 0.0 is silence
function MSRS:GetVolume()
return self.volume
end
--- Set label.
-- @param #MSRS self
-- @param #number Label. Default "ROBOT"
-- @return #MSRS self
function MSRS:SetLabel(Label)
self.Label=Label or "ROBOT"
return self
end
--- Get label.
-- @param #MSRS self
-- @return #number Label.
function MSRS:GetLabel()
return self.Label
end
--- Set port.
-- @param #MSRS self
-- @param #number Port Port. Default 5002.
-- @return #MSRS self
function MSRS:SetPort(Port)
self.port=Port or 5002
return self
end
--- Get port.
@@ -214,6 +276,7 @@ end
-- @return #MSRS self
function MSRS:SetCoalition(Coalition)
self.coalition=Coalition or 0
return self
end
--- Get coalition.
@@ -382,7 +445,7 @@ function MSRS:PlaySoundFile(Soundfile, Delay)
local command=self:_GetCommand()
-- Append file.
command=command.." --file="..tostring(soundfile)
command=command..' --file="'..tostring(soundfile)..'"'
self:_ExecCommand(command)
@@ -625,8 +688,9 @@ end
-- @param #number volume Volume.
-- @param #number speed Speed.
-- @param #number port Port.
-- @param #string label Label, defaults to "ROBOT" (displayed sender name in the radio overlay of SRS) - No spaces allowed!
-- @return #string Command.
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port)
function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, speed, port,label)
local path=self:GetPath() or STTS.DIRECTORY
local exe=STTS.EXECUTABLE or "DCS-SR-ExternalAudio.exe"
@@ -639,6 +703,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
volume=volume or self.volume
speed=speed or self.speed
port=port or self.port
label=label or self.Label
-- Replace modulation
modus=modus:gsub("0", "AM")
@@ -648,12 +713,12 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
--local command=string.format("%s --freqs=%s --modulations=%s --coalition=%d --port=%d --volume=%.2f --speed=%d", exe, freqs, modus, coal, port, volume, speed)
-- Command from orig STTS script. Works better for some unknown reason!
local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
--local command=string.format("start /min \"\" /d \"%s\" /b \"%s\" -f %s -m %s -c %s -p %s -n \"%s\" -h", path, exe, freqs, modus, coal, port, "ROBOT")
--local command=string.format('start /b "" /d "%s" "%s" -f %s -m %s -c %s -p %s -n "%s" > bla.txt', path, exe, freqs, modus, coal, port, "ROBOT")
-- Command.
local command=string.format('%s/%s -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, "ROBOT")
local command=string.format('"%s\\%s" -f %s -m %s -c %s -p %s -n "%s"', path, exe, freqs, modus, coal, port, label)
-- Set voice or gender/culture.
if voice then
@@ -662,7 +727,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
else
-- Add gender.
if gender and gender~="female" then
command=command..string.format(" --gender=%s", tostring(gender))
command=command..string.format(" -g %s", tostring(gender))
end
-- Add culture.
if culture and culture~="en-GB" then
@@ -678,7 +743,7 @@ function MSRS:_GetCommand(freqs, modus, coal, gender, voice, culture, volume, sp
-- Set google.
if self.google then
command=command..string.format(' -G "%s"', self.google)
command=command..string.format(' --ssml -G "%s"', self.google)
end
-- Debug output.
@@ -183,13 +183,17 @@ do -- Sound File
--- Set path, where the sound file is located.
-- @param #SOUNDFILE self
-- @param #string Path Path to the directory, where the sound file is located.
-- @param #string Path Path to the directory, where the sound file is located. In case this is nil, it defaults to the DCS mission temp directory.
-- @return #SOUNDFILE self
function SOUNDFILE:SetPath(Path)
-- Init path.
self.path=Path or "l10n/DEFAULT/"
if not Path and self.useSRS then -- use path to mission temp dir
self.path = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\\DEFAULT"
end
-- Remove (back)slashes.
local nmax=1000 ; local n=1
while (self.path:sub(-1)=="/" or self.path:sub(-1)==[[\]]) and n<=nmax do
+29 -7
View File
@@ -40,7 +40,7 @@ do -- UserSound
-- @param #USERSOUND self
-- @param #string UserSoundFileName The filename of the usersound.
-- @return #USERSOUND
function USERSOUND:New( UserSoundFileName ) --R2.3
function USERSOUND:New( UserSoundFileName )
local self = BASE:Inherit( self, BASE:New() ) -- #USERSOUND
@@ -58,7 +58,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:SetFileName( "BlueVictoryLoud.ogg" ) -- Set the BlueVictory to change the file name to play a louder sound.
--
function USERSOUND:SetFileName( UserSoundFileName ) --R2.3
function USERSOUND:SetFileName( UserSoundFileName )
self.UserSoundFileName = UserSoundFileName
@@ -75,7 +75,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToAll() -- Play the sound that Blue has won.
--
function USERSOUND:ToAll() --R2.3
function USERSOUND:ToAll()
trigger.action.outSound( self.UserSoundFileName )
@@ -91,7 +91,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCoalition( coalition.side.BLUE ) -- Play the sound that Blue has won to the blue coalition.
--
function USERSOUND:ToCoalition( Coalition ) --R2.3
function USERSOUND:ToCoalition( Coalition )
trigger.action.outSoundForCoalition(Coalition, self.UserSoundFileName )
@@ -107,7 +107,7 @@ do -- UserSound
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- BlueVictory:ToCountry( country.id.USA ) -- Play the sound that Blue has won to the USA country.
--
function USERSOUND:ToCountry( Country ) --R2.3
function USERSOUND:ToCountry( Country )
trigger.action.outSoundForCountry( Country, self.UserSoundFileName )
@@ -123,9 +123,9 @@ do -- UserSound
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerGroup = GROUP:FindByName( "PlayerGroup" ) -- Search for the active group named "PlayerGroup", that contains a human player.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the sound that Blue has won to the player group.
-- BlueVictory:ToGroup( PlayerGroup ) -- Play the victory sound to the player group.
--
function USERSOUND:ToGroup( Group, Delay ) --R2.3
function USERSOUND:ToGroup( Group, Delay )
Delay=Delay or 0
if Delay>0 then
@@ -136,5 +136,27 @@ do -- UserSound
return self
end
--- Play the usersound to the given @{Wrapper.Unit}.
-- @param #USERSOUND self
-- @param Wrapper.Unit#UNIT Unit The @{Wrapper.Unit} to play the usersound to.
-- @param #number Delay (Optional) Delay in seconds, before the sound is played. Default 0.
-- @return #USERSOUND The usersound instance.
-- @usage
-- local BlueVictory = USERSOUND:New( "BlueVictory.ogg" )
-- local PlayerUnit = UNIT:FindByName( "PlayerUnit" ) -- Search for the active unit named "PlayerUnit", a human player.
-- BlueVictory:ToUnit( PlayerUnit ) -- Play the victory sound to the player unit.
--
function USERSOUND:ToUnit( Unit, Delay )
Delay=Delay or 0
if Delay>0 then
SCHEDULER:New(nil, USERSOUND.ToUnit,{self, Unit}, Delay)
else
trigger.action.outSoundForUnit( Unit:GetID(), self.UserSoundFileName )
end
return self
end
end
@@ -216,7 +216,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
local MenuMissionsDetails = MENU_GROUP_COMMAND:New( EventGroup, "Missions Players Report", MenuReporting, self.ReportMissionsPlayers, self, EventGroup )
self:ReportSummary( EventGroup )
--self:ReportSummary( EventGroup )
local PlayerUnit = EventData.IniUnit
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
@@ -560,9 +560,11 @@ function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false
if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
self.autoAssignTasksScheduleID=self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop( self.AssignTasks )
self:ScheduleStop()
-- FF this is not the schedule ID
--self:ScheduleStop( self.AssignTasks )
end
end
@@ -683,6 +683,7 @@ do -- TASK_CARGO_DISPATCHER
-- If no TaskPrefix is given, then "Transport" will be used as the prefix.
-- @param Core.SetCargo#SET_CARGO SetCargo The SetCargo to be transported.
-- @param #string Briefing The briefing of the task transport to be shown to the player.
-- @param #boolean Silent If true don't send a message that a new task is available.
-- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT
-- @usage
--
@@ -705,10 +706,12 @@ do -- TASK_CARGO_DISPATCHER
-- -- Here we set a TransportDeployZone. We use the WorkplaceTask as the reference, and provide a ZONE object.
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
--
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing )
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskPrefix, SetCargo, Briefing, Silent )
self.TransportCount = self.TransportCount + 1
local verbose = Silent or false
local TaskName = string.format( ( TaskPrefix or "Transport" ) .. ".%03d", self.TransportCount )
self.Transport[TaskName] = {}
@@ -717,7 +720,7 @@ do -- TASK_CARGO_DISPATCHER
self.Transport[TaskName].Task = nil
self.Transport[TaskName].TaskPrefix = TaskPrefix
self:ManageTasks()
self:ManageTasks(verbose)
return self.Transport[TaskName] and self.Transport[TaskName].Task
end
@@ -785,10 +788,11 @@ do -- TASK_CARGO_DISPATCHER
--- Assigns tasks to the @{Core.Set#SET_GROUP}.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #boolean Silent Announce new task (nil/false) or not (true).
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_CARGO_DISPATCHER:ManageTasks()
function TASK_CARGO_DISPATCHER:ManageTasks(Silent)
self:F()
local verbose = Silent and true
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
@@ -897,7 +901,7 @@ do -- TASK_CARGO_DISPATCHER
local TaskText = TaskReport:Text(", ")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" and not verbose then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end
end
@@ -2,7 +2,7 @@
--
-- **Specific features:**
--
-- * Creates a task to transport @{Cargo.Cargo} to and between deployment zones.
-- * Creates a task to transport #Cargo.Cargo to and between deployment zones.
-- * Derived from the TASK_CARGO class, which is derived from the TASK class.
-- * Orchestrate the task flow, so go from Planned to Assigned to Success, Failed or Cancelled.
-- * Co-operation tasking, so a player joins a group of players executing the same task.
@@ -44,7 +44,7 @@
--
-- ===
--
-- Please read through the @{Tasking.Task_Cargo} process to understand the mechanisms of tasking and cargo tasking and handling.
-- Please read through the #Tasking.Task_Cargo process to understand the mechanisms of tasking and cargo tasking and handling.
--
-- Enjoy!
-- FC
@@ -57,7 +57,7 @@
do -- TASK_CARGO_TRANSPORT
--- @type TASK_CARGO_TRANSPORT
-- @type TASK_CARGO_TRANSPORT
-- @extends Tasking.Task_CARGO#TASK_CARGO
--- Orchestrates the task for players to transport cargo to or between deployment zones.
@@ -76,7 +76,7 @@ do -- TASK_CARGO_TRANSPORT
--
-- ## 1.1) Create a command center.
--
-- First you need to create a command center using the @{Tasking.CommandCenter#COMMANDCENTER.New}() constructor.
-- First you need to create a command center using the Tasking.CommandCenter#COMMANDCENTER.New constructor.
--
-- local CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" ) -- Create the CommandCenter.
@@ -85,7 +85,7 @@ do -- TASK_CARGO_TRANSPORT
--
-- Tasks work in a mission, which groups these tasks to achieve a joint mission goal.
-- A command center can govern multiple missions.
-- Create a new mission, using the @{Tasking.Mission#MISSION.New}() constructor.
-- Create a new mission, using the Tasking.Mission#MISSION.New constructor.
--
-- -- Declare the Mission for the Command Center.
-- local Mission = MISSION
@@ -99,7 +99,7 @@ do -- TASK_CARGO_TRANSPORT
-- ## 1.3) Create the transport cargo task.
--
-- So, now that we have a command center and a mission, we now create the transport task.
-- We create the transport task using the @{#TASK_CARGO_TRANSPORT.New}() constructor.
-- We create the transport task using the #TASK_CARGO_TRANSPORT.New constructor.
--
-- Because a transport task will not generate the cargo itself, you'll need to create it first.
-- The cargo in this case will be the downed pilot!
@@ -118,7 +118,7 @@ do -- TASK_CARGO_TRANSPORT
-- -- The cargoset "CargoSet" will embed all defined cargo of type "Pilots" (prefix) into its set.
-- local CargoGroup = CARGO_GROUP:New( PilotGroup, "Cargo", "Engineer Team 1", 500 )
--
-- What is also needed, is to have a set of @{Core.Group}s defined that contains the clients of the players.
-- What is also needed, is to have a set of Core.Groups defined that contains the clients of the players.
--
-- -- Allocate the Transport, which are the helicopter to retrieve the pilot, that can be manned by players.
-- local GroupSet = SET_GROUP:New():FilterPrefixes( "Transport" ):FilterStart()
@@ -139,48 +139,48 @@ do -- TASK_CARGO_TRANSPORT
-- By doing this, cargo transport tasking will become a dynamic experience.
--
--
-- # 2) Create a task using the @{Tasking.Task_Cargo_Dispatcher} module.
-- # 2) Create a task using the Tasking.Task_Cargo_Dispatcher module.
--
-- Actually, it is better to **GENERATE** these tasks using the @{Tasking.Task_Cargo_Dispatcher} module.
-- Using the dispatcher module, transport tasks can be created much more easy.
-- Actually, it is better to **GENERATE** these tasks using the Tasking.Task_Cargo_Dispatcher module.
-- Using the dispatcher module, transport tasks can be created easier.
--
-- Find below an example how to use the TASK_CARGO_DISPATCHER class:
--
--
-- -- Find the HQ group.
-- HQ = GROUP:FindByName( "HQ", "Bravo" )
-- -- Find the HQ group.
-- HQ = GROUP:FindByName( "HQ", "Bravo" )
--
-- -- Create the command center with the name "Lima".
-- CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" )
-- -- Create the command center with the name "Lima".
-- CommandCenter = COMMANDCENTER
-- :New( HQ, "Lima" )
--
-- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition.
-- Mission = MISSION
-- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE )
-- -- Create the mission, for the command center, with the name "Operation Cargo Fun", a "Tactical" mission, with the mission briefing "Transport Cargo", for the BLUE coalition.
-- Mission = MISSION
-- :New( CommandCenter, "Operation Cargo Fun", "Tactical", "Transport Cargo", coalition.side.BLUE )
--
-- -- Create the SET of GROUPs containing clients (players) that will transport the cargo.
-- -- These are have a name that start with "Transport" and are of the "blue" coalition.
-- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart()
-- -- Create the SET of GROUPs containing clients (players) that will transport the cargo.
-- -- These are have a name that start with "Transport" and are of the "blue" coalition.
-- TransportGroups = SET_GROUP:New():FilterCoalitions( "blue" ):FilterPrefixes( "Transport" ):FilterStart()
--
--
-- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups.
-- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups )
-- -- Here we create the TASK_CARGO_DISPATCHER object! This is where we assign the dispatcher to generate tasks in the Mission for the TransportGroups.
-- TaskDispatcher = TASK_CARGO_DISPATCHER:New( Mission, TransportGroups )
--
--
-- -- Here we declare the SET of CARGOs called "Workmaterials".
-- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart()
-- -- Here we declare the SET of CARGOs called "Workmaterials".
-- local CargoSetWorkmaterials = SET_CARGO:New():FilterTypes( "Workmaterials" ):FilterStart()
--
-- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set.
-- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on.
-- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 )
-- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 )
-- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 )
-- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 )
-- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 )
-- -- Here we declare (add) CARGO_GROUP objects of various types, that are filtered and added in the CargoSetworkmaterials cargo set.
-- -- These cargo objects have the type "Workmaterials" which is exactly the type of cargo the CargoSetworkmaterials is filtering on.
-- local EngineerCargoGroup = CARGO_GROUP:New( GROUP:FindByName( "Engineers" ), "Workmaterials", "Engineers", 250 )
-- local ConcreteCargo = CARGO_SLINGLOAD:New( STATIC:FindByName( "Concrete" ), "Workmaterials", "Concrete", 150, 50 )
-- local CrateCargo = CARGO_CRATE:New( STATIC:FindByName( "Crate" ), "Workmaterials", "Crate", 150, 50 )
-- local EnginesCargo = CARGO_CRATE:New( STATIC:FindByName( "Engines" ), "Workmaterials", "Engines", 150, 50 )
-- local MetalCargo = CARGO_CRATE:New( STATIC:FindByName( "Metal" ), "Workmaterials", "Metal", 150, 50 )
--
-- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher.
-- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." )
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
-- -- And here we create a new WorkplaceTask, using the :AddTransportTask method of the TaskDispatcher.
-- local WorkplaceTask = TaskDispatcher:AddTransportTask( "Build a Workplace", CargoSetWorkmaterials, "Transport the workers, engineers and the equipment near the Workplace." )
-- TaskDispatcher:SetTransportDeployZone( WorkplaceTask, ZONE:New( "Workplace" ) )
--
-- # 3) Handle cargo task events.
--
@@ -189,7 +189,7 @@ do -- TASK_CARGO_TRANSPORT
-- In order to properly capture the events and avoid mistakes using the documentation, it is advised that you execute the following actions:
--
-- * **Copy / Paste** the code section into your script.
-- * **Change** the CLASS literal to the task object name you have in your script.
-- * **Change** the "myclass" literal to the task object name you have in your script.
-- * Within the function, you can now **write your own code**!
-- * **IntelliSense** will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored,
-- but you need to declare them as they are automatically provided by the event handling system of MOOSE.
@@ -210,14 +210,13 @@ do -- TASK_CARGO_TRANSPORT
-- If you want to code your own event handler, use this code fragment to tailor the event when a player carrier has picked up a cargo object in the CarrierGroup.
-- You can use this event handler to post messages to players, or provide status updates etc.
--
-- --- CargoPickedUp event handler OnAfter for CLASS.
-- -- @param #CLASS self
-- --- CargoPickedUp event handler OnAfter for "myclass".
-- -- @param #string From A string that contains the "*from state name*" when the event was triggered.
-- -- @param #string Event A string that contains the "*event name*" when the event was triggered.
-- -- @param #string To A string that contains the "*to state name*" when the event was triggered.
-- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has picked up the cargo.
-- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been picked up. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object!
-- function CLASS:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo )
-- function myclass:OnAfterCargoPickedUp( From, Event, To, TaskUnit, Cargo )
--
-- -- Write here your own code.
--
@@ -240,15 +239,14 @@ do -- TASK_CARGO_TRANSPORT
-- You can use this event handler to post messages to players, or provide status updates etc.
--
--
-- --- CargoDeployed event handler OnAfter for CLASS.
-- -- @param #CLASS self
-- --- CargoDeployed event handler OnAfter foR "myclass".
-- -- @param #string From A string that contains the "*from state name*" when the event was triggered.
-- -- @param #string Event A string that contains the "*event name*" when the event was triggered.
-- -- @param #string To A string that contains the "*to state name*" when the event was triggered.
-- -- @param Wrapper.Unit#UNIT TaskUnit The unit (client) of the player that has deployed the cargo.
-- -- @param Cargo.Cargo#CARGO Cargo The cargo object that has been deployed. Note that this can be a CARGO_GROUP, CARGO_CRATE or CARGO_SLINGLOAD object!
-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE.
-- function CLASS:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone )
-- function myclass:OnAfterCargoDeployed( From, Event, To, TaskUnit, Cargo, DeployZone )
--
-- -- Write here your own code.
--
+201 -2
View File
@@ -11,7 +11,7 @@
--
-- DCS itself provides a lot of enumerators for various things. See [Enumerators](https://wiki.hoggitworld.com/view/Category:Enumerators) on Hoggit.
--
-- Other Moose classe also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
-- Other Moose classes also have enumerators. For example, the AIRBASE class has enumerators for airbase names.
--
-- @module ENUMS
-- @image MOOSE.JPG
@@ -126,6 +126,8 @@ ENUMS.WeaponFlag={
AnyMissile = 268402688, -- AnyASM + AnyAAM
--- Guns
Cannons = 805306368, -- GUN_POD + BuiltInCannon
--- Torpedo
Torpedo = 4294967296,
---
-- Even More Genral
Auto = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
@@ -136,6 +138,93 @@ ENUMS.WeaponFlag={
AnyGuided = 268402702, -- Any Guided Weapon
}
--- Weapon types by category. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerator on hoggit wiki.
-- @type ENUMS.WeaponType
-- @field #table Bomb Bombs.
-- @field #table Rocket Rocket.
-- @field #table Gun Guns.
-- @field #table Missile Missiles.
-- @field #table AAM Air-to-Air missiles.
-- @field #table Torpedo Torpedos.
-- @field #table Any Combinations.
ENUMS.WeaponType={}
ENUMS.WeaponType.Bomb={
-- Bombs
LGB = 2,
TvGB = 4,
SNSGB = 8,
HEBomb = 16,
Penetrator = 32,
NapalmBomb = 64,
FAEBomb = 128,
ClusterBomb = 256,
Dispencer = 512,
CandleBomb = 1024,
ParachuteBomb = 2147483648,
-- Combinations
GuidedBomb = 14, -- (LGB + TvGB + SNSGB)
AnyUnguidedBomb = 2147485680, -- (HeBomb + Penetrator + NapalmBomb + FAEBomb + ClusterBomb + Dispencer + CandleBomb + ParachuteBomb)
AnyBomb = 2147485694, -- (GuidedBomb + AnyUnguidedBomb)
}
ENUMS.WeaponType.Rocket={
-- Rockets
LightRocket = 2048,
MarkerRocket = 4096,
CandleRocket = 8192,
HeavyRocket = 16384,
-- Combinations
AnyRocket = 30720, -- LightRocket + MarkerRocket + CandleRocket + HeavyRocket
}
ENUMS.WeaponType.Gun={
-- Guns
GunPod = 268435456,
BuiltInCannon = 536870912,
-- Combinations
Cannons = 805306368, -- GUN_POD + BuiltInCannon
}
ENUMS.WeaponType.Missile={
-- Missiles
AntiRadarMissile = 32768,
AntiShipMissile = 65536,
AntiTankMissile = 131072,
FireAndForgetASM = 262144,
LaserASM = 524288,
TeleASM = 1048576,
CruiseMissile = 2097152,
AntiRadarMissile2 = 1073741824,
-- Combinations
GuidedASM = 1572864, -- (LaserASM + TeleASM)
TacticalASM = 1835008, -- (GuidedASM + FireAndForgetASM)
AnyASM = 4161536, -- (AntiRadarMissile + AntiShipMissile + AntiTankMissile + FireAndForgetASM + GuidedASM + CruiseMissile)
AnyASM2 = 1077903360, -- 4161536+1073741824,
AnyAutonomousMissile = 36012032, -- IR_AAM + AntiRadarMissile + AntiShipMissile + FireAndForgetASM + CruiseMissile
AnyMissile = 268402688, -- AnyASM + AnyAAM
}
ENUMS.WeaponType.AAM={
-- Air-To-Air Missiles
SRAM = 4194304,
MRAAM = 8388608,
LRAAM = 16777216,
IR_AAM = 33554432,
SAR_AAM = 67108864,
AR_AAM = 134217728,
-- Combinations
AnyAAM = 264241152, -- IR_AAM + SAR_AAM + AR_AAM + SRAAM + MRAAM + LRAAM
}
ENUMS.WeaponType.Torpedo={
-- Torpedo
Torpedo = 4294967296,
}
ENUMS.WeaponType.Any={
-- General combinations
Weapon = 3221225470, -- Any Weapon (AnyBomb + AnyRocket + AnyMissile + Cannons)
AG = 2956984318, -- Any Air-To-Ground Weapon
AA = 264241152, -- Any Air-To-Air Weapon
Unguided = 2952822768, -- Any Unguided Weapon
Guided = 268402702, -- Any Guided Weapon
}
--- Mission tasks.
-- @type ENUMS.MissionTask
-- @field #string NOTHING No special task. Group can perform the minimal tasks: Orbit, Refuelling, Follow and Aerobatics.
@@ -359,4 +448,114 @@ ENUMS.Phonetic =
X = 'Xray',
Y = 'Yankee',
Z = 'Zulu',
}
}
--- Reporting Names (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_NATO_reporting_names_for_fighter_aircraft).
-- DCS known aircraft types
--
-- @type ENUMS.ReportingName
ENUMS.ReportingName =
{
NATO = {
-- Fighters
Dragon = "JF-17", -- China, correctly Fierce Dragon, Thunder for PAC
Fagot = "MiG-15",
Farmer = "MiG-19", -- Shenyang J-6 and Mikoyan-Gurevich MiG-19
Felon = "Su-57",
Fencer = "Su-24",
Fishbed = "MiG-21",
Fitter = "Su-17", -- Sukhoi Su-7 and Su-17/Su-20/Su-22
Flogger = "MiG-23", --and MiG-27
Flogger_D = "MiG-27", --and MiG-23
Flagon = "Su-15",
Foxbat = "MiG-25",
Fulcrum = "MiG-29",
Foxhound = "MiG-31",
Flanker = "Su-27", -- Sukhoi Su-27/Su-30/Su-33/Su-35/Su-37 and Shenyang J-11/J-15/J-16
Flanker_C = "Su-30",
Flanker_E = "Su-35",
Flanker_F = "Su-37",
Flanker_L = "J-11A",
Firebird = "J-10",
Sea_Flanker = "Su-33",
Fullback = "Su-34", -- also Su-32
Frogfoot = "Su-25",
Tomcat = "F-14", -- Iran
Mirage = "Mirage", -- various non-NATO
Codling = "Yak-40",
Maya = "L-39",
-- Fighters US/NATO
Warthog = "A-10",
--Mosquito = "A-20",
Skyhawk = "A-4E",
Viggen = "AJS37",
Harrier = "AV-8B",
Spirit = "B-2",
Aviojet = "C-101",
Nighthawk = "F-117A",
Eagle = "F-15C",
Mudhen = "F-15E",
Viper = "F-16",
Phantom = "F-4E",
Tiger = "F-5", -- was thinkg to name this MiG-25 ;)
Sabre = "F-86",
Hornet = "A-18", -- avoiding the slash
Hawk = "Hawk",
Albatros = "L-39",
Goshawk = "T-45",
Starfighter = "F-104",
Tornado = "Tornado",
-- Transport / Bomber / Others
Atlas = "A400",
Lancer = "B1-B",
Stratofortress = "B-52H",
Hercules = "C-130",
Super_Hercules = "Hercules",
Globemaster = "C-17",
Greyhound = "C-2A",
Galaxy = "C-5",
Hawkeye = "E-2D",
Sentry = "E-3A",
Stratotanker = "KC-135",
Extender = "KC-10",
Orion = "P-3C",
Viking = "S-3B",
Osprey = "V-22",
-- Bomber Rus
Badger = "H6-J",
Bear_J = "Tu-142", -- also Tu-95
Bear = "Tu-95", -- also Tu-142
Blinder = "Tu-22",
Blackjack = "Tu-160",
-- AIC / Transport / Other
Clank = "An-30",
Curl = "An-26",
Candid = "IL-76",
Midas = "IL-78",
Mainstay = "A-50",
Mainring = "KJ-2000", -- A-50 China
Yak = "Yak-52",
-- Helos
Helix = "Ka-27",
Shark = "Ka-50",
Hind = "Mi-24",
Halo = "Mi-26",
Hip = "Mi-8",
Havoc = "Mi-28",
Gazelle = "SA342",
-- Helos US
Huey = "UH-1H",
Cobra = "AH-1",
Apache = "AH-64",
Chinook = "CH-47",
Sea_Stallion = "CH-53",
Kiowa = "OH-58",
Seahawk = "SH-60",
Blackhawk = "UH-60",
Sea_King = "S-61",
-- Drones
UCAV = "WingLoong",
Reaper = "MQ-9",
Predator = "MQ-1A",
}
}
+773
View File
@@ -0,0 +1,773 @@
--- **UTILS** - ClassicFiFo Stack.
--
-- ===
--
-- ## Main Features:
--
-- * Build a simple multi-purpose FiFo (First-In, First-Out) stack for generic data.
-- * [Wikipedia](https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics)
--
-- ===
--
-- ### Author: **applevangelist**
-- @module Utils.FiFo
-- @image MOOSE.JPG
-- Date: April 2022
do
--- FIFO class.
-- @type FIFO
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string version Version of FiFo
-- @field #number counter
-- @field #number pointer
-- @field #table stackbypointer
-- @field #table stackbyid
-- @extends Core.Base#BASE
---
-- @type FIFO.IDEntry
-- @field #number pointer
-- @field #table data
-- @field #table uniqueID
---
-- @field #FIFO
FIFO = {
ClassName = "FIFO",
lid = "",
version = "0.0.5",
counter = 0,
pointer = 0,
stackbypointer = {},
stackbyid = {}
}
--- Instantiate a new FIFO Stack
-- @param #FIFO self
-- @return #FIFO self
function FIFO:New()
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
self.pointer = 0
self.counter = 0
self.stackbypointer = {}
self.stackbyid = {}
self.uniquecounter = 0
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", "FiFo", self.version)
self:T(self.lid .."Created.")
return self
end
--- Empty FIFO Stack
-- @param #FIFO self
-- @return #FIFO self
function FIFO:Clear()
self:T(self.lid.."Clear")
self.pointer = 0
self.counter = 0
self.stackbypointer = nil
self.stackbyid = nil
self.stackbypointer = {}
self.stackbyid = {}
self.uniquecounter = 0
return self
end
--- FIFO Push Object to Stack
-- @param #FIFO self
-- @param #table Object
-- @param #string UniqueID (optional) - will default to current pointer + 1. Note - if you intend to use `FIFO:GetIDStackSorted()` keep the UniqueID numerical!
-- @return #FIFO self
function FIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer = self.pointer + 1
self.counter = self.counter + 1
local uniID = UniqueID
if not UniqueID then
self.uniquecounter = self.uniquecounter + 1
uniID = self.uniquecounter
end
self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID }
self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID }
return self
end
--- FIFO Pull Object from Stack
-- @param #FIFO self
-- @return #table Object or nil if stack is empty
function FIFO:Pull()
self:T(self.lid.."Pull")
if self.counter == 0 then return nil end
--local object = self.stackbypointer[self.pointer].data
--self.stackbypointer[self.pointer] = nil
local object = self.stackbypointer[1].data
self.stackbypointer[1] = nil
self.counter = self.counter - 1
--self.pointer = self.pointer - 1
self:Flatten()
return object
end
--- FIFO Pull Object from Stack by Pointer
-- @param #FIFO self
-- @param #number Pointer
-- @return #table Object or nil if stack is empty
function FIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer " .. tostring(Pointer))
if self.counter == 0 then return nil end
local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry
self.stackbypointer[Pointer] = nil
if object then self.stackbyid[object.uniqueID] = nil end
self.counter = self.counter - 1
self:Flatten()
if object then
return object.data
else
return nil
end
end
--- FIFO Read, not Pull, Object from Stack by Pointer
-- @param #FIFO self
-- @param #number Pointer
-- @return #table Object or nil if stack is empty or pointer does not exist
function FIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer " .. tostring(Pointer))
if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end
local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry
if object then
return object.data
else
return nil
end
end
--- FIFO Read, not Pull, Object from Stack by UniqueID
-- @param #FIFO self
-- @param #number UniqueID
-- @return #table Object data or nil if stack is empty or ID does not exist
function FIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID " .. tostring(UniqueID))
if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end
local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry
if object then
return object.data
else
return nil
end
end
--- FIFO Pull Object from Stack by UniqueID
-- @param #FIFO self
-- @param #tableUniqueID
-- @return #table Object or nil if stack is empty
function FIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID " .. tostring(UniqueID))
if self.counter == 0 then return nil end
local object = self.stackbyid[UniqueID] -- #FIFO.IDEntry
--self.stackbyid[UniqueID] = nil
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
--- FIFO Housekeeping
-- @param #FIFO self
-- @return #FIFO self
function FIFO:Flatten()
self:T(self.lid.."Flatten")
-- rebuild stacks
local pointerstack = {}
local idstack = {}
local counter = 0
for _ID,_entry in pairs(self.stackbypointer) do
counter = counter + 1
pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack) do
idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID}
end
self.stackbypointer = nil
self.stackbypointer = pointerstack
self.stackbyid = nil
self.stackbyid = idstack
self.counter = counter
self.pointer = counter
return self
end
--- FIFO Check Stack is empty
-- @param #FIFO self
-- @return #boolean empty
function FIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter == 0 and true or false
end
--- FIFO Get stack size
-- @param #FIFO self
-- @return #number size
function FIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
--- FIFO Get stack size
-- @param #FIFO self
-- @return #number size
function FIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
--- FIFO Check Stack is NOT empty
-- @param #FIFO self
-- @return #boolean notempty
function FIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
--- FIFO Get the data stack by pointer
-- @param #FIFO self
-- @return #table Table of #FIFO.IDEntry entries
function FIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
--- FIFO Check if a certain UniqeID exists
-- @param #FIFO self
-- @return #boolean exists
function FIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
if self.stackbyid[UniqueID] ~= nil then
return true
else
return false
end
end
--- FIFO Get the data stack by UniqueID
-- @param #FIFO self
-- @return #table Table of #FIFO.IDEntry entries
function FIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
--- FIFO Get table of UniqueIDs sorted smallest to largest
-- @param #FIFO self
-- @return #table Table with index [1] to [n] of UniqueID entries
function FIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack = self:GetIDStack()
local idstack = {}
for _id,_entry in pairs(stack) do
idstack[#idstack+1] = _id
self:T({"pre",_id})
end
local function sortID(a, b)
return a < b
end
table.sort(idstack)
return idstack
end
--- FIFO Get table of data entries
-- @param #FIFO self
-- @return #table Raw table indexed [1] to [n] of object entries - might be empty!
function FIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable = {}
for _,_entry in pairs(self.stackbypointer) do
datatable[#datatable+1] = _entry.data
end
return datatable
end
--- FIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!)
-- @param #FIFO self
-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty!
function FIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable = {}
local idtablesorted = self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted) do
datatable[#datatable+1] = self:ReadByID(_entry)
end
return datatable
end
--- Iterate the FIFO and call an iterator function for the given FIFO data, providing the object for each element of the stack and optional parameters.
-- @param #FIFO self
-- @param #function IteratorFunction The function that will be called.
-- @param #table Arg (Optional) Further Arguments of the IteratorFunction.
-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called.
-- @param #table FunctionArguments (Optional) Function arguments.
-- @return #FIFO self
function FIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments )
self:T(self.lid.."ForEach")
local Set = self:GetPointerStack() or {}
Arg = Arg or {}
local function CoRoutine()
local Count = 0
for ObjectID, ObjectData in pairs( Set ) do
local Object = ObjectData.data
self:T( {Object} )
if Function then
if Function( unpack( FunctionArguments or {} ), Object ) == true then
IteratorFunction( Object, unpack( Arg ) )
end
else
IteratorFunction( Object, unpack( Arg ) )
end
Count = Count + 1
end
return true
end
local co = CoRoutine
local function Schedule()
local status, res = co()
self:T( { status, res } )
if status == false then
error( res )
end
if res == false then
return true -- resume next time the loop
end
return false
end
Schedule()
return self
end
--- FIFO Print stacks to dcs.log
-- @param #FIFO self
-- @return #FIFO self
function FIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("FIFO Flushing Stack by Pointer")
for _id,_data in pairs (self.stackbypointer) do
local data = _data -- #FIFO.IDEntry
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("FIFO Flushing Stack by ID")
for _id,_data in pairs (self.stackbyid) do
local data = _data -- #FIFO.IDEntry
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = " .. self.counter)
self:I("Pointer = ".. self.pointer)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- End FIFO
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- LIFO
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
do
--- **UTILS** - LiFo Stack.
--
-- **Main Features:**
--
-- * Build a simple multi-purpose LiFo (Last-In, First-Out) stack for generic data.
--
-- ===
--
-- ### Author: **applevangelist**
--- LIFO class.
-- @type LIFO
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string version Version of LiFo
-- @field #number counter
-- @field #number pointer
-- @field #table stackbypointer
-- @field #table stackbyid
-- @extends Core.Base#BASE
---
-- @type LIFO.IDEntry
-- @field #number pointer
-- @field #table data
-- @field #table uniqueID
---
-- @field #LIFO
LIFO = {
ClassName = "LIFO",
lid = "",
version = "0.0.5",
counter = 0,
pointer = 0,
stackbypointer = {},
stackbyid = {}
}
--- Instantiate a new LIFO Stack
-- @param #LIFO self
-- @return #LIFO self
function LIFO:New()
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New())
self.pointer = 0
self.counter = 0
self.uniquecounter = 0
self.stackbypointer = {}
self.stackbyid = {}
-- Set some string id for output to DCS.log file.
self.lid=string.format("%s (%s) | ", "LiFo", self.version)
self:T(self.lid .."Created.")
return self
end
--- Empty LIFO Stack
-- @param #LIFO self
-- @return #LIFO self
function LIFO:Clear()
self:T(self.lid.."Clear")
self.pointer = 0
self.counter = 0
self.stackbypointer = nil
self.stackbyid = nil
self.stackbypointer = {}
self.stackbyid = {}
self.uniquecounter = 0
return self
end
--- LIFO Push Object to Stack
-- @param #LIFO self
-- @param #table Object
-- @param #string UniqueID (optional) - will default to current pointer + 1
-- @return #LIFO self
function LIFO:Push(Object,UniqueID)
self:T(self.lid.."Push")
self:T({Object,UniqueID})
self.pointer = self.pointer + 1
self.counter = self.counter + 1
local uniID = UniqueID
if not UniqueID then
self.uniquecounter = self.uniquecounter + 1
uniID = self.uniquecounter
end
self.stackbyid[uniID] = { pointer = self.pointer, data = Object, uniqueID = uniID }
self.stackbypointer[self.pointer] = { pointer = self.pointer, data = Object, uniqueID = uniID }
return self
end
--- LIFO Pull Object from Stack
-- @param #LIFO self
-- @return #table Object or nil if stack is empty
function LIFO:Pull()
self:T(self.lid.."Pull")
if self.counter == 0 then return nil end
local object = self.stackbypointer[self.pointer].data
self.stackbypointer[self.pointer] = nil
--local object = self.stackbypointer[1].data
--self.stackbypointer[1] = nil
self.counter = self.counter - 1
self.pointer = self.pointer - 1
self:Flatten()
return object
end
--- LIFO Pull Object from Stack by Pointer
-- @param #LIFO self
-- @param #number Pointer
-- @return #table Object or nil if stack is empty
function LIFO:PullByPointer(Pointer)
self:T(self.lid.."PullByPointer " .. tostring(Pointer))
if self.counter == 0 then return nil end
local object = self.stackbypointer[Pointer] -- #FIFO.IDEntry
self.stackbypointer[Pointer] = nil
if object then self.stackbyid[object.uniqueID] = nil end
self.counter = self.counter - 1
self:Flatten()
if object then
return object.data
else
return nil
end
end
--- LIFO Read, not Pull, Object from Stack by Pointer
-- @param #LIFO self
-- @param #number Pointer
-- @return #table Object or nil if stack is empty or pointer does not exist
function LIFO:ReadByPointer(Pointer)
self:T(self.lid.."ReadByPointer " .. tostring(Pointer))
if self.counter == 0 or not Pointer or not self.stackbypointer[Pointer] then return nil end
local object = self.stackbypointer[Pointer] -- #LIFO.IDEntry
if object then
return object.data
else
return nil
end
end
--- LIFO Read, not Pull, Object from Stack by UniqueID
-- @param #LIFO self
-- @param #number UniqueID
-- @return #table Object or nil if stack is empty or ID does not exist
function LIFO:ReadByID(UniqueID)
self:T(self.lid.."ReadByID " .. tostring(UniqueID))
if self.counter == 0 or not UniqueID or not self.stackbyid[UniqueID] then return nil end
local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry
if object then
return object.data
else
return nil
end
end
--- LIFO Pull Object from Stack by UniqueID
-- @param #LIFO self
-- @param #tableUniqueID
-- @return #table Object or nil if stack is empty
function LIFO:PullByID(UniqueID)
self:T(self.lid.."PullByID " .. tostring(UniqueID))
if self.counter == 0 then return nil end
local object = self.stackbyid[UniqueID] -- #LIFO.IDEntry
--self.stackbyid[UniqueID] = nil
if object then
return self:PullByPointer(object.pointer)
else
return nil
end
end
--- LIFO Housekeeping
-- @param #LIFO self
-- @return #LIFO self
function LIFO:Flatten()
self:T(self.lid.."Flatten")
-- rebuild stacks
local pointerstack = {}
local idstack = {}
local counter = 0
for _ID,_entry in pairs(self.stackbypointer) do
counter = counter + 1
pointerstack[counter] = { pointer = counter, data = _entry.data, uniqueID = _entry.uniqueID}
end
for _ID,_entry in pairs(pointerstack) do
idstack[_entry.uniqueID] = { pointer = _entry.pointer , data = _entry.data, uniqueID = _entry.uniqueID}
end
self.stackbypointer = nil
self.stackbypointer = pointerstack
self.stackbyid = nil
self.stackbyid = idstack
self.counter = counter
self.pointer = counter
return self
end
--- LIFO Check Stack is empty
-- @param #LIFO self
-- @return #boolean empty
function LIFO:IsEmpty()
self:T(self.lid.."IsEmpty")
return self.counter == 0 and true or false
end
--- LIFO Get stack size
-- @param #LIFO self
-- @return #number size
function LIFO:GetSize()
self:T(self.lid.."GetSize")
return self.counter
end
--- LIFO Get stack size
-- @param #LIFO self
-- @return #number size
function LIFO:Count()
self:T(self.lid.."Count")
return self.counter
end
--- LIFO Check Stack is NOT empty
-- @param #LIFO self
-- @return #boolean notempty
function LIFO:IsNotEmpty()
self:T(self.lid.."IsNotEmpty")
return not self:IsEmpty()
end
--- LIFO Get the data stack by pointer
-- @param #LIFO self
-- @return #table Table of #LIFO.IDEntry entries
function LIFO:GetPointerStack()
self:T(self.lid.."GetPointerStack")
return self.stackbypointer
end
--- LIFO Get the data stack by UniqueID
-- @param #LIFO self
-- @return #table Table of #LIFO.IDEntry entries
function LIFO:GetIDStack()
self:T(self.lid.."GetIDStack")
return self.stackbyid
end
--- LIFO Get table of UniqueIDs sorted smallest to largest
-- @param #LIFO self
-- @return #table Table of #LIFO.IDEntry entries
function LIFO:GetIDStackSorted()
self:T(self.lid.."GetIDStackSorted")
local stack = self:GetIDStack()
local idstack = {}
for _id,_entry in pairs(stack) do
idstack[#idstack+1] = _id
self:T({"pre",_id})
end
local function sortID(a, b)
return a < b
end
table.sort(idstack)
return idstack
end
--- LIFO Check if a certain UniqeID exists
-- @param #LIFO self
-- @return #boolean exists
function LIFO:HasUniqueID(UniqueID)
self:T(self.lid.."HasUniqueID")
return self.stackbyid[UniqueID] and true or false
end
--- LIFO Print stacks to dcs.log
-- @param #LIFO self
-- @return #LIFO self
function LIFO:Flush()
self:T(self.lid.."FiFo Flush")
self:I("LIFO Flushing Stack by Pointer")
for _id,_data in pairs (self.stackbypointer) do
local data = _data -- #LIFO.IDEntry
self:I(string.format("Pointer: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("LIFO Flushing Stack by ID")
for _id,_data in pairs (self.stackbyid) do
local data = _data -- #LIFO.IDEntry
self:I(string.format("ID: %s | Entry: Number = %s Data = %s UniqueID = %s",tostring(_id),tostring(data.pointer),tostring(data.data),tostring(data.uniqueID)))
end
self:I("Counter = " .. self.counter)
self:I("Pointer = ".. self.pointer)
return self
end
--- LIFO Get table of data entries
-- @param #LIFO self
-- @return #table Raw table indexed [1] to [n] of object entries - might be empty!
function LIFO:GetDataTable()
self:T(self.lid.."GetDataTable")
local datatable = {}
for _,_entry in pairs(self.stackbypointer) do
datatable[#datatable+1] = _entry.data
end
return datatable
end
--- LIFO Get sorted table of data entries by UniqueIDs (must be numerical UniqueIDs only!)
-- @param #LIFO self
-- @return #table Table indexed [1] to [n] of sorted object entries - might be empty!
function LIFO:GetSortedDataTable()
self:T(self.lid.."GetSortedDataTable")
local datatable = {}
local idtablesorted = self:GetIDStackSorted()
for _,_entry in pairs(idtablesorted) do
datatable[#datatable+1] = self:ReadByID(_entry)
end
return datatable
end
--- Iterate the LIFO and call an iterator function for the given LIFO data, providing the object for each element of the stack and optional parameters.
-- @param #LIFO self
-- @param #function IteratorFunction The function that will be called.
-- @param #table Arg (Optional) Further Arguments of the IteratorFunction.
-- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called.
-- @param #table FunctionArguments (Optional) Function arguments.
-- @return #LIFO self
function LIFO:ForEach( IteratorFunction, Arg, Function, FunctionArguments )
self:T(self.lid.."ForEach")
local Set = self:GetPointerStack() or {}
Arg = Arg or {}
local function CoRoutine()
local Count = 0
for ObjectID, ObjectData in pairs( Set ) do
local Object = ObjectData.data
self:T( {Object} )
if Function then
if Function( unpack( FunctionArguments or {} ), Object ) == true then
IteratorFunction( Object, unpack( Arg ) )
end
else
IteratorFunction( Object, unpack( Arg ) )
end
Count = Count + 1
end
return true
end
local co = CoRoutine
local function Schedule()
local status, res = co()
self:T( { status, res } )
if status == false then
error( res )
end
if res == false then
return true -- resume next time the loop
end
return false
end
Schedule()
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- End LIFO
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
end
+123 -112
View File
@@ -5,9 +5,9 @@
-- ===
--
-- ### Author: **TAW CougarNL**, *funkyfranky*
--
--
-- @module Utilities.PROFILER
-- @image MOOSE.JPG
-- @image Utils_Profiler.jpg
--- PROFILER class.
@@ -26,67 +26,79 @@
-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler".
-- @field #string fileNameSuffix Output file name prefix, e.g. "txt"
--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? *
--- *The emperor counsels simplicity.* *First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature?*
--
-- ===
--
-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg)
--
-- # The PROFILER Concept
--
--
-- Profile your lua code. This tells you, which functions are called very often and which consume most real time.
-- With this information you can optimize the perfomance of your code.
--
--
-- # Prerequisites
--
-- The modules **os** and **lfs** need to be desanizied.
--
--
--
-- The modules **os**, **io** and **lfs** need to be desanizied. Comment out the lines
--
-- --sanitizeModule('os')
-- --sanitizeModule('io')
-- --sanitizeModule('lfs')
--
-- in your *"DCS World OpenBeta/Scripts/MissionScripting.lua"* file.
--
-- But be aware that these changes can make you system vulnerable to attacks.
--
-- # Disclaimer
--
-- **Profiling itself is CPU expensive!** Don't use this when you want to fly or host a mission.
--
--
-- # Start
--
--
-- The profiler can simply be started with the @{#PROFILER.Start}(*Delay, Duration*) function
--
--
-- PROFILER.Start()
--
--
-- The optional parameter *Delay* can be used to delay the start by a certain amount of seconds and the optional parameter *Duration* can be used to
-- stop the profiler after a certain amount of seconds.
--
--
-- # Stop
--
--
-- The profiler automatically stops when the mission ends. But it can be stopped any time with the @{#PROFILER.Stop}(*Delay*) function
--
--
-- PROFILER.Stop()
--
--
-- The optional parameter *Delay* can be used to specify a delay after which the profiler is stopped.
--
--
-- When the profiler is stopped, the output is written to a file.
--
--
-- # Output
--
--
-- The profiler output is written to a file in your DCS home folder
--
--
-- X:\User\<Your User Name>\Saved Games\DCS OpenBeta\Logs
--
--
-- The default file name is "MooseProfiler.txt". If that file exists, the file name is "MooseProfiler-001.txt" etc.
--
--
-- ## Data
--
--
-- The data in the output file provides information on the functions that were called in the mission.
--
--
-- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time.
--
--
-- If you only want output for functions that are called more than *X* times per second, you can set
--
--
-- PROFILER.ThreshCPS=1.5
--
--
-- With this setting, only functions which are called more than 1.5 times per second are displayed. The default setting is PROFILER.ThreshCPS=0.0 (no threshold).
--
--
-- Furthermore, you can limit the output for functions that consumed a certain amount of CPU time in total by
--
--
-- PROFILER.ThreshTtot=0.005
--
--
-- With this setting, which is also the default, only functions which in total used more than 5 milliseconds CPU time.
--
--
-- @field #PROFILER
PROFILER = {
ClassName = "PROFILER",
@@ -128,11 +140,11 @@ function PROFILER.Start(Delay, Duration)
if not io then
env.error("ERROR: Profiler needs io to be desanitized!")
go=false
end
end
if not lfs then
env.error("ERROR: Profiler needs lfs to be desanitized!")
go=false
end
end
if not go then
return
end
@@ -140,14 +152,14 @@ function PROFILER.Start(Delay, Duration)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration)
else
-- Set start time.
PROFILER.TstartGame=timer.getTime()
PROFILER.TstartOS=os.clock()
-- Add event handler.
world.addEventHandler(PROFILER.eventHandler)
-- Info in log.
env.info('############################ Profiler Started ############################')
if Duration then
@@ -160,22 +172,22 @@ function PROFILER.Start(Delay, Duration)
env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix)))
env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv")))
env.info('###############################################################################')
-- Message on screen
local duration=Duration or 600
trigger.action.outText("### Profiler running ###", duration)
-- Set hook.
debug.sethook(PROFILER.hook, "cr")
-- Auto stop profiler.
if Duration then
PROFILER.Stop(Duration)
end
end
end
--- Stop profiler.
@@ -183,24 +195,24 @@ end
function PROFILER.Stop(Delay)
if Delay and Delay>0 then
BASE:ScheduleOnce(Delay, PROFILER.Stop)
else
-- Remove hook.
debug.sethook()
-- Run time game.
local runTimeGame=timer.getTime()-PROFILER.TstartGame
-- Run time real OS.
local runTimeOS=os.clock()-PROFILER.TstartOS
-- Show info.
PROFILER.showInfo(runTimeGame, runTimeOS)
end
end
@@ -221,35 +233,35 @@ end
function PROFILER.hook(event)
local f=debug.getinfo(2, "f").func
if event=='call' then
if PROFILER.Counters[f]==nil then
PROFILER.Counters[f]=1
PROFILER.dInfo[f]=debug.getinfo(2,"Sn")
if PROFILER.fTimeTotal[f]==nil then
PROFILER.fTimeTotal[f]=0
end
else
PROFILER.Counters[f]=PROFILER.Counters[f]+1
end
if PROFILER.fTime[f]==nil then
PROFILER.fTime[f]=os.clock()
end
elseif (event=='return') then
if PROFILER.fTime[f]~=nil then
PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f])
PROFILER.fTime[f]=nil
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -265,12 +277,12 @@ end
function PROFILER.getData(func)
local n=PROFILER.dInfo[func]
if n.what=="C" then
return n.name, "?", "?", PROFILER.fTimeTotal[func]
end
return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func]
return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func]
end
--- Write text to log file.
@@ -287,24 +299,24 @@ end
function PROFILER.showTable(data, f, runTimeGame)
-- Loop over data.
for i=1, #data do
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps=t.count/runTimeGame
local threshCPS=cps>=PROFILER.ThreshCPS
local threshTot=t.tm>=PROFILER.ThreshTtot
if threshCPS and threshTot then
-- Output
local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
PROFILER._flog(f, text)
end
end
end
--- Print csv file.
@@ -319,20 +331,20 @@ function PROFILER.printCSV(data, runTimeGame)
-- Header.
local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number,"
g:write(text.."\r\n")
-- Loop over data.
for i=1, #data do
for i=1, #data do
local t=data[i] --#PROFILER.Data
-- Calls per second.
local cps=t.count/runTimeGame
-- Output
local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line))
g:write(txt.."\r\n")
end
-- Close file.
g:close()
end
@@ -344,23 +356,23 @@ end
function PROFILER.getfilename(ext)
local dir=lfs.writedir()..[[Logs\]]
ext=ext or PROFILER.fileNameSuffix
local file=dir..PROFILER.fileNamePrefix.."."..ext
if not UTILS.FileExists(file) then
return file
end
for i=1,999 do
local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext)
if not UTILS.FileExists(file) then
return file
end
end
end
@@ -372,30 +384,30 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
-- Output file.
local file=PROFILER.getfilename(PROFILER.fileNameSuffix)
local f=io.open(file, 'w')
local f=io.open(file, 'w')
-- Gather data.
local Ttot=0
local Calls=0
local t={}
local tcopy=nil --#PROFILER.Data
local tserialize=nil --#PROFILER.Data
local tforgen=nil --#PROFILER.Data
local tpairs=nil --#PROFILER.Data
for func, count in pairs(PROFILER.Counters) do
local s,src,line,tm=PROFILER.getData(func)
if PROFILER.logUnknown==true then
if s==nil then s="<Unknown>" end
end
if s~=nil then
-- Profile data.
local T=
{ func=s,
@@ -404,7 +416,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
count=count,
tm=tm,
} --#PROFILER.Data
-- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data.
if s=="_copy" then
if tcopy==nil then
@@ -419,49 +431,49 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
else
tserialize.count=tserialize.count+T.count
tserialize.tm=tserialize.tm+T.tm
end
end
elseif s=="(for generator)" then
if tforgen==nil then
tforgen=T
else
tforgen.count=tforgen.count+T.count
tforgen.tm=tforgen.tm+T.tm
end
end
elseif s=="pairs" then
if tpairs==nil then
tpairs=T
else
tpairs.count=tpairs.count+T.count
tpairs.tm=tpairs.tm+T.tm
end
end
else
table.insert(t, T)
end
-- Total function time.
Ttot=Ttot+tm
-- Total number of calls.
Calls=Calls+count
end
end
-- Add special cases.
-- Add special cases.
if tcopy then
table.insert(t, tcopy)
end
if tserialize then
table.insert(t, tserialize)
table.insert(t, tserialize)
end
if tforgen then
table.insert(t, tforgen)
end
if tpairs then
table.insert(t, tpairs)
end
end
env.info('############################ Profiler Stopped ############################')
env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame))
env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS))
@@ -470,11 +482,11 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
env.info(string.format("* Total func calls : %d", Calls))
env.info(string.format("* Writing to file : \"%s\"", file))
env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv")))
env.info("##############################################################################")
env.info("##############################################################################")
-- Sort by total time.
table.sort(t, function(a,b) return a.tm>b.tm end)
-- Write data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
@@ -493,7 +505,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
PROFILER._flog(f,string.format("* Total func calls = %d", Calls))
PROFILER._flog(f,"")
PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS))
PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot))
PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot))
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
PROFILER._flog(f,"")
@@ -501,7 +513,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
-- Sort by number of calls.
table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end)
-- Detailed data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
@@ -511,10 +523,10 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
PROFILER._flog(f,"--------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Sort by number of calls.
table.sort(t, function(a,b) return a.count>b.count end)
-- Detailed data.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
@@ -524,7 +536,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
PROFILER._flog(f,"------------------------------------")
PROFILER._flog(f,"")
PROFILER.showTable(t, f, runTimeGame)
-- Closing.
PROFILER._flog(f,"")
PROFILER._flog(f,"************************************************************************************************************************")
@@ -532,8 +544,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS)
PROFILER._flog(f,"************************************************************************************************************************")
-- Close file.
f:close()
-- Print csv file.
PROFILER.printCSV(t, runTimeGame)
end
@@ -750,7 +750,7 @@ routines.groupRandomDistSelf = function(gpData, dist, form, heading, speed)
local pos = routines.getLeadPos(gpData)
local fakeZone = {}
fakeZone.radius = dist or math.random(300, 1000)
fakeZone.point = {x = pos.x, y, pos.y, z = pos.z}
fakeZone.point = {x = pos.x, y = pos.y, z = pos.z}
routines.groupToRandomZone(gpData, fakeZone, form, heading, speed)
return
@@ -125,6 +125,9 @@ end
--
-- * (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
--
-- @param #number length can also be passed as #string
-- @param #number speed Defaults to 1.0
-- @param #boolean isGoogle We're using Google TTS
function STTS.getSpeechTime(length,speed,isGoogle)
local maxRateRatio = 3
+719 -30
View File
@@ -1,4 +1,4 @@
--- This module contains derived utilities taken from the MIST framework, which are excellent tools to be reused in an OO environment.
--- This module contains derived utilities taken from the MIST framework, as well as a lot of added helpers from the MOOSE community.
--
-- ### Authors:
--
@@ -7,6 +7,7 @@
-- ### Contributions:
--
-- * FlightControl : Rework to OO framework.
-- * And many more
--
-- @module Utils
-- @image MOOSE.JPG
@@ -130,6 +131,62 @@ CALLSIGN={
Dublin=9,
Perth=10,
},
F16={
Viper=9,
Venom=10,
Lobo=11,
Cowboy=12,
Python=13,
Rattler=14,
Panther=15,
Wolf=16,
Weasel=17,
Wild=18,
Ninja=19,
Jedi=20,
},
F18={
Hornet=9,
Squid=10,
Ragin=11,
Roman=12,
Sting=13,
Jury=14,
Jokey=15,
Ram=16,
Hawk=17,
Devil=18,
Check=19,
Snake=20,
},
F15E={
Dude=9,
Thud=10,
Gunny=11,
Trek=12,
Sniper=13,
Sled=14,
Best=15,
Jazz=16,
Rage=17,
Tahoe=18,
},
B1B={
Bone=9,
Dark=10,
Vader=11
},
B52={
Buff=9,
Dump=10,
Kenworth=11,
},
TransportAircraft={
Heavy=9,
Trash=10,
Cargo=11,
Ascot=12,
},
} --#CALLSIGN
--- Utilities static class.
@@ -409,13 +466,17 @@ end
-- @param #number knots Speed in knots.
-- @return #number Speed in m/s.
UTILS.KnotsToMps = function( knots )
return knots / 1.94384 --* 1852 / 3600
if type(knots) == "number" then
return knots / 1.94384 --* 1852 / 3600
else
return 0
end
end
--- Convert temperature from Celsius to Farenheit.
--- Convert temperature from Celsius to Fahrenheit.
-- @param #number Celcius Temperature in degrees Celsius.
-- @return #number Temperature in degrees Farenheit.
UTILS.CelciusToFarenheit = function( Celcius )
-- @return #number Temperature in degrees Fahrenheit.
UTILS.CelsiusToFahrenheit = function( Celcius )
return Celcius * 9/5 + 32
end
@@ -748,7 +809,7 @@ function UTILS.BeaufortScale(speed)
return bn,bd
end
--- Split string at seperators. C.f. http://stackoverflow.com/questions/1426954/split-string-in-lua
--- Split string at seperators. C.f. [split-string-in-lua](http://stackoverflow.com/questions/1426954/split-string-in-lua).
-- @param #string str Sting to split.
-- @param #string sep Speparator for split.
-- @return #table Split text.
@@ -970,6 +1031,15 @@ function UTILS.VecDot(a, b)
return a.x*b.x + a.y*b.y + a.z*b.z
end
--- Calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of two 2D vectors. The result is a number.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param DCS#Vec2 b Vector in 2D with x, y components.
-- @return #number Scalar product of the two vectors a*b.
function UTILS.Vec2Dot(a, b)
return a.x*b.x + a.y*b.y
end
--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 3D vector.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @return #number Norm of the vector.
@@ -977,6 +1047,13 @@ function UTILS.VecNorm(a)
return math.sqrt(UTILS.VecDot(a, a))
end
--- Calculate the [euclidean norm](https://en.wikipedia.org/wiki/Euclidean_distance) (length) of a 2D vector.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @return #number Norm of the vector.
function UTILS.Vec2Norm(a)
return math.sqrt(UTILS.Vec2Dot(a, 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.
@@ -1020,6 +1097,14 @@ function UTILS.VecSubstract(a, b)
return {x=a.x-b.x, y=a.y-b.y, z=a.z-b.z}
end
--- Calculate the difference between two 2D vectors by substracting the x,y components from each other.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param DCS#Vec2 b Vector in 2D with x, y components.
-- @return DCS#Vec2 Vector c=a-b with c(i)=a(i)-b(i), i=x,y.
function UTILS.Vec2Substract(a, b)
return {x=a.x-b.x, y=a.y-b.y}
end
--- Calculate the total vector of two 3D vectors by adding the x,y,z components of each other.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
@@ -1028,6 +1113,14 @@ function UTILS.VecAdd(a, b)
return {x=a.x+b.x, y=a.y+b.y, z=a.z+b.z}
end
--- Calculate the total vector of two 2D vectors by adding the x,y components of each other.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param DCS#Vec2 b Vector in 2D with x, y components.
-- @return DCS#Vec2 Vector c=a+b with c(i)=a(i)+b(i), i=x,y.
function UTILS.Vec2Add(a, b)
return {x=a.x+b.x, y=a.y+b.y}
end
--- Calculate the angle between two 3D vectors.
-- @param DCS#Vec3 a Vector in 3D with x, y, z components.
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
@@ -1059,6 +1152,17 @@ function UTILS.VecHdg(a)
return h
end
--- Calculate "heading" of a 2D vector in the X-Y plane.
-- @param DCS#Vec2 a Vector in "D with x, y components.
-- @return #number Heading in degrees in [0,360).
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y, a.x))
if h<0 then
h=h+360
end
return h
end
--- Calculate the difference between two "heading", i.e. angles in [0,360) deg.
-- @param #number h1 Heading one.
-- @param #number h2 Heading two.
@@ -1095,6 +1199,22 @@ function UTILS.VecTranslate(a, distance, angle)
return {x=TX, y=a.y, z=TY}
end
--- Translate 2D vector in the 2D (x,z) plane.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param #number distance The distance to translate.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec2 Translated vector.
function UTILS.Vec2Translate(a, distance, angle)
local SX = a.x
local SY = a.y
local Radians=math.rad(angle or 0)
local TX=distance*math.cos(Radians)+SX
local TY=distance*math.sin(Radians)+SY
return {x=TX, y=TY}
end
--- Rotate 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.
-- @param #number angle Rotation angle in degrees.
@@ -1115,6 +1235,25 @@ function UTILS.Rotate2D(a, angle)
return A
end
--- Rotate 2D vector in the 2D (x,z) plane.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @param #number angle Rotation angle in degrees.
-- @return DCS#Vec2 Vector rotated in the (x,y) plane.
function UTILS.Vec2Rotate2D(a, angle)
local phi=math.rad(angle)
local x=a.x
local y=a.y
local X=x*math.cos(phi)-y*math.sin(phi)
local Y=x*math.sin(phi)+y*math.cos(phi)
local A={x=X, y=Y}
return A
end
--- Converts a TACAN Channel/Mode couple into a frequency in Hz.
-- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X".
@@ -1198,26 +1337,6 @@ function UTILS.GetMissionDayOfYear(Time)
end
--- Returns the current date.
-- @return #string Mission date in yyyy/mm/dd format.
-- @return #number The year anno domini.
-- @return #number The month.
-- @return #number The day.
function UTILS.GetDate()
-- Mission start date
local date, year, month, day=UTILS.GetDCSMissionDate()
local time=timer.getAbsTime()
local clock=UTILS.SecondsToClock(time, false)
local x=tonumber(UTILS.Split(clock, "+")[2])
local day=day+x
end
--- Returns the magnetic declination of the map.
-- Returned values for the current maps are:
--
@@ -1328,6 +1447,23 @@ function UTILS.GetModulationName(Modulation)
end
--- Get the NATO reporting name of a unit type name
-- @param #number Typename The type name.
-- @return #string The Reporting name or "Bogey".
function UTILS.GetReportingName(Typename)
local typename = string.lower(Typename)
for name, value in pairs(ENUMS.ReportingName.NATO) do
local svalue = string.lower(value)
if string.find(typename,svalue,1,true) then
return name
end
end
return "Bogey"
end
--- Get the callsign name from its enumerator value
-- @param #number Callsign The enumerator callsign.
-- @return #string The callsign name or "Ghostrider".
@@ -1357,6 +1493,48 @@ function UTILS.GetCallsignName(Callsign)
end
end
for name, value in pairs(CALLSIGN.B1B) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.B52) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.F15E) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.F16) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.F18) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.FARP) do
if value==Callsign then
return name
end
end
for name, value in pairs(CALLSIGN.TransportAircraft) do
if value==Callsign then
return name
end
end
return "Ghostrider"
end
@@ -1549,8 +1727,8 @@ function UTILS.GetOSTime()
end
--- Shuffle a table accoring to Fisher Yeates algorithm
--@param #table table to be shuffled
--@return #table
--@param #table t Table to be shuffled.
--@return #table Shuffled table.
function UTILS.ShuffleTable(t)
if t == nil or type(t) ~= "table" then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
@@ -1568,6 +1746,32 @@ function UTILS.ShuffleTable(t)
return TempTable
end
--- Get a random element of a table.
--@param #table t Table.
--@param #boolean replace If `true`, the drawn element is replaced, i.e. not deleted.
--@return #number Table element.
function UTILS.GetRandomTableElement(t, replace)
if t == nil or type(t) ~= "table" then
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return
end
math.random()
math.random()
math.random()
local r=math.random(#t)
local element=t[r]
if not replace then
table.remove(t, r)
end
return element
end
--- (Helicopter) Check if one loading door is open.
--@param #string unit_name Unit name to be checked
--@return #boolean Outcome - true if a (loading door) is open, false if not, nil if none exists.
@@ -1612,15 +1816,31 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " side door is open")
ret_val = true
end
if string.find(type_name, "Bell-47") then -- bell aint got no doors so always ready to load injured soldiers
BASE:T(unit_name .. " door is open")
ret_val = true
end
if string.find(type_name, "UH-60L") and (unit:getDrawArgumentValue(401) == 1) or (unit:getDrawArgumentValue(402) == 1) then
BASE:T(unit_name .. " cargo door is open")
ret_val = true
end
if string.find(type_name, "UH-60L" ) and unit:getDrawArgumentValue(38) == 1 or unit:getDrawArgumentValue(400) == 1 then
BASE:T(unit_name .. " front door(s) are open")
ret_val = true
end
if type_name == "AH-64D_BLK_II" then
BASE:T(unit_name .. " front door(s) are open")
ret_val = true -- no doors on this one ;)
end
if ret_val == false then
BASE:T(unit_name .. " all doors are closed")
end
return ret_val
end -- nil
@@ -1772,3 +1992,472 @@ function UTILS.GenerateLaserCodes()
end
return jtacGeneratedLaserCodes
end
--- Function to save an object to a file
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file. Existing file will be overwritten.
-- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line.
-- @return #boolean outcome True if saving is possible, else false.
function UTILS.SaveToFile(Path,Filename,Data)
-- Thanks to @FunkyFranky
-- Check io module is available.
if not io then
BASE:E("ERROR: io not desanitized. Can't save current file.")
return false
end
-- Check default path.
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
local path = nil
if lfs then
path=Path or lfs.writedir()
end
-- Set file name.
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
-- write
local f = assert(io.open(filename, "wb"))
f:write(Data)
f:close()
return true
end
--- Function to save an object to a file
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #boolean outcome True if reading is possible and successful, else false.
-- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string!
function UTILS.LoadFromFile(Path,Filename)
-- Thanks to @FunkyFranky
-- Check io module is available.
if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.")
return false
end
-- Check default path.
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
local path = nil
if lfs then
path=Path or lfs.writedir()
end
-- Set file name.
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local exists=UTILS.CheckFileExists(Path,Filename)
if not exists then
BASE:E(string.format("ERROR: File %s does not exist!",filename))
return false
end
-- read
local file=assert(io.open(filename, "rb"))
local loadeddata = {}
for line in file:lines() do
loadeddata[#loadeddata+1] = line
end
file:close()
return true, loadeddata
end
--- Function to check if a file exists.
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #boolean outcome True if reading is possible, else false.
function UTILS.CheckFileExists(Path,Filename)
-- Thanks to @FunkyFranky
-- 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
-- Check io module is available.
if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.")
return false
end
-- Check default path.
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
local path = nil
if lfs then
path=Path or lfs.writedir()
end
-- Set file name.
local filename=Filename
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local exists=_fileexists(filename)
if not exists then
BASE:E(string.format("ERROR: File %s does not exist!",filename))
return false
else
return true
end
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.
-- @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).
-- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites).
-- 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)
local filename = Filename or "StateListofGroups"
local data = "--Save Stationary List of Groups: "..Filename .."\n"
for _,_group in pairs (List) do
local group = GROUP:FindByName(_group) -- Wrapper.Group#GROUP
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)
else
data = string.format("%s%s,0,0,0,0\n",data,_group)
end
end
-- save the data
local outcome = UTILS.SaveToFile(Path,Filename,data)
return outcome
end
--- Function to save the state of a set of Wrapper.Group#GROUP objects.
-- @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.
-- @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.
-- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce
-- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups
-- cannot be covered with this.
-- **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)
local filename = Filename or "SetOfGroups"
local data = "--Save SET of groups: "..Filename .."\n"
local List = Set:GetSetObjects()
for _,_group in pairs (List) do
local group = _group -- Wrapper.Group#GROUP
if group and group:IsAlive() then
local name = group:GetName()
local template = string.gsub(name,"-(.+)$","")
if string.find(template,"#") then
template = string.gsub(name,"#(%d+)$","")
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)
end
end
-- save the data
local outcome = UTILS.SaveToFile(Path,Filename,data)
return outcome
end
--- Function to save the state of a set of Wrapper.Static#STATIC objects.
-- @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.
-- @return #boolean outcome True if saving is successful, else false.
-- @usage
-- We will go through the set and find the corresponding static and save the current name and postion when alive.
-- The data will be a simple comma separated list of name and state etc, with one header line.
function UTILS.SaveSetOfStatics(Set,Path,Filename)
local filename = Filename or "SetOfStatics"
local data = "--Save SET of statics: "..Filename .."\n"
local List = Set:GetSetObjects()
for _,_group in pairs (List) do
local group = _group -- Wrapper.Static#STATIC
if group and group:IsAlive() then
local name = group:GetName()
local position = group:GetVec3()
data = string.format("%s%s,%d,%d,%d\n",data,name,position.x,position.y,position.z)
end
end
-- save the data
local outcome = UTILS.SaveToFile(Path,Filename,data)
return outcome
end
--- Function to save the state of a list of statics found by name
-- @param #table List Table of strings with statics names
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #boolean outcome True if saving is successful, else false.
-- @usage
-- We will go through the list and find the corresponding static and save the current alive state as 1 (0 when dead).
-- Position is saved for your usage. **Note** this works on UNIT-name level.
-- 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 name and state etc, with one header line.
function UTILS.SaveStationaryListOfStatics(List,Path,Filename)
local filename = Filename or "StateListofStatics"
local data = "--Save Stationary List of Statics: "..Filename .."\n"
for _,_group in pairs (List) do
local group = STATIC:FindByName(_group,false) -- Wrapper.Static#STATIC
if group and group:IsAlive() then
local position = group:GetVec3()
data = string.format("%s%s,1,%d,%d,%d\n",data,_group,position.x,position.y,position.z)
else
data = string.format("%s%s,0,0,0,0\n",data,_group)
end
end
-- save the data
local outcome = UTILS.SaveToFile(Path,Filename,data)
return outcome
end
--- Load back a stationary list 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 Reduce If false, existing loaded groups will not be reduced to fit the saved number.
-- @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)
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofGroups"
local datatable = {}
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- groupname,units,position.x,position.y,position.z
local groupname = dataset[1]
local size = tonumber(dataset[2])
local posx = tonumber(dataset[3])
local posy = tonumber(dataset[4])
local posz = tonumber(dataset[5])
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)
end
end
end
table.insert(datatable,data)
end
else
return nil
end
return datatable
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.
-- @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 }`
function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
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 = {}
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- groupname,template,units,position.x,position.y,position.z
local groupname = dataset[1]
local template = dataset[2]
local size = tonumber(dataset[3])
local posx = tonumber(dataset[4])
local posy = tonumber(dataset[5])
local posz = tonumber(dataset[6])
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local group=nil
local data = { groupname=groupname, size=size, coordinate=coordinate }
table.insert(datatable,data)
if spawn then
local group = SPAWN:New(groupname)
: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)
end
end
else
return nil
end
if spawn then
return setdata
else
return datatable
end
end
--- Load back a SET of statics from file.
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return Core.Set#SET_STATIC Set SET_STATIC containing the static objects.
function UTILS.LoadSetOfStatics(Path,Filename)
local filename = Filename or "SetOfStatics"
local datatable = SET_STATIC:New()
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
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))
end
else
return nil
end
return datatable
end
--- Load back a stationary list of statics 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 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.
-- Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
local reduce = true
if Reduce == false then reduce = false end
local filename = Filename or "StateListofStatics"
local datatable = {}
if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- staticname,units(1/0),position.x,position.y,position.z)
local staticname = dataset[1]
local size = tonumber(dataset[2])
local posx = tonumber(dataset[3])
local posy = tonumber(dataset[4])
local posz = tonumber(dataset[5])
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
local data = { staticname=staticname, size=size, coordinate=coordinate, static=STATIC:FindByName(staticname,false) }
table.insert(datatable,data)
if size==0 and reduce then
local static = STATIC:FindByName(staticname,false)
if static then
static:Destroy(false)
end
end
end
else
return nil
end
return datatable
end
--- Heading Degrees (0-360) to Cardinal
-- @param #number Heading The heading
-- @return #string Cardinal, e.g. "NORTH"
function UTILS.BearingToCardinal(Heading)
if Heading >= 0 and Heading <= 22 then return "North"
elseif Heading >= 23 and Heading <= 66 then return "North-East"
elseif Heading >= 67 and Heading <= 101 then return "East"
elseif Heading >= 102 and Heading <= 146 then return "South-East"
elseif Heading >= 147 and Heading <= 201 then return "South"
elseif Heading >= 202 and Heading <= 246 then return "South-West"
elseif Heading >= 247 and Heading <= 291 then return "West"
elseif Heading >= 292 and Heading <= 338 then return "North-West"
elseif Heading >= 339 then return "North"
end
end
--- Create a BRAA NATO call string BRAA between two GROUP objects
-- @param Wrapper.Group#GROUP FromGrp GROUP object
-- @param Wrapper.Group#GROUP ToGrp GROUP object
-- @return #string Formatted BRAA NATO call
function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
local BRAANATO = "Merged."
local GroupNumber = FromGrp:GetSize()
local GroupWords = "Singleton"
if GroupNumber == 2 then GroupWords = "Two-Ship"
elseif GroupNumber >= 3 then GroupWords = "Heavy"
end
local grpLeadUnit = ToGrp:GetUnit(1)
local tgtCoord = grpLeadUnit:GetCoordinate()
local currentCoord = FromGrp:GetCoordinate()
local hdg = UTILS.Round(ToGrp:GetHeading()/100,1)*100
local bearing = UTILS.Round(currentCoord:HeadingTo(tgtCoord),0)
local rangeMetres = tgtCoord:Get2DDistance(currentCoord)
local rangeNM = UTILS.Round( UTILS.MetersToNM(rangeMetres), 0)
local aspect = tgtCoord:ToStringAspect(currentCoord)
local alt = UTILS.Round(UTILS.MetersToFeet(grpLeadUnit:GetAltitude())/1000,0)--*1000
local track = UTILS.BearingToCardinal(hdg)
if rangeNM > 3 then
if aspect == "" then
BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track)
else
BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track)
end
end
return BRAANATO
end
+192 -40
View File
@@ -69,7 +69,12 @@ AIRBASE = {
[Airbase.Category.HELIPAD] = "Helipad",
[Airbase.Category.SHIP] = "Ship",
},
AirbaseName=nil,
AirbaseZone=nil,
AirbaseCategory=nil,
AirbaseCategoryName=nil,
activerwyno=nil,
parking={},
}
--- Enumeration to identify the airbases in the Caucasus region.
@@ -137,31 +142,31 @@ AIRBASE.Caucasus = {
-- * AIRBASE.Nevada.Laughlin_Airport
-- * AIRBASE.Nevada.Lincoln_County
-- * AIRBASE.Nevada.Mesquite
-- * AIRBASE.Nevada.Mina_Airport_3Q0
-- * AIRBASE.Nevada.Mina_Airport
-- * AIRBASE.Nevada.North_Las_Vegas
-- * AIRBASE.Nevada.Pahute_Mesa_Airstrip
-- * AIRBASE.Nevada.Tonopah_Airport
-- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield
--
--
-- @field Nevada
AIRBASE.Nevada = {
["Creech_AFB"] = "Creech AFB",
["Groom_Lake_AFB"] = "Groom Lake AFB",
["McCarran_International_Airport"] = "McCarran International Airport",
["Nellis_AFB"] = "Nellis AFB",
["Beatty_Airport"] = "Beatty Airport",
["Boulder_City_Airport"] = "Boulder City Airport",
["Creech_AFB"] = "Creech",
["Groom_Lake_AFB"] = "Groom Lake",
["McCarran_International_Airport"] = "McCarran International",
["Nellis_AFB"] = "Nellis",
["Beatty_Airport"] = "Beatty",
["Boulder_City_Airport"] = "Boulder City",
["Echo_Bay"] = "Echo Bay",
["Henderson_Executive_Airport"] = "Henderson Executive Airport",
["Jean_Airport"] = "Jean Airport",
["Laughlin_Airport"] = "Laughlin Airport",
["Henderson_Executive_Airport"] = "Henderson Executive",
["Jean_Airport"] = "Jean",
["Laughlin_Airport"] = "Laughlin",
["Lincoln_County"] = "Lincoln County",
["Mesquite"] = "Mesquite",
["Mina_Airport_3Q0"] = "Mina Airport 3Q0",
["Mina_Airport"] = "Mina",
["North_Las_Vegas"] = "North Las Vegas",
["Pahute_Mesa_Airstrip"] = "Pahute Mesa Airstrip",
["Tonopah_Airport"] = "Tonopah Airport",
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range Airfield",
["Pahute_Mesa_Airstrip"] = "Pahute Mesa",
["Tonopah_Airport"] = "Tonopah",
["Tonopah_Test_Range_Airfield"] = "Tonopah Test Range",
}
--- Airbases of the Normandy map:
@@ -197,7 +202,7 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Funtington
-- * AIRBASE.Normandy.Tangmere
-- * AIRBASE.Normandy.Ford_AF
--
--
-- @field Normandy
AIRBASE.Normandy = {
["Saint_Pierre_du_Mont"] = "Saint Pierre du Mont",
@@ -271,7 +276,7 @@ AIRBASE.Normandy = {
-- * AIRBASE.PersianGulf.Sirri_Island
-- * AIRBASE.PersianGulf.Tunb_Island_AFB
-- * AIRBASE.PersianGulf.Tunb_Kochak
--
--
-- @field PersianGulf
AIRBASE.PersianGulf = {
["Abu_Dhabi_International_Airport"] = "Abu Dhabi Intl",
@@ -316,6 +321,9 @@ AIRBASE.PersianGulf = {
-- * AIRBASE.TheChannel.Lympne
-- * AIRBASE.TheChannel.Detling
-- * AIRBASE.TheChannel.High_Halden
-- * AIRBASE.TheChannel.Biggin_Hill
-- * AIRBASE.TheChannel.Eastchurch
-- * AIRBASE.TheChannel.Headcorn
--
-- @field TheChannel
AIRBASE.TheChannel = {
@@ -328,6 +336,9 @@ AIRBASE.TheChannel = {
["Lympne"] = "Lympne",
["Detling"] = "Detling",
["High_Halden"] = "High Halden",
["Biggin_Hill"] = "Biggin Hill",
["Eastchurch"] = "Eastchurch",
["Headcorn"] = "Headcorn",
}
--- Airbases of the Syria map:
@@ -346,7 +357,6 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Al_Dumayr
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Ru_Convoy_4
-- * AIRBASE.Syria.Hatay
-- * AIRBASE.Syria.Nicosia
-- * AIRBASE.Syria.Pinarbashi
@@ -364,7 +374,6 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Akrotiri
-- * AIRBASE.Syria.Naqoura
-- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.CVN_71
-- * AIRBASE.Syria.Sayqal
-- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Shayrat
@@ -385,6 +394,16 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Beirut_Rafic_Hariri
-- * AIRBASE.Syria.An_Nasiriyah
-- * AIRBASE.Syria.Abu_al_Duhur
-- * AIRBASE.Syria.At_Tanf
-- * AIRBASE.Syria.H3
-- * 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.Ruwayshid
-- * AIRBASE.Syria.Sanliurfa
-- * AIRBASE.Syria.Tal_Siman
--
--@field Syria
AIRBASE.Syria={
@@ -402,7 +421,7 @@ AIRBASE.Syria={
["Wujah_Al_Hajar"]="Wujah Al Hajar",
["Al_Dumayr"]="Al-Dumayr",
["Gazipasa"]="Gazipasa",
["Ru_Convoy_4"]="Ru Convoy-4",
--["Ru_Convoy_4"]="Ru Convoy-4",
["Hatay"]="Hatay",
["Nicosia"]="Nicosia",
["Pinarbashi"]="Pinarbashi",
@@ -440,10 +459,19 @@ AIRBASE.Syria={
["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri",
["An_Nasiriyah"]="An Nasiriyah",
["Abu_al_Duhur"]="Abu al-Duhur",
["At_Tanf"]="At Tanf",
["H3"]="H3",
["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",
["Ruwayshid"]="Ruwayshid",
["Sanliurfa"]="Sanliurfa",
["Tal_Siman"]="Tal Siman",
["Deir_ez-Zor"] = "Deir ez-Zor",
}
--- Airbases of the Mariana Islands map:
--
-- * AIRBASE.MarianaIslands.Rota_Intl
@@ -464,6 +492,17 @@ AIRBASE.MarianaIslands={
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
-- @type AIRBASE.SpotStatus
-- @field #string FREE Spot is free.
-- @field #string OCCUPIED Spot is occupied.
-- @field #string RESERVED Spot is reserved.
AIRBASE.SpotStatus = {
FREE="Free",
OCCUPIED="Occupied",
RESERVED="Reserved",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
-- @type AIRBASE.ParkingSpot
-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot.
@@ -473,6 +512,13 @@ AIRBASE.MarianaIslands={
-- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment.
-- @field #number TerminalID0 Unknown what this means. If you know, please tell us!
-- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID.
-- @field #string AirbaseName Name of the airbase.
-- @field #number MarkerID Numerical ID of marker placed at parking spot.
-- @field Wrapper.Marker#MARKER Marker The marker on the F10 map.
-- @field #string ClientSpot Client unit sitting at this spot or *nil*.
-- @field #string Status Status of spot e.g. AIRBASE.SpotStatus.FREE.
-- @field #string OccupiedBy Name of the aircraft occupying the spot or "unknown". Can be *nil* if spot is not occupied.
-- @field #string ReservedBy Name of the aircraft for which this spot is reserved. Can be *nil* if spot is not reserved.
--- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking
--
@@ -507,6 +553,17 @@ AIRBASE.TerminalType = {
FighterAircraft=244,
}
--- Status of a parking spot.
-- @type AIRBASE.SpotStatus
-- @field #string FREE Spot is free.
-- @field #string OCCUPIED Spot is occupied.
-- @field #string RESERVED Spot is reserved.
AIRBASE.SpotStatus = {
FREE="Free",
OCCUPIED="Occupied",
RESERVED="Reserved",
}
--- Runway data.
-- @type AIRBASE.Runway
-- @field #number heading Heading of the runway in degrees.
@@ -537,6 +594,9 @@ function AIRBASE:Register(AirbaseName)
-- Get descriptors.
self.descriptors=self:GetDesc()
-- Debug info.
--self:I({airbase=AirbaseName, descriptors=self.descriptors})
-- Category.
self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME
@@ -553,13 +613,15 @@ function AIRBASE:Register(AirbaseName)
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
end
else
self:E("ERROR: Unknown airbase category!")
end
-- Init parking spots.
self:_InitParkingSpots()
-- Get 2D position vector.
local vec2=self:GetVec2()
-- Init coordinate.
@@ -575,8 +637,27 @@ function AIRBASE:Register(AirbaseName)
self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500)
end
else
self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName))
self:E(string.format("ERROR: Cannot get category of airbase %s due to DCS 2.5.6 bug! Assuming it is an AIRDROME for now...", tostring(self.AirbaseName)))
end
self.AirbaseCategory=category
-- Category name.
self.AirbaseCategoryName=AIRBASE.CategoryName[self.AirbaseCategory]
-- Airbase zone.
if self.AirbaseCategory==Airbase.Category.SHIP then
local unit=UNIT:FindByName(AirbaseName)
self.AirbaseZone = ZONE_UNIT:New(AirbaseName, unit, 2500)
else
self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, self:GetVec2(), 2500 )
end
-- Get Parking data.
self.parking=self:GetParkingSpotsTable()
-- Register zone.
_DATABASE.ZONENAMES[AirbaseName] = AirbaseName
_DATABASE:AddZone( AirbaseName, self.AirbaseZone )
return self
end
@@ -641,6 +722,24 @@ function AIRBASE:GetDCSObject()
return nil
end
--- Get ID of the airbase.
-- @param #AIRBASE self
-- @return #number The airbase ID.
function AIRBASE:GetID()
local DCSAirbase = Airbase.getByName( self.AirbaseName )
if DCSAirbase then
local id=tonumber(DCSAirbase:getID())
if self.AirbaseCategory==Airbase.Category.SHIP then
return -id
else
return id
end
end
return nil
end
--- Get the airbase zone.
-- @param #AIRBASE self
-- @return Core.Zone#ZONE_RADIUS The zone radius of the airbase.
@@ -851,7 +950,7 @@ function AIRBASE:GetParkingSpotsNumber(termtype)
local parkingdata=self:GetParkingData(false)
local nspots=0
for _,parkingspot in pairs(parkingdata) do
for _,parkingspot in pairs(parkingdata) do --self.parking) do
if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then
nspots=nspots+1
end
@@ -1012,9 +1111,40 @@ function AIRBASE:GetParkingSpotsTable(termtype)
return false
end
-- Get client coordinates.
local function _isclient(_coord)
for clientname, client in pairs(_DATABASE.CLIENTS) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units) do
-- Unit coordinate.
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
-- Distance to parking spot.
local dist=coord:Get2DDistance(_coord)
if dist<=5 then
return clientname
end
end
end
return nil
end
-- Put coordinates of parking spots into table.
local spots={}
for _,_spot in pairs(parkingdata) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then
self:T2({_spot=_spot})
local _free=_isfree(_spot)
local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos)
local _client=_isclient(_coord)
table.insert(spots, {AirbaseName=self.AirbaseName, Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW, ClientSpot=_client})
end
end
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then
@@ -1024,6 +1154,8 @@ function AIRBASE:GetParkingSpotsTable(termtype)
spot.Free=_isfree(_spot) -- updated
spot.TOAC=_spot.TO_AC -- updated
spot.AirbaseName=self.AirbaseName
spot.ClientSpot=nil --TODO
table.insert(spots, spot)
@@ -1055,14 +1187,8 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
for _,_spot in pairs(parkingfree) do
if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then
if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then
local spot=self:_GetParkingSpotByID(_spot.Term_Index)
spot.Free=true -- updated
spot.TOAC=_spot.TO_AC -- updated
table.insert(freespots, spot)
local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos)
table.insert(freespots, {AirbaseName=self.AirbaseName, Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW})
end
end
end
@@ -1095,6 +1221,7 @@ end
-- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed.
-- @param #boolean mark If false, do not place markers but only give output to DCS.log file. Default true.
-- @return #table Table with marker IDs.
function AIRBASE:MarkParkingSpots(termtype, mark)
-- Default is true.
@@ -1109,6 +1236,8 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
local airbasename=self:GetName()
self:E(string.format("Parking spots at %s for terminal type %s:", airbasename, tostring(termtype)))
self.parkingmarks=self.parkingmarks or {}
for _,_spot in pairs(parkingdata) do
-- Mark text.
@@ -1117,16 +1246,31 @@ function AIRBASE:MarkParkingSpots(termtype, mark)
-- Create mark on the F10 map.
if mark then
_spot.Coordinate:MarkToAll(_text)
local mid=_spot.Coordinate:MarkToAll(_text)
if self.parkingmarks[_spot.TerminalID] then
_spot.Coordinate:RemoveMark(self.parkingmarks[_spot.TerminalID])
end
self.parkingmarks[_spot.TerminalID]=mid
end
-- Info to DCS.log file.
local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m",
airbasename, _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)
self:E(_text)
self:I(_text)
end
return self.parkingmarks
end
--- Place markers of parking spots on the F10 map and update regularly.
-- @param #AIRBASE self
-- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed.
-- @param #boolean mark If false, do not place markers but only give output to DCS.log file. Default true.
function AIRBASE:MonitorParkingSpots(termtype, mark)
local sched,shedid=SCHEDULER:New(self, self.MarkParkingSpots, {self,termtype,mark}, 5, 5)
end
--- Seach unoccupied parking spots at the airbase for a specific group of aircraft. The routine also optionally checks for other unit, static and scenery options in a certain radius around the parking spot.
-- The dimension of the spawned aircraft and of the potential obstacle are taken into account. Note that the routine can only return so many spots that are free.
-- @param #AIRBASE self
@@ -1141,7 +1285,7 @@ end
-- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function.
-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID.
function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata)
-- Init default
scanradius=scanradius or 50
if scanunits==nil then
@@ -1197,7 +1341,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
az = 17 -- width
end
-- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size!
local _nspots=nspots or group:GetSize()
@@ -1325,7 +1469,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius,
-- Retrun spots we found, even if there were not enough.
return validspots
end
--- Check black and white lists.
@@ -1344,7 +1488,7 @@ function AIRBASE:_CheckParkingLists(TerminalID)
end
end
-- Check if a whitelist was defined.
if self.parkingWhitelist and #self.parkingWhitelist>0 then
for _,terminalID in pairs(self.parkingWhitelist or {}) do
@@ -1361,6 +1505,14 @@ function AIRBASE:_CheckParkingLists(TerminalID)
return true
end
--- Get category of airbase.
-- @param #AIRBASE self
-- @return #number Category of airbase from GetDesc().category.
function AIRBASE:GetAirbaseCategory()
return self.AirbaseCategory
end
--- Helper function to check for the correct terminal type including "artificial" ones.
-- @param #number Term_Type Termial type from getParking routine.
-- @param #AIRBASE.TerminalType termtype Terminal type from AIRBASE.TerminalType enumerator.
@@ -1454,7 +1606,7 @@ function AIRBASE:GetRunwayData(magvar, mark)
name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or
name==AIRBASE.PersianGulf.Dubai_Intl or
name==AIRBASE.PersianGulf.Shiraz_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport or
name==AIRBASE.PersianGulf.Kish_International_Airport or
name==AIRBASE.MarianaIslands.Andersen_AFB then
-- 1-->4, 2-->3, 3-->2, 4-->1
@@ -1407,7 +1407,7 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E
DCSTask = {
id = 'Escort',
params = {
groupId = FollowControllable:GetID(),
groupId = FollowControllable and FollowControllable:GetID() or nil,
pos = Vec3,
lastWptIndexFlag = LastWaypointIndex and true or false,
lastWptIndex = LastWaypointIndex,
@@ -1437,11 +1437,11 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
id = 'FireAtPoint',
params = {
point = Vec2,
x=Vec2.x,
y=Vec2.y,
x = Vec2.x,
y = Vec2.y,
zoneRadius = Radius,
radius = Radius,
expendQty = 100, -- dummy value
expendQty = 1, -- dummy value
expendQtyEnabled = false,
alt_type = ASL and 0 or 1
}
@@ -1460,7 +1460,8 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti
DCSTask.params.weaponType=WeaponType
end
--self:I(DCSTask)
--env.info("FF fireatpoint")
--BASE:I(DCSTask)
return DCSTask
end
@@ -1832,7 +1833,7 @@ end
do -- Patrol methods
--- (GROUND) Patrol iteratively using the waypoints the for the (parent) group.
--- (GROUND) Patrol iteratively using the waypoints of the (parent) group.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:PatrolRoute()
@@ -1851,27 +1852,27 @@ do -- Patrol methods
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
-- test for submarine
local depth = 0
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
end
local Waypoint = Waypoints[1]
local Speed = Waypoint.speed or (20 / 3.6)
local From = FromCoord:WaypointGround( Speed )
if IsSub then
if IsSub then
From = FromCoord:WaypointNaval( Speed, Waypoint.alt )
end
table.insert( Waypoints, 1, From )
local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" )
@@ -1916,7 +1917,7 @@ do -- Patrol methods
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
@@ -1982,16 +1983,16 @@ do -- Patrol methods
self:F( { PatrolGroup = PatrolGroup:GetName() } )
if PatrolGroup:IsGround() or PatrolGroup:IsShip() then
-- Calculate the new Route.
local FromCoord = PatrolGroup:GetCoordinate()
-- test for submarine
local depth = 0
local IsSub = false
if PatrolGroup:IsShip() then
local navalvec3 = FromCoord:GetVec3()
if navalvec3.y < 0 then
if navalvec3.y < 0 then
depth = navalvec3.y
IsSub = true
end
@@ -2022,22 +2023,20 @@ do -- Patrol methods
end
--- Return a Misson task to follow a given route defined by Points.
--- Return a "Misson" task to follow a given route defined by Points.
-- @param #CONTROLLABLE self
-- @param #table Points A table of route points.
-- @return DCS#Task
-- @return DCS#Task DCS mission task. Has entries `.id="Mission"`, `params`, were params has entries `airborne` and `route`, which is a table of `points`.
function CONTROLLABLE:TaskRoute( Points )
self:F2( Points )
local DCSTask = {
id = 'Mission',
params = {
airborne = self:IsAir(),
airborne = self:IsAir(), -- This is important to make aircraft land without respawning them (which was a long standing DCS issue).
route = {points = Points},
},
}
self:T3( { DCSTask } )
return DCSTask
end
@@ -3775,10 +3774,10 @@ end
--- (GROUND) Relocate controllable to a random point within a given radius; use e.g.for evasive actions; Note that not all ground controllables can actually drive, also the alarm state of the controllable might stop it from moving.
-- @param #CONTROLLABLE self
-- @param #number speed Speed of the controllable, default 20
-- @param #number radius Radius of the relocation zone, default 500
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
-- @param #number speed Speed of the controllable, default 20
-- @param #number radius Radius of the relocation zone, default 500
-- @param #boolean onroad If true, route on road (less problems with AI way finding), default true
-- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false
-- @param #string formation Formation string as in the mission editor, e.g. "Vee", "Diamond", "Line abreast", etc. Defaults to "Off Road"
-- @return #CONTROLLABLE self
function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut, formation)
@@ -3845,3 +3844,45 @@ function POSITIONABLE:IsSubmarine()
return nil
end
--- Sets the controlled group to go at the specified speed in meters per second.
-- @param #CONTROLLABLE self
-- @param #number Speed Speed in meters per second
-- @param #boolean Keep (Optional) When set to true, will maintain the speed on passing waypoints. If not present or false, the controlled group will return to the speed as defined by their route.
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetSpeed(Speed, Keep)
self:F2( { self.ControllableName } )
-- Set default if not specified.
local speed = Speed or 5
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
Controller:setSpeed(speed, Keep)
end
end
return self
end
--- [AIR] Sets the controlled aircraft group to fly at the specified altitude in meters.
-- @param #CONTROLLABLE self
-- @param #number Altitude Altitude in meters.
-- @param #boolean Keep (Optional) When set to true, will maintain the altitude on passing waypoints. If not present or false, the controlled group will return to the altitude as defined by their route.
-- @param #string AltType (Optional) Specifies the altitude type used. If nil, the altitude type of the current waypoint will be used. Accepted values are "BARO" and "RADIO".
-- @return #CONTROLLABLE self
function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType)
self:F2( { self.ControllableName } )
-- Set default if not specified.
local altitude = Altitude or 1000
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if Controller then
if self:IsAir() then
Controller:setAltitude(altitude, Keep, AltType)
end
end
end
return self
end

Some files were not shown because too many files have changed in this diff Show More