Compare commits

...

433 Commits

Author SHA1 Message Date
Frank d52e427238 Merge branch 'FF/Ops' into FF/Fox2 2022-05-09 08:54:01 +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 66d913e4bf Merge branch 'FF/Ops' into FF/Fox2 2020-06-29 21:10:46 +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 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
18 changed files with 7385 additions and 358 deletions
+9 -1
View File
@@ -228,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]
@@ -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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+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
@@ -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
@@ -1597,6 +1597,7 @@ WAREHOUSE = {
lowfuelthresh = 0.15,
respawnafterdestroyed=false,
respawndelay = nil,
flightcontrol = nil,
}
--- Item of the warehouse stock table.
@@ -2581,6 +2582,16 @@ function WAREHOUSE:SetSafeParkingOff()
return self
end
--- Set flight control.
-- @param #WAREHOUSE self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol Flight control object.
-- @return #WAREHOUSE self
function WAREHOUSE:SetFlightControl(flightcontrol)
self.flightcontrol=flightcontrol
return self
end
--- Set low fuel threshold. If one unit of an asset has less fuel than this number, the event AssetLowFuel will be fired.
-- @param #WAREHOUSE self
-- @param #number threshold Relative low fuel threshold, i.e. a number in [0,1]. Default 0.15 (15%).
@@ -4967,6 +4978,7 @@ function WAREHOUSE:onafterUnloaded(From, Event, To, group)
end
end
--- On before "Arrived" event. Triggered when a group has arrived at its destination warehouse.
-- @param #WAREHOUSE self
-- @param #string From From state.
@@ -6009,7 +6021,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol
local AirbaseCategory = self:GetAirbaseCategory()
-- Check enough parking spots.
if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then
if AirbaseCategory==Airbase.Category.HELIPAD then
elseif AirbaseCategory==Airbase.Category.SHIP then
--TODO Figure out what's necessary in this case.
@@ -6314,6 +6328,10 @@ function WAREHOUSE:_RouteAir(aircraft)
if not self.flightcontrol then
local starttime=math.random(60)
local fc=_DATABASE:GetFlightControl(self.airbasename)
if fc then
-- Nothing to do. FC will start the aircraft once it gets taxi clearance.
else
aircraft:StartUncontrolled(starttime)
end
@@ -6472,9 +6490,8 @@ function WAREHOUSE:_OnEventBirth(EventData)
else
self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s", tostring(aid), tostring(rid), tostring(EventData.IniUnitName)))
end
else
--self:T3({wid=wid, uid=self.uid, match=(wid==self.uid), tw=type(wid), tu=type(self.uid)})
-- Born unit does not belong to this warehouse.
end
end
@@ -7887,6 +7904,25 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
return coords
end
if airbase:GetAirbaseCategory()==Airbase.Category.SHIP then
-- Scan a radius of 100 meters around the spot.
local _,_,_,units=airbase:GetCoordinate():ScanObjects(200, true, false, false)
local N=0
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
if not unit:InAir() then
N=N+1
end
end
env.info(string.format("FF number of units on deck=%d", N))
if #assets>=6 and N==0 then
-- More than 6 is difficult. We need an empty deck.
elseif N+#assets>6 then
-- wait until max 6 ac are on deck.
return {}
end
end
-- Get parking spot data table. This contains all free and "non-free" spots.
local parkingdata=airbase.parking --airbase:GetParkingSpotsTable()
@@ -7982,6 +8018,24 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
local free=true
local problem=nil
-- Safe parking using TO_AC from DCS result.
self:I(self.lid..string.format("Parking spot %d TOAC=%s (safe park=%s).", _termid, tostring(_toac), tostring(self.safeparking)))
if self.safeparking and _toac then
free=false
self:I(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).", _termid))
end
-- Check if spot is reserved.
local fc=_DATABASE:GetFlightControl(self.airbasename)
if fc then
local reserved=fc:IsParkingReserved(parkingspot)
if reserved then
free=false
self:I(self.lid..string.format("Parking spot %d is reserved for flight element %s", _termid, tostring(reserved)))
end
end
-- Loop over all obstacles.
for _,obstacle in pairs(obstacles) do
@@ -8800,7 +8854,7 @@ end
--- Fireworks!
-- @param #WAREHOUSE self
-- @param Core.Point#COORDINATE coord
-- @param Core.Point#COORDINATE coord Coordinate where to ignite the fireworks. Default is at the warehouse position.
function WAREHOUSE:_Fireworks(coord)
-- Place.
@@ -9164,9 +9218,11 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination)
wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent*3.6, 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*3.6, true, nil, nil, "Holding")
if not _DATABASE:GetFlightControl(destination:GetName()) then
Pholding.y=H_holding+h_holding
c[#c+1]=Pholding
wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding*3.6, true, nil, nil, "Holding")
end
--- Final destination.
c[#c+1]=Pdestination
+1
View File
@@ -43,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' )
File diff suppressed because it is too large Load Diff
+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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -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
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+116 -14
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.
@@ -487,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.
@@ -621,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
@@ -687,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.
@@ -897,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
@@ -1058,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
@@ -1103,16 +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
spot.AirbaseName=self.AirbaseName
spot.ClientSpot=nil --TODO
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
@@ -1145,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.
@@ -1159,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.
@@ -1167,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
@@ -1411,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.
+47
View File
@@ -0,0 +1,47 @@
-- This routine is called from the LDT environment to create the Moose.lua file stub for use in .miz files.
local MoosePath="D:/DCS/Scripts/moose/MOOSE"
local MooseDevelopmentPath = MoosePath.."/Moose Development/Moose"
local MooseSetupPath = MoosePath.."/Moose Setup"
local MooseTargetPath =MoosePath.."/Moose Setup"
local MooseCommitHash = "Static FF Development"
print( "Commit Hash ID : " .. MooseCommitHash )
print( "Moose development path : " .. MooseDevelopmentPath )
print( "Moose setup path : " .. MooseSetupPath )
print( "Moose target path : " .. MooseTargetPath )
local MooseModulesFilePath=MooseDevelopmentPath.."/Modules.lua"
local LoaderFilePath=MooseTargetPath.."/Moose.lua"
print( "Reading Moose source list : " .. MooseModulesFilePath )
local LoaderFile=io.open(LoaderFilePath, "w")
LoaderFile:write( "env.info( '*** MOOSE DEVELOPMENT version: " .. MooseCommitHash .. " ***' )\n" )
local MooseSourcesFile = io.open( MooseModulesFilePath, "r" )
local MooseSource = MooseSourcesFile:read("*l")
while( MooseSource ) do
if MooseSource ~= "" then
MooseSource = string.match( MooseSource, "Scripts/Moose/(.+)'" )
local MooseFilePath = MooseDevelopmentPath .. "/" .. MooseSource
print( "Load static: " .. MooseFilePath )
local MooseSourceFile = io.open( MooseFilePath, "r" )
local MooseSourceFileText = MooseSourceFile:read( "*a" )
MooseSourceFile:close()
LoaderFile:write( MooseSourceFileText )
end
MooseSource = MooseSourcesFile:read("*l")
end
LoaderFile:write( "BASE:TraceOnOff( false )\n" )
LoaderFile:write( "env.info( '*** MOOSE INCLUDE END *** ' )\n" )
MooseSourcesFile:close()
LoaderFile:close()