Compare commits

...

141 Commits
2.3.0 ... 2.4.a

Author SHA1 Message Date
FlightControl_Master
0e0ab3507c Okay, fixed the problem with the crashing at the deploy zone.
Also added methods to set and/or randomize the pickup speed, pickup radius, deploy speed, deploy radius.
2018-05-13 15:57:28 +02:00
Frank
92a70d07d9 Merge pull request #899 from FlightControl-Master/FF/Develop
ARTY v0.9.1
2018-05-12 15:44:58 +02:00
funkyfranky
8db5351ad7 ARTY v0.9.1
Added option to not engage an already assigned target.
Fixed bug in _checkname function.
2018-05-12 15:42:15 +02:00
FlightControl_Master
25a6cbcf6d Helicopter AI_CARGO_DISPATCHER working (almost).
Queueing at deploy locations working.
2018-05-12 08:37:27 +02:00
Frank
ac7ae68ee8 Merge pull request #898 from FlightControl-Master/FF/Develop
Added Big Smoke Effect
2018-05-11 21:56:01 +02:00
funkyfranky
230a803045 Corrected description. 2018-05-11 12:30:33 +02:00
funkyfranky
d61840d834 Added Big Smoke Effect
Added big smoke effects to COORDINATE API.
2018-05-11 12:27:55 +02:00
funkyfranky
f74520afd3 Merge branch 'develop' into FF/Develop 2018-05-11 12:26:25 +02:00
FlightControl_Master
74668bb7db Fixes 2018-05-10 23:42:14 +02:00
FlightControl_Master
bca964aca8 Helicopter version of AI_CARGO_DISPATCHER_HELICOPTER 2018-05-10 23:17:06 +02:00
funkyfranky
ad9e97f192 Merge branch 'develop' into FF/Develop 2018-05-10 22:48:29 +02:00
funkyfranky
aac5efbf61 ARTY added shots to report output 2018-05-10 22:48:14 +02:00
FlightControl_Master
6b31ad9645 Moose file changes 2018-05-10 20:00:50 +02:00
Frank
0097316333 Merge pull request #896 from FlightControl-Master/FF/Develop
New classes.
2018-05-10 17:57:21 +02:00
funkyfranky
a0adca43f1 Merge branch 'develop' into FF/Develop 2018-05-10 17:05:45 +02:00
funkyfranky
6f0507ea7f Minor fixes.
Positionalble: Added isExist because of problems with getting coords from scenery objects.
Suppresson: Fixes. Added new transitions.
PseudoATC: Removed eject.
Artillery: Optimized debug output.
2018-05-10 15:40:10 +02:00
FlightControl_Master
1159d11a12 New AI_CARGO_DISPATCHER_APC 2018-05-10 14:39:22 +02:00
FlightControl_Master
9ee21f80ac comments 2018-05-10 08:07:28 +02:00
funkyfranky
f9d7eea721 ARTY, PseudoATC, RANGE, RAT, SUPPRESSION
ARTY v0.9.0: Added anti-ship missiles.  Various fixes.
PSEUDOATC v0.9.0: Added docu.  Cleaned up code.  Bug fixes.
RANGE v1.1.1: Changed menu.
RAT v2.2.2:  Changed default setting to menu = off. Added user function to enable/disable menus.
SUPPRESSION v0.9.0: Improvements.
2018-05-09 23:56:55 +02:00
funkyfranky
191fcb25be PseudoATC v0.7.0
Pseudo ATC improvements.
Minor changes in RAT and Suppression.
2018-05-09 00:11:53 +02:00
FlightControl_Master
a1eb0ff4d9 Fixes 2018-05-08 20:26:46 +02:00
FlightControl_Master
0600c96282 Finish Cargo/AI_Cargo_APC 2018-05-08 06:43:15 +02:00
funkyfranky
95d7b8250d Minor Changes. 2018-05-07 23:32:57 +02:00
funkyfranky
fc852e0386 Merge branch 'develop' into FF/Develop 2018-05-07 17:52:33 +02:00
funkyfranky
9429ec66ca RAT fixed typo 2018-05-07 17:44:53 +02:00
funkyfranky
92c3b530cc ARTY v0.8.9
Improved documentation.
2018-05-07 17:33:48 +02:00
FlightControl_Master
dd636939bb Documentation and performance fix. 2018-05-07 06:11:58 +02:00
funkyfranky
6554fa55d1 ARTY v0.8.8
Minor Changes.
2018-05-07 00:20:19 +02:00
funkyfranky
3157a63b7e ARTY v0.8.8
Added working implementation for moves.
Other minor adjustments.
2018-05-06 20:40:14 +02:00
funkyfranky
d9222c23cb ARTY v0.8.7
Added first version of move implementation. Moves are not executed yet.
2018-05-06 17:03:31 +02:00
funkyfranky
16d4a65569 ARTY v0.8.6
Added user function for rearming properties.
Minor bug fixes.
2018-05-06 15:13:36 +02:00
funkyfranky
a2c12dc05e ARTY v0.8.5
Improved GetAmmo function. Display of ammo table.
Removed all schedulers.
2018-05-06 12:51:51 +02:00
funkyfranky
c36579f88a ARTY v0.8.4
Several improvments and fixes.
2018-05-06 12:05:23 +02:00
FlightControl_Master
19b3dcec21 # Conflicts:
#	Moose Development/Moose/AI/AI_Cargo_APC.lua
#	Moose Development/Moose/Wrapper/Controllable.lua
2018-05-06 07:36:58 +02:00
funkyfranky
ba944444da ARTY v0.8.3
Various improvements. Still WIP.
2018-05-05 23:59:02 +02:00
funkyfranky
f33856cddd ARTY v0.8.2
Fixes. Stil WIP
2018-05-05 22:51:43 +02:00
funkyfranky
57c5ab1ecd ARTY v0.8.1
Improvements in FSM states.
Still not quite functional.
2018-05-05 21:45:07 +02:00
funkyfranky
0cf4b8845e ARTY v0.8.0
WIP version. Not functional.
2018-05-05 08:44:16 +02:00
funkyfranky
4fccfa38d4 ARTY v0.7
Added "NewTarget" event.
Improved task function for waypoints
Removed TargetQueue scheduler.
2018-05-03 23:42:03 +02:00
funkyfranky
531c1d7e90 ARTY v0.6 (WIP)
Added documentation
Added user FSM functions.
Adjusted FSM (untested)
Added task route function.
2018-05-03 00:24:23 +02:00
FlightControl_Master
810d900d7e Working with Transporting mode on or off. 2018-05-02 22:18:07 +02:00
funkyfranky
274b44459e ARTY v0.5
- Adjusted schedulers.
- Improved transitions.
- Make rearming unit go back to its original position.
- Added user functions.
- Using only coodinates for target assignments.
- Added dead event handling.
2018-05-02 00:14:05 +02:00
funkyfranky
394b5f5b89 ARTY v0.4.0
Improved assigned time for engagement. Next day should be possible now.
Added check whether a group started firing within a certain time.
2018-04-30 22:53:57 +02:00
funkyfranky
e5268a29cf ARTY v0.3
First working version. But still WIP.
2018-04-30 12:05:51 +02:00
funkyfranky
b14a672b0e ARTY improvements. 2018-04-26 23:08:16 +02:00
funkyfranky
0ec3192fb7 Arty improvements 2018-04-25 22:49:36 +02:00
funkyfranky
33271edf78 Added ARTY class
First draft...
2018-04-24 23:38:41 +02:00
funkyfranky
441fba0830 PseudoATC
Fixed waypoints BR
2018-04-24 21:12:35 +02:00
funkyfranky
7dbc9436ed PseudoATC
restructured menu
2018-04-24 00:29:09 +02:00
FlightControl_Master
c999389cda Handler for unloading too. 2018-04-23 06:51:49 +02:00
funkyfranky
462564cd01 PseudoATC and RANGE
Range: corrected heading
PseudoATC: lots of changes
2018-04-22 23:55:21 +02:00
funkyfranky
c2a968d2ef Merge branch 'develop' into FF/Develop 2018-04-20 00:13:49 +02:00
funkyfranky
2297646873 Range v1.1.1
Added strafe pit/bombing targets info
2018-04-20 00:13:30 +02:00
FlightControl_Master
af050629aa Made it. Now cargo can be defined in the Mission Editor as #CARGO 2018-04-17 06:25:40 +02:00
FlightControl_Master
3757eb06d9 Fixes for AI_CARGO_AIRPLANE 2018-04-15 04:41:52 +02:00
funkyfranky
06b555a50a Merge branch 'develop' into FF/Develop 2018-04-14 19:35:09 +02:00
FlightControl_Master
b9eab34d6a WIP on AI_Cargo_Airplane 2018-04-14 13:45:00 +02:00
FlightControl_Master
1444a613c5 New AI_Cargo_Airplane file 2018-04-14 12:02:48 +02:00
FlightControl_Master
ecf3434c3d Merge branch 'master' into develop 2018-04-14 08:08:01 +02:00
FlightControl_Master
5988ceec05 A new AI Cargo Helicopter class. 2018-04-13 22:31:19 +02:00
FlightControl_Master
f01b2c9149 Finish Cargo/AI_Cargo_APC 2018-04-13 20:35:38 +02:00
FlightControl_Master
b33dd94fa0 Fixed a bug in Spawn regarding spawning with templates. 2018-04-13 13:57:47 +02:00
FlightControl_Master
47dd73a377 Fixes 2018-04-13 12:22:39 +02:00
FlightControl_Master
25ae0c3d15 Finish this feature. 2018-04-13 09:56:00 +02:00
FlightControl_Master
3389c908d7 Finish Task_Assignment 2018-04-12 20:04:19 +02:00
FlightControl_Master
3e6017c0d5 Task Assignment 2018-04-12 20:03:44 +02:00
FlightControl_Master
fea2f55fbb Lots of optimizations 2018-04-12 13:59:55 +02:00
FlightControl_Master
dcc42b8e62 Fixes 2018-04-11 23:24:13 +02:00
FlightControl_Master
9cea486fdc Progress 2018-04-11 17:09:59 +02:00
FlightControl_Master
faa934ffce Fixed stuff for multi player 2018-04-11 17:00:11 +02:00
FlightControl_Master
1ae1b9abd7 Ready for another test session. 2018-04-11 09:14:12 +02:00
FlightControl_Master
ffe4d9a143 Finish Cargo/FC/Transport 2018-04-10 20:02:40 +02:00
funkyfranky
d5d0703502 Merge branch 'develop' into FF/Develop 2018-04-09 13:57:40 +02:00
funkyfranky
fe3e91d1a0 Merge branch 'master' into FF/Develop 2018-04-09 13:57:17 +02:00
FlightControl_Master
1beb34231e Getting rid of an old annoyance. 2018-04-09 13:38:32 +02:00
FlightControl_Master
a6889be676 # Conflicts:
#	Moose Development/Moose/Tasking/Task_CARGO.lua
2018-04-09 13:30:33 +02:00
FlightControl_Master
4eb596f5bf Finish Cargo/FC/Transport 2018-04-09 12:56:11 +02:00
funkyfranky
01de126a8f Merge branch 'develop' into FF/Develop 2018-04-09 08:16:10 +02:00
FlightControl_Master
86cc1f81b6 Finish Cargo/FC/Transport 2018-04-08 22:38:45 +02:00
FlightControl_Master
a95afe9956 # Conflicts:
#	Moose Development/Moose/Tasking/Task_CARGO.lua
2018-04-08 22:36:58 +02:00
FlightControl_Master
8b8fcaaacd # Conflicts:
#	Moose Development/Moose/Tasking/Task_CARGO.lua
2018-04-08 18:59:42 +02:00
FlightControl_Master
31f0bb9fef Adding events for Deployed and PickedUp. 2018-04-08 13:33:18 +02:00
FlightControl_Master
af23aa3b79 -- Lots of fixes done. Especially on the
- Messaging
- Menu system
- Crashing DCS
- Routing
2018-04-08 11:01:46 +02:00
funkyfranky
e606ab1b8a Merge branch 'develop' into FF/Develop 2018-04-07 23:58:40 +02:00
funkyfranky
303f5cb571 RANGE v1.1.0
- Missiles are now tracked.
- Statics can now be used for strafe pits.
- Statics are automatically recognized.
- More user functions to add bomb or strafe targets.
- Bomb targets are allowed to move.
- Bomb targets can automatically move randomly inside the range zone.
- Improved trace output.
- Documentation updated.
2018-04-07 23:36:09 +02:00
FlightControl_Master
b1ecdc727c Faster menu and fix for Disembark loaded engineers in other helicopter. 2018-04-07 19:00:18 +02:00
FlightControl_Master
7735120f25 Progress on AI_CARGO_TROOPS 2018-04-07 14:21:34 +02:00
FlightControl_Master
a247f56c7e Progress on AI_CARGO_TROOPS 2018-04-07 10:18:52 +02:00
FlightControl_Master
718138abd6 Fixed slingload deploy problem.
Fixed messaging. Now a message is shown when cargo reports itself when in LoadRange and when near, it will allow for loading of cargo.
Fixed the perception that cargo can be boarded when loaded in an other carrier, which is totally wrong.
2018-04-06 21:33:54 +02:00
FlightControl_Master
269b52fd0e Fixed slingload deploy problem.
Fixed messaging. Now a message is shown when cargo reports itself when in LoadRange and when near, it will allow for loading of cargo.
Fixed the perception that cargo can be boarded when loaded in an other carrier, which is totally wrong.
2018-04-06 21:28:43 +02:00
funkyfranky
fd34bab65a Merge branch 'develop' into FF/Develop 2018-04-05 22:47:38 +02:00
funkyfranky
e2df7f9732 Merge branch 'master' into FF/Develop 2018-04-05 22:44:41 +02:00
funkyfranky
ed0c5b2264 Added Range 1.0.3
Just copied from other branch.
2018-04-05 22:44:33 +02:00
FlightControl_Master
2ebd25d677 Finish Cargo/FC/Csar 2018-04-05 19:54:24 +02:00
FlightControl_Master
376eeb63c5 Added CARGO_SLINGLOAD and fixed some bugs. 2018-04-05 19:44:04 +02:00
FlightControl_Master
5971f6de09 Added CARGO_SLINGLOAD 2018-04-05 19:43:24 +02:00
FlightControl_Master
a586d81f68 Cargo Crate transportation working :-) 2018-04-05 15:06:59 +02:00
FlightControl_Master
c60dda2545 Cargo Crate transportation working :-) 2018-04-05 15:06:36 +02:00
FlightControl_Master
45d1cb48bb Added Transport tasking to the dispatcher
# Conflicts:
#	Moose Development/Moose/Tasking/Task_Cargo_Dispatcher.lua
2018-04-04 14:44:15 +02:00
FlightControl_Master
0d995d1832 Progress 2018-04-04 14:38:49 +02:00
funkyfranky
e094c8133a Added PseudoATC and Suppression 2018-04-03 16:49:34 +02:00
FlightControl_Master
1f578d4ab5 Working CSAR ... huray 2018-04-03 07:45:26 +02:00
FlightControl_Master
ec973fcc76 Working CSAR ... huray 2018-04-03 07:43:55 +02:00
FlightControl_Master
25831db057 Progress
-- Still need to fix an issue with some strang objects appearing and a crash.
2018-04-02 19:33:44 +02:00
FlightControl_Master
d0925ddfc1 Progress 2018-04-02 09:29:51 +02:00
FlightControl_Master
a4d3089fdb Updates Csar 2018-03-31 09:10:11 +02:00
FlightControl_Master
a94e744028 -- New modules for CSAR tasking. 2018-03-31 07:35:18 +02:00
FlightControl_Master
1401bb29aa New cargo files 2018-03-29 17:56:38 +02:00
FlightControl_Master
9688c606f0 New Cargo files 2018-03-29 17:55:58 +02:00
FlightControl_Master
c4ba2c0235 -- Fixing respawning of CargoGroups after dead event. 2018-03-29 12:48:47 +02:00
FlightControl_Master
35f18d0d1f -- Fixing respawning of CargoGroups after dead event. 2018-03-29 12:48:24 +02:00
FlightControl_Master
93640b1d8e -- Further fixes for TASK_CARGO_TRANSPORT 2018-03-29 12:02:07 +02:00
FlightControl_Master
75d179176d -- A lot of fixes to the new Cargo handling model. Now also TASK_CARGO_TRANSPORT is working. 2018-03-29 12:00:46 +02:00
FlightControl_Master
c37560275e -- A lot of fixes to the new Cargo handling model. Now also TASK_CARGO_TRANSPORT is working. 2018-03-29 12:00:11 +02:00
FlightControl_Master
812902b009 -- First prototype of AI_CARGO_TROOPS 2018-03-28 18:01:07 +02:00
FlightControl_Master
b29ce9b45e Boarding of troops 2018-03-28 18:00:17 +02:00
FlightControl_Master
31a7a4e993 Default of near radius is 25 meters. 2018-03-28 16:48:03 +02:00
FlightControl_Master
7a8881974c Optimizations of cargo deployment. 2018-03-28 16:42:37 +02:00
FlightControl_Master
ea069455d3 Merge branch 'AI_Cargo_Troops_old' into feature/AI_Cargo_Troops_Handling 2018-03-28 16:16:21 +02:00
FlightControl_Master
59bf7fdc96 Merged 'develop'.
# Conflicts:
#	Moose Development/Moose/Core/Cargo.lua
2018-03-28 13:49:57 +02:00
FlightControl_Master
2caada0119 -- Fixed a lot of issues with cargo when the cargo representative is destroyed. 2018-03-27 21:03:30 +02:00
FlightControl_Master
7a579a0ab5 -- Fixed a lot of issues with cargo when the cargo representative is destroyed. 2018-03-27 20:59:37 +02:00
FlightControl_Master
272308cf98 Finish SET_AIRBASE_does_not_update_#817 2018-03-27 15:10:54 +02:00
FlightControl_Master
b4b2034792 Finish SET_AIRBASE_does_not_update_#817 2018-03-27 15:03:47 +02:00
FlightControl_Master
12fcb99218 -- The infantry must only be 5 meters near. 2018-03-27 14:16:25 +02:00
FlightControl_Master
b1a1c6c552 -- The infantry must only be 5 meters near. 2018-03-27 14:16:04 +02:00
FlightControl_Master
61e2e8ca6b Added lua file 2018-03-27 13:57:09 +02:00
FlightControl_Master
21a7bac4e0 added file 2018-03-27 13:56:53 +02:00
FlightControl_Master
12c4e142e9 comment 2018-03-27 13:47:25 +02:00
FlightControl_Master
9759640d52 comment 2018-03-27 13:46:55 +02:00
FlightControl_Master
395923eb07 First prototype of AI_CARGO_TROOPS 2018-03-27 12:13:08 +02:00
FlightControl_Master
727d64927b First working prototype of AI_CARGO_TROOPS. 2018-03-27 12:07:16 +02:00
FlightControl_Master
b6fc46fdd0 Cargo Troops 2018-03-26 17:53:23 +02:00
FlightControl_Master
e7518d69e6 Cargo Troops 2018-03-26 07:39:55 +02:00
FlightControl_Master
cce90b1f46 New AI_CARGO_TROOPS class 2018-03-26 06:52:24 +02:00
FlightControl_Master
a15ef93e7c Merged 'develop'. 2018-03-26 06:10:11 +02:00
FlightControl_Master
7d1d165a5a Finish Detection_error_with_units_and_types 2018-03-26 05:34:20 +02:00
FlightControl_Master
c4f2446b92 fix 2018-03-25 08:17:33 +02:00
FlightControl_Master
7088c4c426 updated branched creation of include repo 2018-03-25 08:15:13 +02:00
FlightControl_Master
7963f04bdc progress 2018-03-24 08:01:49 +01:00
FlightControl_Master
ca32c57c52 Progress on the new cargo group movements 2018-03-24 07:32:04 +01:00
57 changed files with 14529 additions and 2863 deletions

View File

@@ -48,7 +48,7 @@ install:
build_script:
- ps: |
if( $env:appveyor_repo_branch -eq 'master' )
if( $env:appveyor_repo_branch -eq 'master' -or $env:appveyor_repo_branch -eq 'develop' )
{
$apiUrl = 'https://ci.appveyor.com/api'
$token = 'qts80b5kpq0ooj4x6vvw'
@@ -56,8 +56,8 @@ build_script:
"Authorization" = "Bearer $token"
"Content-type" = "application/json"
}
$RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = 'master'; environmentVariables = @{} } | ConvertTo-Json
# get project with last build details
$RequestBody = @{ accountName = 'FlightControl-Master'; projectSlug = 'moose-include'; branch = "$env:appveyor_repo_branch"; environmentVariables = @{} } | ConvertTo-Json
# Generate the new version ...
$project = Invoke-RestMethod -method Post -Uri "$apiUrl/builds" -Headers $headers -Body $RequestBody
}
- ps: |

View File

@@ -0,0 +1,680 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_APC
--- @type AI_CARGO_APC
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_APC class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\APC brings a dynamic cargo handling capability for AI groups.
--
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\APC module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\APC object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will load automatically cargo when the APCs are within boarding or loading range.
-- The boarding or loading range is specified when the cargo is created in the simulation, and therefore, this range depends on the type of cargo
-- and the specified boarding range.
--
-- ## Enemies nearby.
--
-- When the APCs are approaching enemy units, something special is happening.
-- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group.
-- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs,
-- to ensure that the APCs are not too far away from the following running infantry.
-- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route.
--
-- A combat range needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
-- This combat range will trigger the unboarding of troops when enemies are within the combat range around the APCs.
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit range (effectiveness) versus
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
-- A combat range of 350 meters to 500 meters has been proven to be the most effective and efficient.
--
-- ## Infantry health.
--
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
-- ## Control the APCs on the map.
--
-- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map.
-- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch!
-- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up.
-- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine.
-- No workaround is possible on this.
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
--
--
-- @field #AI_CARGO_APC
AI_CARGO_APC = {
ClassName = "AI_CARGO_APC",
Coordinate = nil, -- Core.Point#COORDINATE,
APC_Cargo = {},
}
--- Creates a new AI_CARGO_APC object.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_APC
function AI_CARGO_APC:New( CargoCarrier, CargoSet, CombatRadius )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_APC
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
self.CombatRadius = CombatRadius
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( { "Unboarding", "Unloaded" }, "Unloaded", "Unloaded" )
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Follow", "Following" )
self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Home", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforePickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterPickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Pickup
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Pickup
-- @param #AI_CARGO_APC self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforeDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Delay
--- Loaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterLoaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
--- Unloaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterUnloaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
self:__Monitor( 1 )
self:SetCarrier( CargoCarrier )
self.Transporting = false
self.Relocating = false
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_APC
function AI_CARGO_APC:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
CargoCarrier:HandleEvent( EVENTS.Hit )
function CargoCarrier:OnEventDead( EventData )
self:F({"dead"})
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
AICargoTroops:Destroyed()
end
end
end
function CargoCarrier:OnEventHit( EventData )
self:F({"hit"})
local AICargoTroops = self:GetState( self, "AI_CARGO_APC" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
AICargoTroops:Unload( false )
end
end
end
self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius )
self.Coalition = self.CargoCarrier:GetCoalition()
self:SetControllable( CargoCarrier )
self:Guard()
return self
end
function AI_CARGO_APC:IsTransporting()
return self.Transporting == true
end
function AI_CARGO_APC:IsRelocating()
return self.Relocating == true
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_APC" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
return NearUnit:GetGroup()
end
end
end
return nil
end
--- Follow Infantry to the Carrier.
-- @param #AI_CARGO_APC self
-- @param #AI_CARGO_APC Me
-- @param Wrapper.Unit#UNIT APCUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_APC
function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
--if self:Is( "Following" ) then
if APCUnit:IsAlive() then
-- We check if the Cargo is near to the CargoCarrier.
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then
-- The Cargo does not need to follow the Carrier.
Me:Guard()
else
self:F( { InfantryGroup = InfantryGroup:GetName() } )
if InfantryGroup:IsAlive() then
self:F( { InfantryGroup = InfantryGroup:GetName() } )
local Waypoints = {}
-- Calculate the new Route.
local FromCoord = InfantryGroup:GetCoordinate()
local FromGround = FromCoord:WaypointGround( 10, "Diamond" )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
InfantryGroup:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if APC and APC:IsAlive() then
if self.CarrierCoordinate then
if self:IsRelocating() == true then
local Coordinate = APC:GetCoordinate()
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) or self:Is( "Following" ) then
-- There are no enemies within combat range. Load the CargoCarrier.
self:Load()
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
self:__Unload( 1, false )
else
if self:Is( "Unloaded" ) then
self:Follow()
end
if self:Is( "Following" ) then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if not Cargo:IsNear( APCUnit, 40 ) then
APCUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( APCUnit, 25 ) then
APCUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = APC:GetCoordinate()
end
self:__Monitor( -5 )
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To )
self:F( { APC, From, Event, To } )
local Boarding = false
self.BoardingCount = 0
if APC and APC:IsAlive() then
self.APC_Cargo = {}
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), APC:GetName() } )
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then
self:F( { "In radius", APCUnit:GetName() } )
APC:RouteStop()
--Cargo:Ungroup()
Cargo:Board( APCUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.APC_Cargo[APCUnit] = Cargo
break
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo } )
if APC and APC:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() then
self:__Board( 10, Cargo )
else
self:__Loaded( 1 )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To )
self:F( { APC, From, Event, To } )
local Loaded = true
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
if Loaded == true then
APC:RouteResume()
end
return Loaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnload( APC, From, Event, To, Deployed )
self:F( { APC, From, Event, To, Deployed } )
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
APC:RouteStop()
for _, Cargo in pairs( APCUnit:GetCargo() ) do
Cargo:UnBoard()
self:__Unboard( 10, Cargo, Deployed )
end
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName() } )
if APC and APC:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, Deployed )
else
self:__Unloaded( 1, Cargo, Deployed )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } )
local AllUnloaded = true
--Cargo:Regroup()
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
local CargoCheck = self.APC_Cargo[APCUnit]
if CargoCheck then
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
end
if AllUnloaded == true then
if Deployed == true then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
Cargo:SetDeployed( true )
end
self.APC_Cargo = {}
end
self:Guard()
self.CargoCarrier = APC
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
self:F( { APC, From, Event, To } )
self:F( "Follow" )
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsUnLoaded() then
self:FollowToCarrier( self, APCUnit, Cargo )
APCUnit:RouteResume()
end
end
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Pickup( APC, self )
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
if APC:IsAlive() then
self:Load()
self.Relocating = true
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Deploy( APC, self )
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
if APC:IsAlive() then
self:Unload( true )
self.Transporting = false
self.Relocating = false
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
-- @param #string EndPointFormation The formation at the end point of the action.
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate )
if APC and APC:IsAlive() then
if Coordinate then
self.RoutePickup = true
local Waypoints = APC:TaskGroundOnRoad( Coordinate, 150, "Line abreast" )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
else
AI_CARGO_APC._Pickup( APC, self )
end
self.Transporting = true
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
-- @param #string EndPointFormation The formation at the end point of the action.
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate )
if APC and APC:IsAlive() then
self.RouteDeploy = true
local Waypoints = APC:TaskGroundOnRoad( Coordinate, 150, "Line abreast" )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate )
if APC and APC:IsAlive() ~= nil then
self.RouteHome = true
local Waypoints = APC:TaskGroundOnRoad( Coordinate, 120, "Line abreast" )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end

View File

@@ -0,0 +1,448 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo).
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Airplane
--- @type AI_CARGO_AIRPLANE
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_AIRPLANE class, extends @{Core.Base@BASE}
--
-- ===
--
-- @field #AI_CARGO_AIRPLANE
AI_CARGO_AIRPLANE = {
ClassName = "AI_CARGO_AIRPLANE",
Coordinate = nil -- Core.Point#COORDINATE,
}
--- Creates a new AI_CARGO_AIRPLANE object.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_AIRPLANE
function AI_CARGO_AIRPLANE:New( Airplane, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_AIRPLANE
self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "Unloaded", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Pickup Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Pickup
-- @param #AI_CARGO_AIRPLANE self
-- @param #number Delay
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Handler OnBefore for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy
-- @param #AI_CARGO_AIRPLANE self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
--- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE
-- @function [parent=#AI_CARGO_AIRPLANE] __Deploy
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Delay
self:SetCarrier( Airplane )
return self
end
--- Set the Carrier.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @return #AI_CARGO_AIRPLANE
function AI_CARGO_AIRPLANE:SetCarrier( Airplane )
local AICargo = self
self.Airplane = Airplane -- Wrapper.Group#GROUP
self.Airplane:SetState( self.Airplane, "AI_CARGO_AIRPLANE", self )
self.RoutePickup = false
self.RouteDeploy = false
Airplane:HandleEvent( EVENTS.Dead )
Airplane:HandleEvent( EVENTS.Hit )
Airplane:HandleEvent( EVENTS.EngineShutdown )
function Airplane:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Destroyed()
end
end
end
function Airplane:OnEventHit( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_AIRPLANE" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the Airplane.
AICargoTroops:Unload()
end
end
end
function Airplane:OnEventEngineShutdown( EventData )
AICargo:Landed()
end
self.Coalition = self.Airplane:GetCoalition()
self:SetControllable( Airplane )
return self
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Radius
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
CoordinateZone:Scan( { Object.Category.UNIT } )
for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do
local NearUnit = UNIT:Find( DCSUnit )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_AIRPLANE" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
self:SetCarrier( NearUnit )
break
end
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
if self.RoutePickup == true then
self:Load( Airplane:GetPointVec2() )
self.RoutePickup = false
end
if self.RouteDeploy == true then
self:Unload()
self.RouteDeploy = false
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
self:Route( Airplane, Airbase, Speed )
self.RoutePickup = true
self.Airbase = Airbase
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param From
-- @param Event
-- @param To
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
self:Route( Airplane, Airbase, Speed )
self.RouteDeploy = true
self.Airbase = Airbase
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterLoad( Airplane, From, Event, To, Coordinate )
if Airplane and Airplane:IsAlive() then
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if Cargo:IsInLoadRadius( Coordinate ) then
self:__Board( 5 )
Cargo:Board( Airplane, 25 )
self.Cargo = Cargo
break
end
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self:F({ IsLoaded = self.Cargo:IsLoaded() } )
if not self.Cargo:IsLoaded() then
self:__Board( 10 )
else
self:__Loaded( 1 )
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterLoaded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self.Cargo:UnBoard()
self:__Unboard( 10 )
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnboard( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
if not self.Cargo:IsUnLoaded() then
self:__Unboard( 10 )
else
self:__Unloaded( 1 )
end
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
function AI_CARGO_AIRPLANE:onafterUnloaded( Airplane, From, Event, To )
if Airplane and Airplane:IsAlive() then
self.Airplane = Airplane
end
end
--- @param #AI_CARGO_AIRPLANE self
-- @param Wrapper.Group#GROUP Airplane
-- @param Wrapper.Airbase#AIRBASE Airbase
-- @param #number Speed
function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed )
if Airplane and Airplane:IsAlive() then
local PointVec3 = Airplane:GetPointVec3()
local Takeoff = SPAWN.Takeoff.Hot
local Template = Airplane:GetTemplate()
if Template then
local Points = {}
if self.Airbase then
local FromWaypoint = Template.route.points[1]
-- These are only for ships.
FromWaypoint.linkUnit = nil
FromWaypoint.helipadId = nil
FromWaypoint.airdromeId = nil
local AirbaseID = self.Airbase:GetID()
local AirbaseCategory = self.Airbase:GetDesc().category
FromWaypoint.airdromeId = AirbaseID
FromWaypoint.alt = 0
FromWaypoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
FromWaypoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #Template.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. Template.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. Template.units[UnitID].y )
-- These cause a lot of confusion.
local UnitTemplate = Template.units[UnitID]
UnitTemplate.parking = 15
UnitTemplate.parking_id = "1"
UnitTemplate.alt = 0
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = FromWaypoint.x
local BY = FromWaypoint.y
local TX = PointVec3.x + ( SX - BX )
local TY = PointVec3.z + ( SY - BY )
UnitTemplate.x = TX
UnitTemplate.y = TY
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
end
FromWaypoint.x = PointVec3.x
FromWaypoint.y = PointVec3.z
Points[#Points+1] = FromWaypoint
else
local GroupPoint = Airplane:GetVec2()
local GroupVelocity = Airplane:GetUnit(1):GetDesc().speedMax
local FromWaypoint = {}
FromWaypoint.x = GroupPoint.x
FromWaypoint.y = GroupPoint.y
FromWaypoint.type = "Turning Point"
FromWaypoint.action = "Turning Point"
FromWaypoint.speed = GroupVelocity
Points[#Points+1] = FromWaypoint
end
local AirbasePointVec2 = Airbase:GetPointVec2()
local ToWaypoint = AirbasePointVec2:WaypointAir(
POINT_VEC3.RoutePointAltType.BARO,
"Land",
"Landing",
Speed or Airplane:GetUnit(1):GetDesc().speedMax
)
ToWaypoint["airdromeId"] = Airbase:GetID()
ToWaypoint["speed_locked"] = true,
self:F( ToWaypoint )
Points[#Points+1] = ToWaypoint
Template.x = PointVec3.x
Template.y = PointVec3.z
self:T3( Points )
Template.route.points = Points
--self:Respawn( Template )
local GroupSpawned = Airplane:Respawn( Template )
return GroupSpawned
end
end
end

View File

@@ -0,0 +1,356 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Dispatcher
--- @type AI_CARGO_DISPATCHER
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_DISPATCHER class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\_DISPATCHER brings a dynamic cargo handling capability for AI groups.
--
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
--
--
-- @field #AI_CARGO_DISPATCHER
AI_CARGO_DISPATCHER = {
ClassName = "AI_CARGO_DISPATCHER",
SetAPC = nil,
SetDeployZones = nil,
AI_CARGO_APC = {}
}
--- @type AI_CARGO_DISPATCHER.AI_CARGO_APC
-- @map <Wrapper.Group#GROUP, AI.AI_Cargo_APC#AI_CARGO_APC>
--- @field #AI_CARGO_DISPATCHER.AI_CARGO_APC
AI_CARGO_DISPATCHER.AI_Cargo = {}
--- @field #AI_CARGO_DISPATCHER.PickupCargo
AI_CARGO_DISPATCHER.PickupCargo = {}
--- Creates a new AI_CARGO_DISPATCHER object.
-- @param #AI_CARGO_DISPATCHER self
-- @param Core.Set#SET_GROUP SetAPC
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
function AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER
self.SetAPC = SetAPC -- Core.Set#SET_GROUP
self.SetCargo = SetCargo -- Core.Set#SET_CARGO
self.SetDeployZones = SetDeployZones -- Core.Set#SET_ZONE
self:SetStartState( "Dispatch" )
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Pickup", "*" )
self:AddTransition( "*", "Loading", "*" )
self:AddTransition( "*", "Loaded", "*" )
self:AddTransition( "*", "Deploy", "*" )
self:AddTransition( "*", "Unloading", "*" )
self:AddTransition( "*", "Unloaded", "*" )
self:AddTransition( "*", "Home", "*" )
self.MonitorTimeInterval = 30
self.DeployRadiusInner = 200
self.DeployRadiusOuter = 500
self.CarrierHome = {}
return self
end
--- Set the home zone.
-- When there is nothing anymore to pickup, the carriers will go to a random coordinate in this zone.
-- They will await here new orders.
-- @param #AI_CARGO_DISPATCHER self
-- @param Core.Zone#ZONE_BASE HomeZone
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
-- -- Set the home coordinate
-- local HomeZone = ZONE:New( "Home" )
-- AICargoDispatcher:SetHomeZone( HomeZone )
--
function AI_CARGO_DISPATCHER:SetHomeZone( HomeZone )
self.HomeZone = HomeZone
return self
end
--- Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius.
-- This radius is influencing the location where the carrier will land to pickup the cargo.
-- There are two aspects that are very important to remember and take into account:
--
-- - Ensure that the outer and inner radius are within reporting radius set by the cargo.
-- For example, if the cargo has a reporting radius of 400 meters, and the outer and inner radius is set to 500 and 450 respectively,
-- then no cargo will be loaded!!!
-- - Also take care of the potential cargo position and possible reasons to crash the carrier. This is especially important
-- for locations which are crowded with other objects, like in the middle of villages or cities.
-- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces.
--
-- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number OuterRadius The outer radius in meters around the cargo coordinate.
-- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters!
-- AICargoDispatcher:SetPickupRadius( 500, 300 )
--
function AI_CARGO_DISPATCHER:SetPickupRadius( OuterRadius, InnerRadius )
OuterRadius = OuterRadius or 0
InnerRadius = InnerRadius or OuterRadius
self.PickupOuterRadius = OuterRadius
self.PickupInnerRadius = InnerRadius
return self
end
--- Set the speed or randomizes the speed in km/h to pickup the cargo.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number MaxSpeed (optional) The maximum speed to move to the cargo pickup location.
-- @param #number MinSpeed The minimum speed to move to the cargo pickup location.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
-- -- Set the minimum pickup speed to be 100 km/h and the maximum speed to be 200 km/h.
-- AICargoDispatcher:SetPickupSpeed( 200, 100 )
--
function AI_CARGO_DISPATCHER:SetPickupSpeed( MaxSpeed, MinSpeed )
MaxSpeed = MaxSpeed or 999
MinSpeed = MinSpeed or MaxSpeed
self.PickupMinSpeed = MinSpeed
self.PickupMaxSpeed = MaxSpeed
return self
end
--- Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius.
-- This radius is influencing the location where the carrier will land to deploy the cargo.
-- There is an aspect that is very important to remember and take into account:
--
-- - Take care of the potential cargo position and possible reasons to crash the carrier. This is especially important
-- for locations which are crowded with other objects, like in the middle of villages or cities.
-- So, for the best operation of cargo operations, always ensure that the cargo is located at open spaces.
--
-- The default radius is 0, so the center. In case of a polygon zone, a random location will be selected as the center in the zone.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number OuterRadius The outer radius in meters around the cargo coordinate.
-- @param #number InnerRadius (optional) The inner radius in meters around the cargo coordinate.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
-- -- Set the carrier to land within a band around the cargo coordinate between 500 and 300 meters!
-- AICargoDispatcher:SetDeployRadius( 500, 300 )
--
function AI_CARGO_DISPATCHER:SetDeployRadius( OuterRadius, InnerRadius )
OuterRadius = OuterRadius or 0
InnerRadius = InnerRadius or OuterRadius
self.DeployOuterRadius = OuterRadius
self.DeployInnerRadius = InnerRadius
return self
end
--- Sets or randomizes the speed in km/h to deploy the cargo.
-- @param #AI_CARGO_DISPATCHER self
-- @param #number MaxSpeed The maximum speed to move to the cargo deploy location.
-- @param #number MinSpeed (optional) The minimum speed to move to the cargo deploy location.
-- @return #AI_CARGO_DISPATCHER
-- @usage
--
-- -- Create a new cargo dispatcher
-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone )
--
-- -- Set the minimum deploy speed to be 100 km/h and the maximum speed to be 200 km/h.
-- AICargoDispatcher:SetDeploySpeed( 200, 100 )
--
function AI_CARGO_DISPATCHER:SetDeploySpeed( MaxSpeed, MinSpeed )
MaxSpeed = MaxSpeed or 999
MinSpeed = MinSpeed or MaxSpeed
self.DeployMinSpeed = MinSpeed
self.DeployMaxSpeed = MaxSpeed
return self
end
--- The Start trigger event, which actually takes action at the specified time interval.
-- @param #AI_CARGO_DISPATCHER self
-- @param Wrapper.Group#GROUP APC
-- @return #AI_CARGO_DISPATCHER
function AI_CARGO_DISPATCHER:onafterMonitor()
for APCGroupName, Carrier in pairs( self.SetAPC:GetSet() ) do
local Carrier = Carrier -- Wrapper.Group#GROUP
local AI_Cargo = self.AI_Cargo[Carrier]
if not AI_Cargo then
-- ok, so this APC does not have yet an AI_CARGO_APC object...
-- let's create one and also declare the Loaded and UnLoaded handlers.
self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius )
AI_Cargo = self.AI_Cargo[Carrier]
function AI_Cargo.OnAfterPickup( AI_Cargo, APC, From, Event, To, Cargo )
self:Pickup( APC, Cargo )
end
function AI_Cargo.OnAfterLoad( AI_Cargo, APC )
self:Loading( APC )
end
function AI_Cargo.OnAfterLoaded( AI_Cargo, APC, From, Event, To, Cargo )
self:Loaded( APC, Cargo )
end
function AI_Cargo.OnAfterDeploy( AI_Cargo, APC )
self:Deploy( APC )
end
function AI_Cargo.OnAfterUnload( AI_Cargo, APC )
self:Unloading( APC )
end
function AI_Cargo.OnAfterUnloaded( AI_Cargo, APC )
self:Unloaded( APC )
end
end
-- The Pickup sequence ...
-- Check if this APC need to go and Pickup something...
self:I( { IsTransporting = AI_Cargo:IsTransporting() } )
if AI_Cargo:IsTransporting() == false then
-- ok, so there is a free APC
-- now find the first cargo that is Unloaded
local PickupCargo = nil
for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Cargo] ~= nil } )
if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then
local CargoCoordinate = Cargo:GetCoordinate()
local CoordinateFree = true
for APC, Coordinate in pairs( self.PickupCargo ) do
if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then
CoordinateFree = false
break
end
end
if CoordinateFree == true then
self.PickupCargo[Carrier] = CargoCoordinate
PickupCargo = Cargo
break
end
end
end
if PickupCargo then
self.CarrierHome[Carrier] = nil
local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius )
AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) )
break
else
if self.HomeZone then
if not self.CarrierHome[Carrier] then
self.CarrierHome[Carrier] = true
AI_Cargo:__Home( 10, self.HomeZone:GetRandomPointVec2() )
end
end
end
end
end
self:__Monitor( self.MonitorTimeInterval )
return self
end
--- Make a APC run for a cargo deploy action after the cargo Pickup trigger has been initiated, by default.
-- @param #AI_CARGO_DISPATCHER self
-- @param Wrapper.Group#GROUP APC
-- @return #AI_CARGO_DISPATCHER
function AI_CARGO_DISPATCHER:onafterPickup( From, Event, To, APC, Cargo )
return self
end
--- Make a APC run for a cargo deploy action after the cargo has been loaded, by default.
-- @param #AI_CARGO_DISPATCHER self
-- @param Wrapper.Group#GROUP APC
-- @return #AI_CARGO_DISPATCHER
function AI_CARGO_DISPATCHER:OnAfterLoaded( From, Event, To, APC, Cargo )
self:I( { "Loaded Dispatcher", APC } )
local DeployZone = self.SetDeployZones:GetRandomZone()
self:I( { RandomZone = DeployZone } )
local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius )
self.AI_Cargo[APC]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) )
self.PickupCargo[APC] = nil
return self
end

View File

@@ -0,0 +1,67 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using APCs.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Dispatcher_APC
--- @type AI_CARGO_DISPATCHER_APC
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- # AI\_CARGO\_DISPATCHER\_APC class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\_DISPATCHER\_APC brings a dynamic cargo handling capability for AI groups.
--
-- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_APC module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_APC object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
--
--
-- @field #AI_CARGO_DISPATCHER_APC
AI_CARGO_DISPATCHER_APC = {
ClassName = "AI_CARGO_DISPATCHER_APC",
}
--- Creates a new AI_CARGO_DISPATCHER_APC object.
-- @param #AI_CARGO_DISPATCHER_APC self
-- @param Core.Set#SET_GROUP SetAPC
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER_APC
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo )
--
function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_APC
self.CombatRadius = 500
self:SetDeploySpeed( 70, 120 )
self:SetPickupSpeed( 70, 120 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:Monitor( 1 )
return self
end
function AI_CARGO_DISPATCHER_APC:AICargo( APC, SetCargo )
return AI_CARGO_APC:New( APC, SetCargo, self.CombatRadius )
end

View File

@@ -0,0 +1,54 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Planes.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Dispatcher_Airplane
--- @type AI_CARGO_DISPATCHER_AIRPLANE
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- # AI\_CARGO\_DISPATCHER\_AIRPLANE class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\_DISPATCHER\_AIRPLANE brings a dynamic cargo handling capability for AI groups.
--
-- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_AIRPLANE module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_AIRPLANE object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
--
--
-- @field #AI_CARGO_DISPATCHER_AIRPLANE
AI_CARGO_DISPATCHER_AIRPLANE = {
ClassName = "AI_CARGO_DISPATCHER_AIRPLANE",
}
--- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object.
-- @param #AI_CARGO_DISPATCHER_AIRPLANE self
-- @param Core.Set#SET_GROUP SetAirplane
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER_AIRPLANE
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetAirplane = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo )
--
function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAirplane, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE
return self
end

View File

@@ -0,0 +1,65 @@
--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Dispatcher_Helicopter
--- @type AI_CARGO_DISPATCHER_HELICOPTER
-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER
--- # AI\_CARGO\_DISPATCHER\_HELICOPTER class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\_DISPATCHER\_HELICOPTER brings a dynamic cargo handling capability for AI groups.
--
-- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\_DISPATCHER\_HELICOPTER module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_HELICOPTER object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
--
--
-- @field #AI_CARGO_DISPATCHER_HELICOPTER
AI_CARGO_DISPATCHER_HELICOPTER = {
ClassName = "AI_CARGO_DISPATCHER_HELICOPTER",
}
--- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object.
-- @param #AI_CARGO_DISPATCHER_HELICOPTER self
-- @param Core.Set#SET_GROUP SetHelicopter
-- @param Core.Set#SET_CARGO SetCargo
-- @param Core.Set#SET_ZONE SetDeployZone
-- @return #AI_CARGO_DISPATCHER_HELICOPTER
-- @usage
--
-- -- Create a new cargo dispatcher
-- SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart()
-- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart()
-- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart()
-- AICargoDispatcher = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo )
--
function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZones )
local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER
self:SetDeploySpeed( 200, 150 )
self:SetPickupSpeed( 200, 150 )
self:SetPickupRadius( 0, 0 )
self:SetDeployRadius( 0, 0 )
self:Monitor( 1 )
return self
end
function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, SetCargo )
return AI_CARGO_HELICOPTER:New( Helicopter, SetCargo )
end

View File

@@ -0,0 +1,685 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo).
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI_Cargo_Helicopter
--- @type AI_CARGO_HELICOPTER
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_TROOPS class, extends @{Core.Base@BASE}
--
-- ===
--
-- @field #AI_CARGO_HELICOPTER
AI_CARGO_HELICOPTER = {
ClassName = "AI_CARGO_HELICOPTER",
Coordinate = nil -- Core.Point#COORDINATE,
}
AI_CARGO_QUEUE = {}
--- Creates a new AI_CARGO_HELICOPTER object.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_HELICOPTER
self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP
self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 )
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "Unloaded", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unloaded" )
self:AddTransition( "*", "Landed", "*" )
self:AddTransition( "*", "Queue", "*" )
self:AddTransition( "*", "Orbit" , "*" )
self:AddTransition( "*", "Home" , "*" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforePickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterPickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Pickup
-- @param #AI_CARGO_HELICOPTER self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Handler OnBefore for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] OnAfterDeploy
-- @param #AI_CARGO_HELICOPTER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER
-- @function [parent=#AI_CARGO_HELICOPTER] __Deploy
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Delay
self:SetCarrier( Helicopter )
return self
end
function AI_CARGO_HELICOPTER:IsTransporting()
return self.Transporting == true
end
function AI_CARGO_HELICOPTER:IsRelocating()
return self.Relocating == true
end
--- Set the Carrier.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
local AICargo = self
self.Helicopter = Helicopter -- Wrapper.Group#GROUP
self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self )
self.RoutePickup = false
self.RouteDeploy = false
Helicopter:HandleEvent( EVENTS.Dead )
Helicopter:HandleEvent( EVENTS.Hit )
Helicopter:HandleEvent( EVENTS.Land )
function Helicopter:OnEventDead( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" )
self:F({AICargoTroops=AICargoTroops})
if AICargoTroops then
self:F({})
if not AICargoTroops:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the Helicopter.
AICargoTroops:Destroyed()
end
end
end
function Helicopter:OnEventHit( EventData )
local AICargoTroops = self:GetState( self, "AI_CARGO_HELICOPTER" )
if AICargoTroops then
self:F( { OnHitLoaded = AICargoTroops:Is( "Loaded" ) } )
if AICargoTroops:Is( "Loaded" ) or AICargoTroops:Is( "Boarding" ) then
-- There are enemies within combat range. Unload the Helicopter.
AICargoTroops:Unload()
end
end
end
function Helicopter:OnEventLand( EventData )
AICargo:Landed()
end
self.Coalition = self.Helicopter:GetCoalition()
self:SetControllable( Helicopter )
return self
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To )
if Helicopter and Helicopter:IsAlive() then
if self.RoutePickup == true then
self:Load( Helicopter:GetPointVec2() )
self.RoutePickup = false
self.Relocating = true
end
if self.RouteDeploy == true then
self:Unload( true )
self.RouteDeploy = false
self.Transporting = false
self.Relocating = false
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate )
local HelicopterInZone = false
if Helicopter and Helicopter:IsAlive() then
local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() )
if Distance > 500 then
self:__Queue( -10, Coordinate )
else
local ZoneFree = true
for Helicopter, ZoneQueue in pairs( AI_CARGO_QUEUE ) do
local ZoneQueue = ZoneQueue -- Core.Zone#ZONE_RADIUS
if ZoneQueue:IsCoordinateInZone( Coordinate ) then
ZoneFree = false
end
end
self:F({ZoneFree=ZoneFree})
if ZoneFree == true then
local ZoneQueue = ZONE_RADIUS:New( Helicopter:GetName(), Coordinate:GetVec2(), 100 )
AI_CARGO_QUEUE[Helicopter] = ZoneQueue
local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate()
-- local WaypointFrom = CoordinateFrom:WaypointAir(
-- "RADIO",
-- POINT_VEC3.RoutePointType.TurningPoint,
-- POINT_VEC3.RoutePointAction.TurningPoint,
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
else
self:__Queue( -10, Coordinate )
end
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordinate )
if Helicopter and Helicopter:IsAlive() then
if not self:IsTransporting() then
local Route = {}
-- local CoordinateFrom = Helicopter:GetCoordinate()
-- local WaypointFrom = CoordinateFrom:WaypointAir(
-- "RADIO",
-- POINT_VEC3.RoutePointType.TurningPoint,
-- POINT_VEC3.RoutePointAction.TurningPoint,
-- Speed,
-- true
-- )
-- Route[#Route+1] = WaypointFrom
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
50,
true
)
Route[#Route+1] = WaypointTo
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 80 ), 0, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To, Coordinate )
local Boarding = false
if Helicopter and Helicopter:IsAlive() then
self.BoardingCount = 0
if Helicopter and Helicopter:IsAlive() then
self.Helicopter_Cargo = {}
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded() } )
if Cargo:IsUnLoaded() then
if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then
self:F( { "In radius", HelicopterUnit:GetName() } )
--Cargo:Ungroup()
Cargo:Board( HelicopterUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.Helicopter_Cargo[HelicopterUnit] = Cargo
break
end
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo } )
if Helicopter and Helicopter:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded() } )
if not Cargo:IsLoaded() then
self:__Board( 10, Cargo )
else
self:__Loaded( 1, Cargo )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To, Cargo )
self:F( { APC, From, Event, To } )
local Loaded = true
if Helicopter and Helicopter:IsAlive() then
for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
return Loaded
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To, Deployed )
if Helicopter and Helicopter:IsAlive() then
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do
Cargo:UnBoard()
Cargo:SetDeployed( true )
self:__Unboard( 10, Cargo, Deployed )
end
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To, Cargo, Deployed )
if Helicopter and Helicopter:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo, Deployed )
else
self:__Unloaded( 1, Cargo, Deployed )
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To, Cargo, Deployed )
self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } )
local AllUnloaded = true
--Cargo:Regroup()
if Helicopter and Helicopter:IsAlive() then
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local CargoCheck = self.Helicopter_Cargo[HelicopterUnit] -- Cargo.Cargo#CARGO
if CargoCheck then
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
end
if AllUnloaded == true then
if Deployed == true then
for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
end
self.Helicopter_Cargo = {}
end
self.Helicopter = Helicopter
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To, Cargo, Deployed )
self:Orbit( Helicopter:GetCoordinate(), 50 )
AI_CARGO_QUEUE[Helicopter] = nil
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate )
if Helicopter and Helicopter:IsAlive() ~= nil then
Helicopter:Activate()
self.RoutePickup = true
Coordinate.y = math.random( 50, 200 )
local Route = {}
--- Calculate the target route point.
local CoordinateFrom = Helicopter:GetCoordinate()
local CoordinateTo = Coordinate
--- Create a route point of type air.
local WaypointFrom = CoordinateFrom:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
--- Create a route point of type air.
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 1 )
self.Transporting = true
end
end
function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate )
AICargoHelicopter:__Queue( -10, Coordinate, 100 )
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate )
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteDeploy = true
local Route = {}
--- Calculate the target route point.
Coordinate.y = math.random( 50, 200 )
--- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
Route[#Route+1] = WaypointFrom
Route[#Route+1] = WaypointFrom
--- Create a route point of type air.
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
Route[#Route+1] = WaypointTo
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate )
Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), 0, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) )
--Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 1 )
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate )
if Helicopter and Helicopter:IsAlive() ~= nil then
self.RouteHome = true
local Route = {}
--- Calculate the target route point.
Coordinate.y = math.random( 50, 200 )
--- Create a route point of type air.
local CoordinateFrom = Helicopter:GetCoordinate()
local WaypointFrom = CoordinateFrom:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
Route[#Route+1] = WaypointFrom
--- Create a route point of type air.
local CoordinateTo = Coordinate
local WaypointTo = CoordinateTo:WaypointAir(
"RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
150,
true
)
Route[#Route+1] = WaypointTo
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
Helicopter:WayPointInitialize( Route )
local Tasks = {}
Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() )
Route[#Route].task = Helicopter:TaskCombo( Tasks )
Route[#Route+1] = WaypointTo
-- Now route the helicopter
Helicopter:Route( Route, 0 )
end
end

View File

@@ -155,8 +155,7 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:F( { ProcessUnit, From, Event, To } )
function ACT_ASSIGN_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
self:__Assign( 1 )
end
@@ -167,10 +166,7 @@ do -- ACT_ASSIGN_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, From, Event, To )
self:F( { ProcessUnit, From, Event, To } )
local ProcessGroup = ProcessUnit:GetGroup()
function ACT_ASSIGN_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To )
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
@@ -192,36 +188,26 @@ do -- ACT_ASSIGN_MENU_ACCEPT
--- Init.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskName
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:New( TaskName, TaskBriefing )
function ACT_ASSIGN_MENU_ACCEPT:New( TaskBriefing )
-- Inherits from BASE
local self = BASE:Inherit( self, ACT_ASSIGN:New() ) -- #ACT_ASSIGN_MENU_ACCEPT
self.TaskName = TaskName
self.TaskBriefing = TaskBriefing
return self
end
function ACT_ASSIGN_MENU_ACCEPT:Init( FsmAssign )
self.TaskName = FsmAssign.TaskName
self.TaskBriefing = FsmAssign.TaskBriefing
end
--- Creates a new task assignment state machine. The process will request from the menu if it accepts the task, if not, the unit is removed from the simulator.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
-- @param #string TaskName
-- @param #string TaskBriefing
-- @return #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskName, TaskBriefing )
function ACT_ASSIGN_MENU_ACCEPT:Init( TaskBriefing )
self.TaskBriefing = TaskBriefing
self.TaskName = TaskName
return self
end
@@ -232,30 +218,31 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, From, Event, To )
self:F( { ProcessUnit, From, Event, To } )
function ACT_ASSIGN_MENU_ACCEPT:onafterStart( ProcessUnit, Task, From, Event, To )
self:GetCommandCenter():MessageTypeToGroup( "Access the radio menu to accept the task. You have 30 seconds or the assignment will be cancelled.", ProcessUnit:GetGroup(), MESSAGE.Type.Information )
self:GetCommandCenter():MessageToGroup( "Task " .. self.Task:GetName() .. " has been assigned to you and your group!\nRead the briefing and use the Radio Menu (F10) / Task ... CONFIRMATION menu to accept or reject the task.\nYou have 2 minutes to accept, or the task assignment will be cancelled!", ProcessUnit:GetGroup(), 120 )
local ProcessGroup = ProcessUnit:GetGroup()
local TaskGroup = ProcessUnit:GetGroup()
self.Menu = MENU_GROUP:New( ProcessGroup, "Task " .. self.TaskName .. " acceptance" )
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Accept task " .. self.TaskName, self.Menu, self.MenuAssign, self )
self.MenuRejectTask = MENU_GROUP_COMMAND:New( ProcessGroup, "Reject task " .. self.TaskName, self.Menu, self.MenuReject, self )
self.Menu = MENU_GROUP:New( TaskGroup, "Task " .. self.Task:GetName() .. " CONFIRMATION" )
self.MenuAcceptTask = MENU_GROUP_COMMAND:New( TaskGroup, "Accept task " .. self.Task:GetName(), self.Menu, self.MenuAssign, self, TaskGroup )
self.MenuRejectTask = MENU_GROUP_COMMAND:New( TaskGroup, "Reject task " .. self.Task:GetName(), self.Menu, self.MenuReject, self, TaskGroup )
self:__Reject( 120, TaskGroup )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign()
function ACT_ASSIGN_MENU_ACCEPT:MenuAssign( TaskGroup )
self:__Assign( 1 )
self:__Assign( -1, TaskGroup )
end
--- Menu function.
-- @param #ACT_ASSIGN_MENU_ACCEPT self
function ACT_ASSIGN_MENU_ACCEPT:MenuReject()
function ACT_ASSIGN_MENU_ACCEPT:MenuReject( TaskGroup )
self:__Reject( 1 )
self:__Reject( -1, TaskGroup )
end
--- StateMachine callback function
@@ -264,8 +251,7 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, From, Event, To )
self:F( { ProcessUnit.UnitNameFrom, Event, To } )
function ACT_ASSIGN_MENU_ACCEPT:onafterAssign( ProcessUnit, Task, From, Event, To, TaskGroup )
self.Menu:Remove()
end
@@ -276,13 +262,25 @@ do -- ACT_ASSIGN_MENU_ACCEPT
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, From, Event, To )
self:F( { ProcessUnit.UnitName, From, Event, To } )
function ACT_ASSIGN_MENU_ACCEPT:onafterReject( ProcessUnit, Task, From, Event, To, TaskGroup )
self:F( { TaskGroup = TaskGroup } )
self.Menu:Remove()
--TODO: need to resolve this problem ... it has to do with the events ...
--self.Task:UnAssignFromUnit( ProcessUnit )needs to become a callback funtion call upon the event
ProcessUnit:Destroy()
self.Task:RejectGroup( TaskGroup )
end
--- StateMachine callback function
-- @param #ACT_ASSIGN_ACCEPT self
-- @param Wrapper.Unit#UNIT ProcessUnit
-- @param #string Event
-- @param #string From
-- @param #string To
function ACT_ASSIGN_MENU_ACCEPT:onenterAssigned( ProcessUnit, Task, From, Event, To, TaskGroup )
--self.Task:AssignToGroup( TaskGroup )
self.Task:Assign( ProcessUnit, ProcessUnit:GetPlayerName() )
end
end -- ACT_ASSIGN_MENU_ACCEPT

View File

@@ -123,15 +123,19 @@ do -- ACT_ROUTE
--- Set a Cancel Menu item.
-- @param #ACT_ROUTE self
-- @return #ACT_ROUTE
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime )
function ACT_ROUTE:SetMenuCancel( MenuGroup, MenuText, ParentMenu, MenuTime, MenuTag )
MENU_GROUP_COMMAND:New(
self.CancelMenuGroupCommand = MENU_GROUP_COMMAND:New(
MenuGroup,
MenuText,
ParentMenu,
self.MenuCancel,
self
):SetTime(MenuTime)
):SetTime( MenuTime ):SetTag( MenuTag )
ParentMenu:SetTime( MenuTime )
ParentMenu:Remove( MenuTime, MenuTag )
return self
end
@@ -206,7 +210,9 @@ do -- ACT_ROUTE
function ACT_ROUTE:MenuCancel()
self:Cancel()
self:F("Cancelled")
self.CancelMenuGroupCommand:Remove()
self:__Cancel( 1 )
end
--- Task Events
@@ -238,10 +244,8 @@ do -- ACT_ROUTE
-- @param #string From
-- @param #string To
function ACT_ROUTE:onbeforeRoute( ProcessUnit, From, Event, To )
self:F( { "BeforeRoute 1", self.DisplayCount, self.DisplayInterval } )
if ProcessUnit:IsAlive() then
self:F( "BeforeRoute 2" )
local HasArrived = self:onfuncHasArrived( ProcessUnit ) -- Polymorphic
if self.DisplayCount >= self.DisplayInterval then
self:T( { HasArrived = HasArrived } )
@@ -253,8 +257,6 @@ do -- ACT_ROUTE
self.DisplayCount = self.DisplayCount + 1
end
self:T( { DisplayCount = self.DisplayCount } )
if HasArrived then
self:__Arrive( 1 )
else
@@ -337,7 +339,7 @@ do -- ACT_ROUTE_POINT
-- @param #ACT_ROUTE_POINT self
-- @param #number Range The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:SetRange( Range )
self:F2( { self.Range } )
self:F2( { Range } )
self.Range = Range or 10000
end
@@ -345,6 +347,7 @@ do -- ACT_ROUTE_POINT
-- @param #ACT_ROUTE_POINT self
-- @return #number The Range to consider the arrival. Default is 10000 meters.
function ACT_ROUTE_POINT:GetRange()
self:F2( { self.Range } )
return self.Range
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object.
--
-- ===
--
-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG)
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module CargoCrate
do -- CARGO_CRATE
--- Models the behaviour of cargo crates, which can be slingloaded and boarded on helicopters.
-- @type CARGO_CRATE
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- # CARGO\_CRATE class, extends @{#CARGO_REPRESENTABLE}
--
-- The CARGO\_CRATE class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_CRATE objects to and from carriers.
--
-- ===
--
-- @field #CARGO_CRATE
CARGO_CRATE = {
ClassName = "CARGO_CRATE"
}
--- CARGO_CRATE Constructor.
-- @param #CARGO_CRATE self
-- @param Wrapper.Static#STATIC CargoStatic
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_CRATE
function CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_CRATE
self:F( { Type, Name, NearRadius } )
self.CargoObject = CargoStatic -- Wrapper.Static#STATIC
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- @param #CARGO_CRATE self
-- @param Core.Event#EVENTDATA EventData
function CARGO_CRATE:OnEventCargoDead( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then
if self.CargoObject:GetName() == EventData.IniUnitName then
if not self.NoDestroy then
Destroyed = true
end
end
else
if self:IsLoaded() then
local CarrierName = self.CargoCarrier:GetName()
if CarrierName == EventData.IniDCSUnitName then
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
Destroyed = true
self.CargoCarrier:ClearCargo()
end
end
end
if Destroyed then
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
self:Destroyed()
end
end
--- Enter UnLoaded State.
-- @param #CARGO_CRATE self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2
function CARGO_CRATE:onenterUnLoaded( From, Event, To, ToPointVec2 )
--self:F( { ToPointVec2, From, Event, To } )
local Angle = 180
local Speed = 10
local Distance = 10
if From == "Loaded" then
local StartCoordinate = self.CargoCarrier:GetCoordinate()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployCoord = StartCoordinate:Translate( Distance, CargoDeployHeading )
ToPointVec2 = ToPointVec2 or COORDINATE:NewFromVec2( { x= CargoDeployCoord.x, y = CargoDeployCoord.z } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
self.CargoCarrier = nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
self.OnUnLoadedCallBack = nil
end
end
--- Loaded State.
-- @param #CARGO_CRATE self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_CRATE:onenterLoaded( From, Event, To, CargoCarrier )
--self:F( { From, Event, To, CargoCarrier } )
self.CargoCarrier = CargoCarrier
-- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects).
if self.CargoObject then
self:T("Destroying")
self.NoDestroy = true
self.CargoObject:Destroy()
--local Coordinate = self.CargoObject:GetCoordinate():GetRandomCoordinateInRadius( 50, 20 )
--self.CargoObject:ReSpawnAt( Coordinate, 0 )
end
end
--- Check if the cargo can be Boarded.
-- @param #CARGO_CRATE self
function CARGO_CRATE:CanBoard()
return false
end
--- Check if the cargo can be Unboarded.
-- @param #CARGO_CRATE self
function CARGO_CRATE:CanUnboard()
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_CRATE:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true
end
end
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be Boarded or Loaded.
-- @param #CARGO self
-- @param Core.Point#Coordinate Coordinate
-- @return #boolean true if the Cargo Crate is within the loading radius.
function CARGO_CRATE:IsInLoadRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.NearRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.NearRadius then
return true
end
end
return false
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_CRATE self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_CRATE:GetCoordinate()
--self:F()
return self.CargoObject:GetCoordinate()
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_CRATE self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_CRATE:IsAlive()
local Alive = true
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
if self:IsLoaded() then
Alive = Alive == true and self.CargoCarrier:IsAlive()
else
Alive = Alive == true and self.CargoObject:IsAlive()
end
return Alive
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_CRATE self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_CRATE:RouteTo( Coordinate )
self:F( {Coordinate = Coordinate } )
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier within NearRadius.
-- @param #CARGO_CRATE self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_CRATE:IsNear( CargoCarrier, NearRadius )
self:F( {NearRadius = NearRadius } )
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
end
--- Respawn the CargoGroup.
-- @param #CARGO_CRATE self
function CARGO_CRATE:Respawn()
self:F( { "Respawning crate " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
self:__Reset( -0.1 )
end
end
--- Respawn the CargoGroup.
-- @param #CARGO_CRATE self
function CARGO_CRATE:onafterReset()
self:F( { "Reset crate " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
self.CargoCarrier = nil
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_CRATE self
-- @return #string The transportation method of the Cargo.
function CARGO_CRATE:GetTransportationMethod()
if self:IsLoaded() then
return "for unloading"
else
if self:IsUnLoaded() then
return "for loading"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end

View File

@@ -0,0 +1,770 @@
--- **Cargo** -- Management of grouped cargo logistics, which are based on a @{Group} object.
--
-- ===
--
-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG)
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module CargoGroup
do -- CARGO_GROUP
--- @type CARGO_GROUP
-- @extends Cargo.Cargo#CARGO_REPORTABLE
-- @field Core.Set#SET_CARGO CargoSet The collection of derived CARGO objects.
-- @field #string GroupName The name of the CargoGroup.
--- # CARGO\_GROUP class
--
-- The CARGO\_GROUP class defines a cargo that is represented by a @{Group} object within the simulator.
-- The cargo can be Loaded, UnLoaded, Boarded, UnBoarded to and from Carriers.
--
-- The above cargo classes are used by the AI\_CARGO\_ classes to allow AI groups to transport cargo:
--
-- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC#AI_CARGO_APC} class.
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter#AI_CARGO_HELICOPTER} class.
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Plane#AI_CARGO_PLANE} class.
-- * AI Ships is planned.
--
-- The above cargo classes are also used by the TASK\_CARGO\_ classes to allow human players to transport cargo as part of a tasking:
--
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
--
-- The
--
-- @field #CARGO_GROUP CARGO_GROUP
--
CARGO_GROUP = {
ClassName = "CARGO_GROUP",
}
--- CARGO_GROUP constructor.
-- This make a new CARGO_GROUP from a @{Group} object.
-- It will "ungroup" the group object within the sim, and will create a @{Set} of individual Unit objects.
-- @param #CARGO_GROUP self
-- @param Wrapper.Group#GROUP CargoGroup
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_GROUP
function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius )
local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius ) ) -- #CARGO_GROUP
self:F( { Type, Name, LoadRadius } )
self.CargoSet = SET_CARGO:New()
self.CargoGroup = CargoGroup
self.Grouped = true
self.CargoUnitTemplate = {}
self:SetDeployed( false )
local WeightGroup = 0
self.CargoGroup:Destroy()
local GroupName = CargoGroup:GetName()
self.CargoName = GroupName:match("(.*)~CARGO") or GroupName
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
GroupTemplate.units = {}
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
UnitTemplate.name = UnitTemplate.name .. "#CARGO"
local CargoUnitName = UnitTemplate.name
self.CargoUnitTemplate[CargoUnitName] = UnitTemplate
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
-- And we register the spawned unit as part of the CargoSet.
local Unit = UNIT:Register( CargoUnitName )
--local WeightUnit = Unit:GetDesc().massEmpty
--WeightGroup = WeightGroup + WeightUnit
local CargoUnit = CARGO_UNIT:New( Unit, Type, CargoUnitName, 10 )
self.CargoSet:Add( CargoUnitName, CargoUnit )
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
self:SetWeight( WeightGroup )
self.CargoLimit = 10
self:T( { "Weight Cargo", WeightGroup } )
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- Ungroup the cargo group into individual groups with one unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to ungroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Ungroup()
if self.Grouped == true then
self.Grouped = false
self.CargoGroup:Destroy()
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
if CargoUnit:IsUnLoaded() then
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
--local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName
GroupTemplate.groupId = nil
if CargoUnit:IsUnLoaded() then
GroupTemplate.units = {}
GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
end
end
end
end
--- Regroup the cargo group into one group with multiple unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to regroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Regroup()
self:F("Regroup")
if self.Grouped == false then
self.Grouped = true
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
GroupTemplate.units = {}
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
self:F( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } )
if CargoUnit:IsUnLoaded() then
CargoUnit.CargoObject:Destroy()
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
self:F( { "Regroup", GroupTemplate } )
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
end
end
--- @param #CARGO_GROUP self
-- @param Core.Event#EVENTDATA EventData
function CARGO_GROUP:OnEventCargoDead( EventData )
self:I( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then
Destroyed = true
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
local Cargo = CargoData -- #CARGO
if Cargo:IsAlive() then
Destroyed = false
else
Cargo:Destroyed()
end
end
else
local CarrierName = self.CargoCarrier:GetName()
if CarrierName == EventData.IniDCSUnitName then
MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll()
Destroyed = true
self.CargoCarrier:ClearCargo()
end
end
if Destroyed then
self:Destroyed()
self:E( { "Cargo group destroyed" } )
end
end
--- Enter Boarding State.
-- @param #CARGO_GROUP self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { CargoCarrier.UnitName, From, Event, To } )
local NearRadius = NearRadius or 25
if From == "UnLoaded" then
-- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
function( Cargo, ... )
Cargo:__Board( 1, CargoCarrier, NearRadius, ... )
end, ...
)
self:__Boarding( 1, CargoCarrier, NearRadius, ... )
end
end
--- Enter Loaded State.
-- @param #CARGO_GROUP self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... )
--self:F( { From, Event, To, CargoCarrier, ...} )
if From == "UnLoaded" then
-- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier.
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
Cargo:Load( CargoCarrier )
end
end
--self.CargoObject:Destroy()
self.CargoCarrier = CargoCarrier
self.CargoCarrier:AddCargo( self )
end
--- Leave Boarding State.
-- @param #CARGO_GROUP self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { CargoCarrier.UnitName, From, Event, To } )
local NearRadius = NearRadius or 100
local Boarded = true
local Cancelled = false
local Dead = true
self.CargoSet:Flush()
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
self:T( { Cargo:GetName(), Cargo.current } )
if not Cargo:is( "Loaded" )
and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function.
Boarded = false
end
if Cargo:is( "UnLoaded" ) then
Cancelled = true
end
if not Cargo:is( "Destroyed" ) then
Dead = false
end
end
if not Dead then
if not Cancelled then
if not Boarded then
self:__Boarding( 1, CargoCarrier, NearRadius, ... )
else
self:F("Group Cargo is loaded")
self:__Load( 1, CargoCarrier, ... )
end
else
self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... )
end
else
self:__Destroyed( 1, CargoCarrier, NearRadius, ... )
end
end
--- Enter UnBoarding State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
--self:F( {From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 25
local Timer = 1
if From == "Loaded" then
if self.CargoObject then
self.CargoObject:Destroy()
end
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo, NearRadius )
if not Cargo:IsDestroyed() then
Cargo:__UnBoard( Timer, ToPointVec2, NearRadius )
Timer = Timer + 3
end
end, { NearRadius }
)
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
end
end
--- Leave UnBoarding State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
--self:F( { From, Event, To, ToPointVec2, NearRadius } )
--local NearRadius = NearRadius or 25
local Angle = 180
local Speed = 10
local Distance = 5
if From == "UnBoarding" then
local UnBoarded = true
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
self:T( { Cargo:GetName(), Cargo.current } )
if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then
UnBoarded = false
end
end
if UnBoarded then
return true
else
self:__UnBoarding( 1, ToPointVec2, NearRadius, ... )
end
return false
end
end
--- UnBoard Event.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2 ToPointVec2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... )
--self:F( { From, Event, To, ToPointVec2, NearRadius } )
--local NearRadius = NearRadius or 25
self:__UnLoad( 1, ToPointVec2, ... )
end
--- Enter UnLoaded State.
-- @param #CARGO_GROUP self
-- @param Core.Point#POINT_VEC2
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... )
--self:F( { From, Event, To, ToPointVec2 } )
if From == "Loaded" then
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
function( Cargo )
--Cargo:UnLoad( ToPointVec2 )
local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10)
Cargo:UnLoad( RandomVec2 )
end
)
end
self.CargoCarrier:RemoveCargo( self )
self.CargoCarrier = nil
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_GROUP self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_GROUP:GetCoordinate()
self:F()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo.CargoObject:GetCoordinate()
end
return nil
end
--- Get the x position of the cargo.
-- @param #CARGO_GROUP self
-- @return #number
function CARGO:GetX()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo:GetCoordinate().x
end
return nil
end
--- Get the y position of the cargo.
-- @param #CARGO_GROUP self
-- @return #number
function CARGO:GetY()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo:GetCoordinate().z
end
return nil
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_GROUP self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_GROUP:IsAlive()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo ~= nil
end
--- Get the first alive Cargo Unit of the Cargo Group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetFirstAlive()
local CargoFirstAlive = nil
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if not Cargo:IsDestroyed() then
CargoFirstAlive = Cargo
break
end
end
return CargoFirstAlive
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetCount()
return self.CargoSet:Count()
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetGroup( Cargo )
local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo.CargoObject:GetGroup()
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_GROUP self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_GROUP:RouteTo( Coordinate )
--self:F( {Coordinate = Coordinate } )
-- For each Cargo within the CargoSet, route each object to the Coordinate
self.CargoSet:ForEach(
function( Cargo )
Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 )
end
)
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius.
-- @param #CARGO_GROUP self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_GROUP:IsNear( CargoCarrier, NearRadius )
self:F( {NearRadius = NearRadius } )
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then
self:F( "Near" )
return true
end
end
end
return nil
end
--- Check if Cargo Group is in the radius for the Cargo to be Boarded.
-- @param #CARGO_GROUP self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Group is within the load radius.
function CARGO_GROUP:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
local Distance = 0
if Cargo:IsLoaded() then
Distance = Coordinate:Get2DDistance( Cargo.CargoCarrier:GetCoordinate() )
else
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
end
self:F( { Distance = Distance, LoadRadius = self.LoadRadius } )
if Distance <= self.LoadRadius then
return true
else
return false
end
end
return nil
end
--- Check if Cargo Group is in the report radius.
-- @param #CARGO_GROUP self
-- @param Core.Point#Coordinate Coordinate
-- @return #boolean true if the Cargo Group is within the report radius.
function CARGO_GROUP:IsInReportRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
self:F( { Cargo } )
local Distance = 0
if Cargo:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true
end
end
end
return nil
end
--- Respawn the CargoGroup.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Respawn()
self:F( { "Respawning" } )
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
local Cargo = CargoData -- #CARGO
Cargo:Destroy()
Cargo:SetStartState( "UnLoaded" )
end
-- We iterate through the group template and for each unit in the template, we create a new group with one unit.
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = GroupName .. "#CARGO#" .. UnitID
GroupTemplate.groupId = nil
GroupTemplate.units = {}
GroupTemplate.units[1] = UnitTemplate
local UnitName = UnitTemplate.name .. "#CARGO"
GroupTemplate.units[1].name = UnitTemplate.name .. "#CARGO"
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
-- And we register the spawned unit as part of the CargoSet.
local Unit = UNIT:FindByName( UnitName )
--local WeightUnit = Unit:GetDesc().massEmpty
--WeightGroup = WeightGroup + WeightUnit
local CargoUnit = CARGO_UNIT:New( Unit, Type, UnitName, 10 )
self.CargoSet:Add( UnitName, CargoUnit )
end
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
end
--- Signal a flare at the position of the CargoGroup.
-- @param #CARGO_GROUP self
-- @param Utilities.Utils#FLARECOLOR FlareColor
function CARGO_GROUP:Flare( FlareColor )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
if Cargo then
Cargo:Flare( FlareColor )
end
end
--- Smoke the CargoGroup.
-- @param #CARGO_GROUP self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The color of the smoke.
-- @param #number Radius The radius of randomization around the center of the first element of the CargoGroup.
function CARGO_GROUP:Smoke( SmokeColor, Radius )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
if Cargo then
Cargo:Smoke( SmokeColor, Radius )
end
end
--- Check if the first element of the CargoGroup is the given @{Zone}.
-- @param #CARGO self
-- @param Core.Zone#ZONE_BASE Zone
-- @return #boolean **true** if the first element of the CargoGroup is in the Zone
-- @return #boolean **false** if there is no element of the CargoGroup in the Zone.
function CARGO_GROUP:IsInZone( Zone )
--self:F( { Zone } )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
if Cargo then
return Cargo:IsInZone( Zone )
end
return nil
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_GROUP self
-- @return #string The transportation method of the Cargo.
function CARGO_GROUP:GetTransportationMethod()
if self:IsLoaded() then
return "for unboarding"
else
if self:IsUnLoaded() then
return "for boarding"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end -- CARGO_GROUP

View File

@@ -0,0 +1,267 @@
--- **Cargo** -- Management of single cargo crates, which are based on a @{Static} object. The cargo can only be slingloaded.
--
-- ===
--
-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG)
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module CargoCrate
do -- CARGO_SLINGLOAD
--- Models the behaviour of cargo crates, which can only be slingloaded.
-- @type CARGO_SLINGLOAD
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- # CARGO\_CRATE class, extends @{#CARGO_REPRESENTABLE}
--
-- The CARGO\_CRATE class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
--
-- ===
--
-- @field #CARGO_SLINGLOAD
CARGO_SLINGLOAD = {
ClassName = "CARGO_SLINGLOAD"
}
--- CARGO_SLINGLOAD Constructor.
-- @param #CARGO_SLINGLOAD self
-- @param Wrapper.Static#STATIC CargoStatic
-- @param #string Type
-- @param #string Name
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_SLINGLOAD
function CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoStatic, Type, Name, nil, LoadRadius, NearRadius ) ) -- #CARGO_SLINGLOAD
self:F( { Type, Name, NearRadius } )
self.CargoObject = CargoStatic
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
self:HandleEvent( EVENTS.Dead, self.OnEventCargoDead )
self:HandleEvent( EVENTS.Crash, self.OnEventCargoDead )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self.OnEventCargoDead )
self:SetEventPriority( 4 )
return self
end
--- @param #CARGO_SLINGLOAD self
-- @param Core.Event#EVENTDATA EventData
function CARGO_SLINGLOAD:OnEventCargoDead( EventData )
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() then
if self.CargoObject:GetName() == EventData.IniUnitName then
if not self.NoDestroy then
Destroyed = true
end
end
end
if Destroyed then
self:I( { "Cargo crate destroyed: " .. self.CargoObject:GetName() } )
self:Destroyed()
end
end
--- Check if the cargo can be Slingloaded.
-- @param #CARGO self
function CARGO:CanSlingload()
return true
end
--- Check if the cargo can be Boarded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanBoard()
return false
end
--- Check if the cargo can be Unboarded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanUnboard()
return false
end
--- Check if the cargo can be Loaded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanLoad()
return false
end
--- Check if the cargo can be Unloaded.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:CanUnload()
return false
end
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_SLINGLOAD:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.LoadRadius then
return true
end
end
return false
end
--- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Slingload is within the loading radius.
function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.NearRadius then
return true
end
end
return false
end
--- Get the current Coordinate of the CargoGroup.
-- @param #CARGO_SLINGLOAD self
-- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup.
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_SLINGLOAD:GetCoordinate()
--self:F()
return self.CargoObject:GetCoordinate()
end
--- Check if the CargoGroup is alive.
-- @param #CARGO_SLINGLOAD self
-- @return #boolean true if the CargoGroup is alive.
-- @return #boolean false if the CargoGroup is dead.
function CARGO_SLINGLOAD:IsAlive()
local Alive = true
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
if self:IsLoaded() then
Alive = Alive == true and self.CargoCarrier:IsAlive()
else
Alive = Alive == true and self.CargoObject:IsAlive()
end
return Alive
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#COORDINATE Coordinate
function CARGO_SLINGLOAD:RouteTo( Coordinate )
--self:F( {Coordinate = Coordinate } )
end
--- Check if Cargo is near to the Carrier.
-- The Cargo is near to the Carrier within NearRadius.
-- @param #CARGO_SLINGLOAD self
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param #number NearRadius
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_SLINGLOAD:IsNear( CargoCarrier, NearRadius )
--self:F( {NearRadius = NearRadius } )
return self:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
end
--- Respawn the CargoGroup.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:Respawn()
--self:F( { "Respawning slingload " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawn() -- A cargo destroy crates a DEAD event.
self:__Reset( -0.1 )
end
end
--- Respawn the CargoGroup.
-- @param #CARGO_SLINGLOAD self
function CARGO_SLINGLOAD:onafterReset()
--self:F( { "Reset slingload " .. self:GetName() } )
-- Respawn the group...
if self.CargoObject then
self:SetDeployed( false )
self:SetStartState( "UnLoaded" )
self.CargoCarrier = nil
-- Cargo objects are added to the _DATABASE and SET_CARGO objects.
_EVENTDISPATCHER:CreateEventNewCargo( self )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_SLINGLOAD self
-- @return #string The transportation method of the Cargo.
function CARGO_SLINGLOAD:GetTransportationMethod()
if self:IsLoaded() then
return "for sling loading"
else
if self:IsUnLoaded() then
return "for sling loading"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end

View File

@@ -0,0 +1,398 @@
--- **Cargo** -- Management of single cargo logistics, which are based on a @{Unit} object.
--
-- ===
--
-- ![Banner Image](..\Presentations\CARGO\Dia1.JPG)
--
-- ===
--
-- ### [Demo Missions]()
--
-- ### [YouTube Playlist]()
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
-- ===
--
-- @module CargoUnit
do -- CARGO_UNIT
--- Models CARGO in the form of units, which can be boarded, unboarded, loaded, unloaded.
-- @type CARGO_UNIT
-- @extends Cargo.Cargo#CARGO_REPRESENTABLE
--- # CARGO\_UNIT class, extends @{#CARGO_REPRESENTABLE}
--
-- The CARGO\_UNIT class defines a cargo that is represented by a UNIT object within the simulator, and can be transported by a carrier.
-- Use the event functions as described above to Load, UnLoad, Board, UnBoard the CARGO\_UNIT objects to and from carriers.
--
-- ===
--
-- @field #CARGO_UNIT CARGO_UNIT
--
CARGO_UNIT = {
ClassName = "CARGO_UNIT"
}
--- CARGO_UNIT Constructor.
-- @param #CARGO_UNIT self
-- @param Wrapper.Unit#UNIT CargoUnit
-- @param #string Type
-- @param #string Name
-- @param #number Weight
-- @param #number LoadRadius (optional)
-- @param #number NearRadius (optional)
-- @return #CARGO_UNIT
function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT
self:I( { Type, Name, Weight, NearRadius } )
self:T( CargoUnit )
self.CargoObject = CargoUnit
self:T( self.ClassName )
self:SetEventPriority( 5 )
return self
end
--- Enter UnBoarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 25
local Angle = 180
local Speed = 60
local DeployDistance = 9
local RouteDistance = 60
if From == "Loaded" then
if not self:IsDestroyed() then
local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE
if CargoCarrier:IsAlive() then
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading )
-- if there is no ToPointVec2 given, then use the CargoRoutePointVec2
local FromDirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3( ToPointVec2 or CargoRoutePointVec2 )
local FromAngle = CargoCarrierPointVec2:GetAngleDegrees(FromDirectionVec3)
local FromPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, FromAngle )
--local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( 10, 5 )
ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, DeployDistance )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading )
self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } )
self.CargoCarrier = nil
local Points = {}
-- From
Points[#Points+1] = FromPointVec2:WaypointGround( Speed, "Vee" )
-- To
Points[#Points+1] = ToPointVec2:WaypointGround( Speed, "Vee" )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 1 )
self:__UnBoarding( 1, ToPointVec2, NearRadius )
end
else
-- the Carrier is dead. This cargo is dead too!
self:Destroyed()
end
end
end
end
--- Leave UnBoarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 100
local Angle = 180
local Speed = 10
local Distance = 5
if From == "UnBoarding" then
--if self:IsNear( ToPointVec2, NearRadius ) then
return true
--else
--self:__UnBoarding( 1, ToPointVec2, NearRadius )
--end
--return false
end
end
--- UnBoard Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2 ToPointVec2
function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius )
self:F( { From, Event, To, ToPointVec2, NearRadius } )
NearRadius = NearRadius or 100
self.CargoInAir = self.CargoObject:InAir()
self:T( self.CargoInAir )
-- Only unboard the cargo when the carrier is not in the air.
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
if not self.CargoInAir then
end
self:__UnLoad( 1, ToPointVec2, NearRadius )
end
--- Enter UnLoaded State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Core.Point#POINT_VEC2
function CARGO_UNIT:onenterUnLoaded( From, Event, To, ToPointVec2 )
self:F( { ToPointVec2, From, Event, To } )
local Angle = 180
local Speed = 10
local Distance = 5
if From == "Loaded" then
local StartPointVec2 = self.CargoCarrier:GetPointVec2()
local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployCoord = StartPointVec2:Translate( Distance, CargoDeployHeading )
ToPointVec2 = ToPointVec2 or COORDINATE:New( CargoDeployCoord.x, CargoDeployCoord.z )
-- Respawn the group...
if self.CargoObject then
self.CargoObject:ReSpawnAt( ToPointVec2, 0 )
self.CargoCarrier = nil
end
end
if self.OnUnLoadedCallBack then
self.OnUnLoadedCallBack( self, unpack( self.OnUnLoadedParameters ) )
self.OnUnLoadedCallBack = nil
end
end
--- Board Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... )
self:F( { From, Event, To, CargoCarrier, NearRadius } )
local NearRadius = NearRadius or 25
self.CargoInAir = self.CargoObject:InAir()
local Desc = self.CargoObject:GetDesc()
local MaxSpeed = Desc.speedMaxOffRoad
local TypeName = Desc.typeName
self:T( self.CargoInAir )
-- Only move the group to the carrier when the cargo is not in the air
-- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
if not self.CargoInAir then
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
self:Load( CargoCarrier, NearRadius, ... )
else
if MaxSpeed and MaxSpeed == 0 or TypeName and TypeName == "Stinger comm" then
self:Load( CargoCarrier, NearRadius, ... )
else
local Speed = 90
local Angle = 180
local Distance = 5
NearRadius = NearRadius or 25
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 2 )
self:__Boarding( -1, CargoCarrier, NearRadius )
self.RunCount = 0
end
end
end
end
--- Boarding Event.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Client#CLIENT CargoCarrier
-- @param #number NearRadius
function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } )
if CargoCarrier and CargoCarrier:IsAlive() and self.CargoObject and self.CargoObject:IsAlive() then
if CargoCarrier:InAir() == false then
if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then
self:__Load( 1, CargoCarrier, ... )
else
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
self.RunCount = self.RunCount + 1
if self.RunCount >= 40 then
self.RunCount = 0
local Speed = 90
local Angle = 180
local Distance = 5
NearRadius = NearRadius or 25
local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2()
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 0.2 )
end
end
else
self.CargoObject:MessageToGroup( "Cancelling Boarding... Get back on the ground!", 5, CargoCarrier:GetGroup(), self:GetName() )
self:CancelBoarding( CargoCarrier, NearRadius, ... )
self.CargoObject:SetCommand( self.CargoObject:CommandStopRoute( true ) )
end
else
self:E("Something is wrong")
end
end
--- Enter Boarding State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... )
--self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } )
local Speed = 90
local Angle = 180
local Distance = 5
local NearRadius = NearRadius or 25
if From == "UnLoaded" or From == "Boarding" then
end
end
--- Loaded State.
-- @param #CARGO_UNIT self
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_UNIT:onenterLoaded( From, Event, To, CargoCarrier )
self:F( { From, Event, To, CargoCarrier } )
self.CargoCarrier = CargoCarrier
-- Only destroy the CargoObject is if there is a CargoObject (packages don't have CargoObjects).
if self.CargoObject then
self:T("Destroying")
self.CargoObject:Destroy()
--self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 )
end
end
--- Get the transportation method of the Cargo.
-- @param #CARGO_UNIT self
-- @return #string The transportation method of the Cargo.
function CARGO_UNIT:GetTransportationMethod()
if self:IsLoaded() then
return "for unboarding"
else
if self:IsUnLoaded() then
return "for boarding"
else
if self:IsDeployed() then
return "delivered"
end
end
end
return ""
end
end -- CARGO_UNIT

View File

@@ -645,6 +645,22 @@ function BASE:CreateEventCrash( EventTime, Initiator )
world.onEvent( Event )
end
--- Creation of a Dead Event.
-- @param #BASE self
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.
-- @param Dcs.DCSWrapper.Object#Object Initiator The initiating object of the event.
function BASE:CreateEventDead( EventTime, Initiator )
self:F( { EventTime, Initiator } )
local Event = {
id = world.event.S_EVENT_DEAD,
time = EventTime,
initiator = Initiator,
}
world.onEvent( Event )
end
--- Creation of a Takeoff Event.
-- @param #BASE self
-- @param Dcs.DCSTypes#Time EventTime The time stamp of the event.

File diff suppressed because it is too large Load Diff

View File

@@ -58,6 +58,7 @@ DATABASE = {
ZONENAMES = {},
HITS = {},
DESTROYS = {},
ZONES = {},
}
local _DATABASECoalition =
@@ -242,35 +243,146 @@ function DATABASE:FindAirbase( AirbaseName )
return AirbaseFound
end
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
do -- Zones
--- Finds a @{Zone} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Core.Zone#ZONE_BASE The found ZONE.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
end
--- Adds a @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @param Core.Zone#ZONE_BASE Zone The zone.
function DATABASE:AddZone( ZoneName, Zone )
if not self.ZONES[ZoneName] then
self.ZONES[ZoneName] = Zone
end
end
--- Deletes a @{Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteZone( ZoneName )
self.ZONES[ZoneName] = nil
end
--- Finds an @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName
-- @return Core.Zone#ZONE_BASE The found @{Zone}.
function DATABASE:FindZone( ZoneName )
local ZoneFound = self.ZONES[ZoneName]
return ZoneFound
end
end
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
do -- cargo
self.CARGOS[CargoName] = nil
end
--- Adds a Cargo based on the Cargo Name in the DATABASE.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:AddCargo( Cargo )
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
if not self.CARGOS[Cargo.Name] then
self.CARGOS[Cargo.Name] = Cargo
end
end
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
--- Deletes a Cargo from the DATABASE based on the Cargo Name.
-- @param #DATABASE self
-- @param #string CargoName The name of the airbase
function DATABASE:DeleteCargo( CargoName )
self.CARGOS[CargoName] = nil
end
--- Finds an CARGO based on the CargoName.
-- @param #DATABASE self
-- @param #string CargoName
-- @return Wrapper.Cargo#CARGO The found CARGO.
function DATABASE:FindCargo( CargoName )
local CargoFound = self.CARGOS[CargoName]
return CargoFound
end
--- Checks if the Template name has a ~CARGO tag.
-- If yes, the group is a cargo.
-- @param #DATABASE self
-- @param #string TemplateName
-- @return #boolean
function DATABASE:IsCargo( TemplateName )
TemplateName = env.getValueDictByKey( TemplateName )
local Cargo = TemplateName:match( "~(CARGO)" )
return Cargo and Cargo == "CARGO"
end
--- Private method that registers new Static Templates within the DATABASE Object.
-- @param #DATABASE self
-- @return #DATABASE self
function DATABASE:RegisterCargos()
for CargoGroupName, CargoGroup in pairs( self.GROUPS ) do
if self:IsCargo( CargoGroupName ) then
local CargoInfo = CargoGroupName:match("~CARGO(.*)")
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
local CargoName = CargoGroupName:match("(.*)~CARGO")
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
self:F({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
end
end
for CargoStaticName, CargoStatic in pairs( self.STATICS ) do
if self:IsCargo( CargoStaticName ) then
local CargoInfo = CargoStaticName:match("~CARGO(.*)")
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
local CargoName = CargoStaticName:match("(.*)~CARGO")
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
if Category == "SLING" then
self:F({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
else
if Category == "CRATE" then
self:F({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
end
end
end
end
end
end -- cargo
--- Finds a CLIENT based on the ClientName.
-- @param #DATABASE self
@@ -450,8 +562,6 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name )
local TraceTable = {}
if not self.Templates.Groups[GroupTemplateName] then
self.Templates.Groups[GroupTemplateName] = {}
self.Templates.Groups[GroupTemplateName].Status = nil
@@ -475,18 +585,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide
self.Templates.Groups[GroupTemplateName].CountryID = CountryID
TraceTable[#TraceTable+1] = "Group"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].GroupName
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CoalitionID
TraceTable[#TraceTable+1] = "Category"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CategoryID
TraceTable[#TraceTable+1] = "Country"
TraceTable[#TraceTable+1] = self.Templates.Groups[GroupTemplateName].CountryID
TraceTable[#TraceTable+1] = "Units"
local UnitNames = {}
for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do
@@ -510,10 +609,16 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category
self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate
end
TraceTable[#TraceTable+1] = self.Templates.Units[UnitTemplate.name].UnitName
UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName
end
self:E( TraceTable )
self:I( { Group = self.Templates.Groups[GroupTemplateName].GroupName,
Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID,
Category = self.Templates.Groups[GroupTemplateName].CategoryID,
Country = self.Templates.Groups[GroupTemplateName].CountryID,
Units = UnitNames
}
)
end
function DATABASE:GetGroupTemplate( GroupName )
@@ -530,8 +635,6 @@ end
-- @return #DATABASE self
function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID )
local TraceTable = {}
local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name)
self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {}
@@ -547,28 +650,22 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category
self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID
self.Templates.Statics[StaticTemplateName].CountryID = CountryID
self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName,
Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID,
Category = self.Templates.Statics[StaticTemplateName].CategoryID,
Country = self.Templates.Statics[StaticTemplateName].CountryID
}
)
TraceTable[#TraceTable+1] = "Static"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].StaticName
self:AddStatic( StaticTemplateName )
TraceTable[#TraceTable+1] = "Coalition"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CoalitionID
TraceTable[#TraceTable+1] = "Category"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CategoryID
TraceTable[#TraceTable+1] = "Country"
TraceTable[#TraceTable+1] = self.Templates.Statics[StaticTemplateName].CountryID
self:E( TraceTable )
end
--- @param #DATABASE self
function DATABASE:GetStaticUnitTemplate( StaticName )
local StaticTemplate = self.Templates.Statics[StaticName].UnitTemplate
StaticTemplate.SpawnCoalitionID = self.Templates.Statics[StaticName].CoalitionID
StaticTemplate.SpawnCategoryID = self.Templates.Statics[StaticName].CategoryID
StaticTemplate.SpawnCountryID = self.Templates.Statics[StaticName].CountryID
return StaticTemplate
return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID
end
@@ -703,7 +800,7 @@ function DATABASE:_RegisterAirbases()
local DCSAirbaseName = DCSAirbase:getName()
self:E( { "Register Airbase:", DCSAirbaseName } )
self:E( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } )
self:AddAirbase( DCSAirbaseName )
end
end
@@ -734,7 +831,7 @@ function DATABASE:_EventOnBirth( Event )
Event.IniGroup = self:FindGroup( Event.IniDCSGroupName )
local PlayerName = Event.IniUnit:GetPlayerName()
self:E( { "PlayerName:", PlayerName } )
if PlayerName ~= "" then
if PlayerName then
self:E( { "Player Joined:", PlayerName } )
if not self.PLAYERS[PlayerName] then
self:AddPlayer( Event.IniUnitName, PlayerName )
@@ -803,7 +900,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event )
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName()
if self.PLAYERS[PlayerName] then
if PlayerName and self.PLAYERS[PlayerName] then
self:E( { "Player Left:", PlayerName } )
local Settings = SETTINGS:Set( PlayerName )
Settings:RemovePlayerMenu( Event.IniUnit )
@@ -1025,7 +1122,6 @@ function DATABASE:_RegisterTemplates()
local CoalitionSide = coalition.side[string.upper(CoalitionName)]
----------------------------------------------
-- build nav points DB
self.Navpoints[CoalitionName] = {}
if coa_data.nav_points then --navpoints
@@ -1040,8 +1136,9 @@ function DATABASE:_RegisterTemplates()
self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0
self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y
end
end
end
end
-------------------------------------------------
if coa_data.country then --there is a country table
for cntry_id, cntry_data in pairs(coa_data.country) do
@@ -1097,6 +1194,8 @@ function DATABASE:_RegisterTemplates()
for ZoneID, ZoneData in pairs( env.mission.triggers.zones ) do
local ZoneName = ZoneData.name
self.ZONENAMES[ZoneName] = ZoneName
self:AddZone( ZoneName, ZONE:New( ZoneName ) )
self:I( "Added ZONE " .. ZoneName )
end
return self

View File

@@ -174,6 +174,7 @@
EVENT = {
ClassName = "EVENT",
ClassID = 0,
MissionEnd = false,
}
world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
@@ -748,8 +749,13 @@ function EVENT:onEvent( Event )
if self and
self.Events and
self.Events[Event.id] and
self.MissionEnd == false and
( Event.initiator ~= nil or ( Event.initiator == nil and Event.id ~= EVENTS.PlayerLeaveUnit ) ) then
if Event.id and Event.id == EVENTS.MissionEnd then
self.MissionEnd = true
end
if Event.initiator then
Event.IniObjectCategory = Event.initiator:getCategory()
@@ -788,6 +794,16 @@ function EVENT:onEvent( Event )
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.CARGO then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
Event.IniUnitName = Event.IniDCSUnitName
Event.IniUnit = CARGO:FindByName( Event.IniDCSUnitName )
Event.IniCoalition = Event.IniDCSUnit:getCoalition()
Event.IniCategory = Event.IniDCSUnit:getDesc().category
Event.IniTypeName = Event.IniDCSUnit:getTypeName()
end
if Event.IniObjectCategory == Object.Category.SCENERY then
Event.IniDCSUnit = Event.initiator
Event.IniDCSUnitName = Event.IniDCSUnit:getName()
@@ -1021,6 +1037,16 @@ function EVENT:onEvent( Event )
end
end
end
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_CARGOs.
-- To prevent this from happening, the Cargo object has a flag NoDestroy.
-- When true, the SET_CARGO won't Remove the Cargo object from the set.
-- But we need to switch that flag off after the event handlers have been called.
if Event.id == EVENTS.DeleteCargo then
Event.Cargo.NoDestroy = nil
end
else
self:T( { EventMeta.Text, Event } )
end

View File

@@ -337,7 +337,7 @@ do -- FSM
--- Creates a new FSM object.
-- @param #FSM self
-- @return #FSM
function FSM:New( FsmT )
function FSM:New()
-- Inherits from BASE
self = BASE:Inherit( self, BASE:New() )
@@ -441,6 +441,8 @@ do -- FSM
-- @return #table
function FSM:GetProcesses()
self:F( { Processes = self._Processes } )
return self._Processes or {}
end
@@ -455,6 +457,18 @@ do -- FSM
error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" )
end
function FSM:SetProcess( From, Event, Fsm )
for ProcessID, Process in pairs( self:GetProcesses() ) do
if Process.From == From and Process.Event == Event then
Process.fsm = Fsm
return true
end
end
error( "Sub-Process from state " .. From .. " with event " .. Event .. " not found!" )
end
--- Adds an End state.
function FSM:AddEndState( State )
@@ -557,8 +571,9 @@ do -- FSM
end
function FSM:_call_handler( handler, params, EventName )
function FSM:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
local ErrorHandler = function( errmsg )
env.info( "Error in SCHEDULER function:" .. errmsg )
@@ -569,83 +584,117 @@ do -- FSM
return errmsg
end
if self[handler] then
self:T2( "Calling " .. handler )
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
return Value
end
end
--- @param #FSM self
function FSM._handler( self, EventName, ... )
local Can, to = self:can( EventName )
local Can, To = self:can( EventName )
if to == "*" then
to = self.current
if To == "*" then
To = self.current
end
if Can then
local from = self.current
local params = { from, EventName, to, ... }
local From = self.current
local Params = { From, EventName, To, ... }
if self.Controllable then
self:T( "FSM Transition for " .. self.Controllable.ControllableName .. " :" .. self.current .. " --> " .. EventName .. " --> " .. to )
if self["onleave".. From] or
self["OnLeave".. From] or
self["onbefore".. EventName] or
self["OnBefore".. EventName] or
self["onafter".. EventName] or
self["OnAfter".. EventName] or
self["onenter".. To] or
self["OnEnter".. To]
then
if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName )
return false
else
if self:_call_handler( "OnBefore", EventName, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnBefore" .. EventName )
return false
else
if self:_call_handler( "onleave", From, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onleave" .. From )
return false
else
if self:_call_handler( "OnLeave", From, Params, EventName ) == false then
self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** OnLeave" .. From )
return false
end
end
end
end
else
self:T( "FSM Transition:" .. self.current .. " --> " .. EventName .. " --> " .. to )
local ClassName = self:GetClassName()
if ClassName == "FSM" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To )
end
if ClassName == "FSM_TASK" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.TaskName )
end
if ClassName == "FSM_CONTROLLABLE" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
end
if ClassName == "FSM_PROCESS" then
self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable.ControllableName .. " *** " )
end
end
if ( self:_call_handler("onbefore" .. EventName, params, EventName ) == false )
or ( self:_call_handler("OnBefore" .. EventName, params, EventName ) == false )
or ( self:_call_handler("onleave" .. from, params, EventName ) == false )
or ( self:_call_handler("OnLeave" .. from, params, EventName ) == false ) then
self:T( "Cancel Transition" )
return false
end
self.current = to
self.current = To
local execute = true
local subtable = self:_gosub( from, EventName )
local subtable = self:_gosub( From, EventName )
for _, sub in pairs( subtable ) do
--if sub.nextevent then
-- self:F2( "nextevent = " .. sub.nextevent )
-- self[sub.nextevent]( self )
--end
self:T( "calling sub start event: " .. sub.StartEvent )
self:T( "*** FSM *** Sub *** " .. sub.StartEvent )
sub.fsm.fsmparent = self
sub.fsm.ReturnEvents = sub.ReturnEvents
sub.fsm[sub.StartEvent]( sub.fsm )
execute = false
end
local fsmparent, Event = self:_isendstate( to )
local fsmparent, Event = self:_isendstate( To )
if fsmparent and Event then
self:F2( { "end state: ", fsmparent, Event } )
self:_call_handler("onenter" .. to, params, EventName )
self:_call_handler("OnEnter" .. to, params, EventName )
self:_call_handler("onafter" .. EventName, params, EventName )
self:_call_handler("OnAfter" .. EventName, params, EventName )
self:_call_handler("onstatechange", params, EventName )
self:T( "*** FSM *** End *** " .. Event )
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
self:_call_handler("onstate", "change", Params, EventName )
fsmparent[Event]( fsmparent )
execute = false
end
if execute then
self:_call_handler("onafter", EventName, Params, EventName )
self:_call_handler("OnAfter", EventName, Params, EventName )
-- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute!
--if from ~= to then
self:_call_handler("onenter" .. to, params, EventName )
self:_call_handler("OnEnter" .. to, params, EventName )
self:_call_handler("onenter", To, Params, EventName )
self:_call_handler("OnEnter", To, Params, EventName )
--end
self:_call_handler("onafter" .. EventName, params, EventName )
self:_call_handler("OnAfter" .. EventName, params, EventName )
self:_call_handler("onstatechange", params, EventName )
self:_call_handler("onstate", "change", Params, EventName )
end
else
self:T( "Cannot execute transition." )
self:T( { From = self.current, Event = EventName, To = to, Can = Can } )
self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " )
end
return nil
@@ -691,17 +740,16 @@ do -- FSM
function FSM:_isendstate( Current )
local FSMParent = self.fsmparent
if FSMParent and self.endstates[Current] then
self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
--self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } )
FSMParent.current = Current
local ParentFrom = FSMParent.current
self:T( ParentFrom )
self:T( self.ReturnEvents )
--self:T( { ParentFrom, self.ReturnEvents } )
local Event = self.ReturnEvents[Current]
self:T( { ParentFrom, Event, self.ReturnEvents } )
--self:T( { Event } )
if Event then
return FSMParent, Event
else
self:T( { "Could not find parent event name for state ", ParentFrom } )
--self:T( { "Could not find parent event name for state ", ParentFrom } )
end
end
@@ -724,6 +772,10 @@ do -- FSM
return self.current
end
function FSM:GetCurrentState()
return self.current
end
function FSM:Is( State )
return self.current == State
@@ -769,10 +821,10 @@ do -- FSM_CONTROLLABLE
-- @param #table FSMT Finite State Machine Table
-- @param Wrapper.Controllable#CONTROLLABLE Controllable (optional) The CONTROLLABLE object that the FSM_CONTROLLABLE governs.
-- @return #FSM_CONTROLLABLE
function FSM_CONTROLLABLE:New( FSMT, Controllable )
function FSM_CONTROLLABLE:New( Controllable )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM:New( FSMT ) ) -- Core.Fsm#FSM_CONTROLLABLE
local self = BASE:Inherit( self, FSM:New() ) -- Core.Fsm#FSM_CONTROLLABLE
if Controllable then
self:SetControllable( Controllable )
@@ -855,7 +907,9 @@ do -- FSM_CONTROLLABLE
return self.Controllable
end
function FSM_CONTROLLABLE:_call_handler( handler, params, EventName )
function FSM_CONTROLLABLE:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
local ErrorHandler = function( errmsg )
@@ -868,7 +922,7 @@ do -- FSM_CONTROLLABLE
end
if self[handler] then
self:F3( "Calling " .. handler )
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** TaskUnit: " .. self.Controllable:GetName() )
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, unpack( params ) ) end, ErrorHandler )
return Value
@@ -915,7 +969,9 @@ do -- FSM_PROCESS
self:T( "No Initialisation" )
end
function FSM_PROCESS:_call_handler( handler, params, EventName )
function FSM_PROCESS:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
local ErrorHandler = function( errmsg )
@@ -928,9 +984,13 @@ do -- FSM_PROCESS
end
if self[handler] then
self:F3( "Calling " .. handler )
if handler ~= "onstatechange" then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.Task:GetName() .. ", TaskUnit: " .. self.Controllable:GetName() )
end
self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler )
if self.Controllable and self.Controllable:IsAlive() == true then
local Result, Value = xpcall( function() return self[handler]( self, self.Controllable, self.Task, unpack( params ) ) end, ErrorHandler )
end
return Value
--return self[handler]( self, self.Controllable, unpack( params ) )
end
@@ -1037,21 +1097,21 @@ do -- FSM_PROCESS
-- TODO: Need to check and fix that an FSM_PROCESS is only for a UNIT. Not for a GROUP.
--- Send a message of the @{Task} to the Group of the Unit.
-- @param #FSM_PROCESS self
function FSM_PROCESS:Message( Message )
self:F( { Message = Message } )
-- @param #FSM_PROCESS self
function FSM_PROCESS:Message( Message )
self:F( { Message = Message } )
local CC = self:GetCommandCenter()
local TaskGroup = self.Controllable:GetGroup()
local CC = self:GetCommandCenter()
local TaskGroup = self.Controllable:GetGroup()
local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit
PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets.
local Callsign = self.Controllable:GetCallsign()
local Prefix = Callsign and " @ " .. Callsign .. PlayerName or ""
local PlayerName = self.Controllable:GetPlayerName() -- Only for a unit
PlayerName = PlayerName and " (" .. PlayerName .. ")" or "" -- If PlayerName is nil, then keep it nil, otherwise add brackets.
local Callsign = self.Controllable:GetCallsign()
local Prefix = Callsign and " @ " .. Callsign .. PlayerName or ""
Message = Prefix .. ": " .. Message
CC:MessageToGroup( Message, TaskGroup )
end
Message = Prefix .. ": " .. Message
CC:MessageToGroup( Message, TaskGroup )
end
@@ -1072,14 +1132,16 @@ end
return self
end
function FSM_PROCESS:onenterAssigned( ProcessUnit )
self:T( "Assign" )
-- function FSM_PROCESS:onenterAssigned( ProcessUnit, Task, From, Event, To )
--
-- if From( "Planned" ) then
-- self:T( "*** FSM *** Assign *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
-- self.Task:Assign()
-- end
-- end
self.Task:Assign()
end
function FSM_PROCESS:onenterFailed( ProcessUnit )
self:T( "Failed" )
function FSM_PROCESS:onenterFailed( ProcessUnit, Task, From, Event, To )
self:T( "*** FSM *** Failed *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
self.Task:Fail()
end
@@ -1091,14 +1153,17 @@ end
-- @param #string Event
-- @param #string From
-- @param #string To
function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To, Dummy )
self:T( { ProcessUnit:GetName(), From, Event, To, Dummy, self:IsTrace() } )
function FSM_PROCESS:onstatechange( ProcessUnit, Task, From, Event, To )
if self:IsTrace() then
--MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
if From ~= To then
self:T( "*** FSM *** Change *** " .. Task:GetName() .. "/" .. ProcessUnit:GetName() .. " *** " .. From .. " --> " .. Event .. " --> " .. To )
end
self:T( { Scores = self._Scores, To = To } )
-- if self:IsTrace() then
-- MESSAGE:New( "@ Process " .. self:GetClassNameAndID() .. " : " .. Event .. " changed to state " .. To, 2 ):ToAll()
-- self:F2( { Scores = self._Scores, To = To } )
-- end
-- TODO: This needs to be reworked with a callback functions allocated within Task, and set within the mission script from the Task Objects...
if self._Scores[To] then
@@ -1133,22 +1198,23 @@ do -- FSM_TASK
--- Creates a new FSM_TASK object.
-- @param #FSM_TASK self
-- @param #table FSMT
-- @param Tasking.Task#TASK Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string TaskName The name of the task.
-- @return #FSM_TASK
function FSM_TASK:New( FSMT )
function FSM_TASK:New( TaskName )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( FSMT ) ) -- Core.Fsm#FSM_TASK
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- Core.Fsm#FSM_TASK
self["onstatechange"] = self.OnStateChange
self.TaskName = TaskName
return self
end
function FSM_TASK:_call_handler( handler, params, EventName )
function FSM_TASK:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
if self[handler] then
self:T( "Calling " .. handler )
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] .. " *** Task: " .. self.TaskName )
self._EventSchedules[EventName] = nil
return self[handler]( self, unpack( params ) )
end
@@ -1210,9 +1276,10 @@ do -- FSM_SET
return self.Controllable
end
function FSM_SET:_call_handler( handler, params, EventName )
function FSM_SET:_call_handler( step, trigger, params, EventName )
local handler = step .. trigger
if self[handler] then
self:T( "Calling " .. handler )
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] )
self._EventSchedules[EventName] = nil
return self[handler]( self, self.Set, unpack( params ) )
end

View File

@@ -213,6 +213,7 @@ do -- MENU_BASE
self.Menus = {}
self.MenuCount = 0
self.MenuTime = timer.getTime()
self.MenuRemoveParent = false
if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
@@ -226,15 +227,31 @@ do -- MENU_BASE
if self.ParentMenu then
self.ParentMenu.Menus = self.ParentMenu.Menus or {}
self.ParentMenu.Menus[MenuText] = Menu
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount + 1
end
end
function MENU_BASE:ClearParentMenu( MenuText )
if self.ParentMenu and self.ParentMenu.Menus[MenuText] then
self.ParentMenu.Menus[MenuText] = nil
self.ParentMenu.MenuCount = self.ParentMenu.MenuCount - 1
if self.ParentMenu.MenuCount == 0 then
--self.ParentMenu:Remove()
end
end
end
--- Sets a @{Menu} to remove automatically the parent menu when the menu removed is the last child menu of that parent @{Menu}.
-- @param #MENU_BASE self
-- @param #boolean RemoveParent If true, the parent menu is automatically removed when this menu is the last child menu of that parent @{Menu}.
-- @return #MENU_BASE
function MENU_BASE:SetRemoveParent( RemoveParent )
--self:F( { RemoveParent } )
self.MenuRemoveParent = RemoveParent
return self
end
--- Gets a @{Menu} from a parent @{Menu}
-- @param #MENU_BASE self
-- @param #string MenuText The text of the child menu.
@@ -900,8 +917,8 @@ do
self:RemoveSubMenus( MenuTime, MenuTag )
if not MenuTime or self.MenuTime ~= MenuTime then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
end
MENU_INDEX:ClearGroupMenu( self.Group, Path )
@@ -992,8 +1009,8 @@ do
if GroupMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
end
MENU_INDEX:ClearGroupMenu( self.Group, Path )
@@ -1133,8 +1150,8 @@ do
self:RemoveSubMenus( MenuTime, MenuTag )
if not MenuTime or self.MenuTime ~= MenuTime then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
end
MENU_INDEX:ClearGroupMenu( self.Group, Path )
@@ -1244,8 +1261,8 @@ do
if GroupMenu == self then
if not MenuTime or self.MenuTime ~= MenuTime then
if ( not MenuTag ) or ( MenuTag and self.MenuTag and MenuTag == self.MenuTag ) then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
if self.MenuPath ~= nil then
self:F( { Group = self.GroupID, Text = self.MenuText, Path = self.MenuPath } )
missionCommands.removeItemForGroup( self.GroupID, self.MenuPath )
end
MENU_INDEX:ClearGroupMenu( self.Group, Path )

View File

@@ -192,6 +192,20 @@ do -- COORDINATE
return self
end
--- COORDINATE constructor.
-- @param #COORDINATE self
-- @param #COORDINATE Coordinate.
-- @return #COORDINATE
function COORDINATE:NewFromCoordinate( Coordinate )
local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE
self.x = Coordinate.x
self.y = Coordinate.y
self.z = Coordinate.z
return self
end
--- Create a new COORDINATE object from Vec2 coordinates.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point.
@@ -241,21 +255,38 @@ do -- COORDINATE
return { x = self.x, y = self.z }
end
--TODO: check this to replace
--- Calculate the distance from a reference @{DCSTypes#Vec2}.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}.
-- @return Dcs.DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters.
function COORDINATE:DistanceFromVec2( Vec2Reference )
self:F2( Vec2Reference )
local Distance = ( ( Vec2Reference.x - self.x ) ^ 2 + ( Vec2Reference.y - self.z ) ^2 ) ^0.5
--- Returns if the 2 coordinates are at the same 2D position.
-- @param #COORDINATE self
-- @param #COORDINATE Coordinate
-- @param #number Precision
-- @return #boolean true if at the same position.
function COORDINATE:IsAtCoordinate2D( Coordinate, Precision )
self:F( { Coordinate = Coordinate:GetVec2() } )
self:F( { self = self:GetVec2() } )
local x = Coordinate.x
local z = Coordinate.z
return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z
end
--- Calculate the distance from a reference @{#COORDINATE}.
-- @param #COORDINATE self
-- @param #COORDINATE PointVec2Reference The reference @{#COORDINATE}.
-- @return Dcs.DCSTypes#Distance The distance from the reference @{#COORDINATE} in meters.
function COORDINATE:DistanceFromPointVec2( PointVec2Reference )
self:F2( PointVec2Reference )
local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5
self:T2( Distance )
return Distance
end
--- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters.
@@ -303,6 +334,18 @@ do -- COORDINATE
end
--- Return a random Coordinate within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Distance OuterRadius
-- @param Dcs.DCSTypes#Distance InnerRadius
-- @return #COORDINATE
function COORDINATE:GetRandomCoordinateInRadius( OuterRadius, InnerRadius )
self:F2( { OuterRadius, InnerRadius } )
return COORDINATE:NewFromVec2( self:GetRandomVec2InRadius( OuterRadius, InnerRadius ) )
end
--- Return a random Vec3 within an Outer Radius and optionally NOT within an Inner Radius of the COORDINATE.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Distance OuterRadius
@@ -426,8 +469,8 @@ do -- COORDINATE
-- @param height (Optional) parameter specifying the height ASL.
-- @return Temperature in Degrees Celsius.
function COORDINATE:GetTemperature(height)
self:F2(height)
local y=height or self.y
env.info("FF height = "..y)
local point={x=self.x, y=height or self.y, z=self.z}
-- get temperature [K] and pressure [Pa] at point
local T,P=atmosphere.getTemperatureAndPressure(point)
@@ -850,11 +893,13 @@ do -- COORDINATE
function COORDINATE:WaypointGround( Speed, Formation )
self:F2( { Formation, Speed } )
local RoutePoint = {}
RoutePoint.x = self.x
RoutePoint.y = self.z
RoutePoint.action = Formation or ""
--RoutePoint.formation_template = Formation and "" or nil
RoutePoint.speed = ( Speed or 20 ) / 3.6
@@ -897,11 +942,15 @@ do -- COORDINATE
function COORDINATE:GetPathOnRoad(ToCoord)
local Path={}
local path = land.findPathOnRoads("roads", self.x, self.z, ToCoord.x, ToCoord.z)
for i, v in ipairs(path) do
--self:E(v)
local coord=COORDINATE:NewFromVec2(v)
Path[#Path+1]=COORDINATE:NewFromVec2(v)
end
Path[#Path+1]=COORDINATE:NewFromVec2(path[1])
Path[#Path+1]=COORDINATE:NewFromVec2(path[#path])
-- I've removed this stuff because it severely slows down DCS in case of paths with a lot of segments.
-- Just the beginning and the end point is sufficient.
-- for i, v in ipairs(path) do
-- self:I(v)
-- local coord=COORDINATE:NewFromVec2(v)
-- Path[#Path+1]=COORDINATE:NewFromVec2(v)
-- end
return Path
end
@@ -964,6 +1013,88 @@ do -- COORDINATE
self:Smoke( SMOKECOLOR.Blue )
end
--- Big smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @param Utilities.Utils#BIGSMOKEPRESET preset Smoke preset (0=small smoke and fire, 1=medium smoke and fire, 2=large smoke and fire, 3=huge smoke and fire, 4=small smoke, 5=medium smoke, 6=large smoke, 7=huge smoke).
-- @param #number density (Optional) Smoke density. Number in [0,...,1]. Default 0.5.
function COORDINATE:BigSmokeAndFire( preset, density )
self:F2( { preset=preset, density=density } )
density=density or 0.5
trigger.action.effectSmokeBig( self:GetVec3(), preset, density )
end
--- Small smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeAndFireSmall( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmokeAndFire, density)
end
--- Medium smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeAndFireMedium( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmokeAndFire, density)
end
--- Large smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeAndFireLarge( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmokeAndFire, density)
end
--- Huge smoke and fire at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeAndFireHuge( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmokeAndFire, density)
end
--- Small smoke at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeSmall( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.SmallSmoke, density)
end
--- Medium smoke at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeMedium( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.MediumSmoke, density)
end
--- Large smoke at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeLarge( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.LargeSmoke, density)
end
--- Huge smoke at the coordinate.
-- @param #COORDINATE self
-- @number density (Optional) Smoke density. Number between 0 and 1. Default 0.5.
function COORDINATE:BigSmokeHuge( density )
self:F2( { density=density } )
density=density or 0.5
self:BigSmokeAndFire(BIGSMOKEPRESET.HugeSmoke, density)
end
--- Flares the point in a color.
-- @param #COORDINATE self
-- @param Utilities.Utils#FLARECOLOR FlareColor
@@ -1244,7 +1375,7 @@ do -- COORDINATE
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringFromRP( ReferenceCoord, ReferenceName, Controllable, Settings ) -- R2.2
self:F( { ReferenceCoord = ReferenceCoord, ReferenceName = ReferenceName } )
self:F2( { ReferenceCoord = ReferenceCoord, ReferenceName = ReferenceName } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1273,7 +1404,7 @@ do -- COORDINATE
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2G( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1308,7 +1439,7 @@ do -- COORDINATE
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToStringA2A( Controllable, Settings ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1348,7 +1479,7 @@ do -- COORDINATE
-- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToString( Controllable, Settings, Task ) -- R2.2
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1397,7 +1528,7 @@ do -- COORDINATE
-- @return #string The pressure text in the configured measurement system.
function COORDINATE:ToStringPressure( Controllable, Settings ) -- R2.3
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1413,7 +1544,7 @@ do -- COORDINATE
-- @return #string The wind text in the configured measurement system.
function COORDINATE:ToStringWind( Controllable, Settings ) -- R2.3
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
@@ -1429,7 +1560,7 @@ do -- COORDINATE
-- @return #string The temperature text in the configured measurement system.
function COORDINATE:ToStringTemperature( Controllable, Settings ) -- R2.3
self:F( { Controllable = Controllable and Controllable:GetName() } )
self:F2( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS

View File

@@ -149,6 +149,7 @@ end
-- @param #SET_BASE self
-- @param #string ObjectName
function SET_BASE:Remove( ObjectName )
self:F2( { ObjectName = ObjectName } )
local Object = self.Set[ObjectName]
@@ -157,7 +158,6 @@ function SET_BASE:Remove( ObjectName )
if Key == ObjectName then
table.remove( self.Index, Index )
self.Set[ObjectName] = nil
self:Flush(self)
break
end
end
@@ -173,7 +173,7 @@ end
-- @param Core.Base#BASE Object
-- @return Core.Base#BASE The added BASE Object.
function SET_BASE:Add( ObjectName, Object )
self:F3( { ObjectName = ObjectName, Object = Object } )
self:F2( { ObjectName = ObjectName, Object = Object } )
-- Ensure that the existing element is removed from the Set before a new one is inserted to the Set
if self.Set[ObjectName] then
@@ -313,10 +313,6 @@ function SET_BASE:_FilterStart()
end
end
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
-- Follow alive players and clients
--self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit )
--self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit )
@@ -935,6 +931,9 @@ function SET_GROUP:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
end
@@ -1643,6 +1642,9 @@ do -- SET_UNIT
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
end
return self
@@ -2559,6 +2561,9 @@ do -- SET_STATIC
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
end
return self
@@ -3200,6 +3205,9 @@ function SET_CLIENT:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
end
return self
@@ -3599,6 +3607,9 @@ function SET_PLAYER:FilterStart()
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.Birth, self._EventOnBirth )
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
end
return self
@@ -4131,6 +4142,19 @@ function SET_CARGO:New() --R2.1
return self
end
--- (R2.1) Add CARGO to SET_CARGO.
-- @param Core.Set#SET_CARGO self
-- @param Cargo.Cargo#CARGO Cargo A single cargo.
-- @return self
function SET_CARGO:AddCargo( Cargo ) --R2.4
self:Add( Cargo:GetName(), Cargo )
return self
end
--- (R2.1) Add CARGOs to SET_CARGO.
-- @param Core.Set#SET_CARGO self
-- @param #string AddCargoNames A single name or an array of CARGO names.
@@ -4257,11 +4281,10 @@ function SET_CARGO:FilterStart() --R2.1
if _DATABASE then
self:_FilterStart()
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
end
self:HandleEvent( EVENTS.NewCargo )
self:HandleEvent( EVENTS.DeleteCargo )
return self
end
@@ -4313,6 +4336,71 @@ function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) --R2.1
return NearestCargo
end
function SET_CARGO:FirstCargoWithState( State )
local FirstCargo = nil
for CargoName, Cargo in pairs( self.Set ) do
if Cargo:Is( State ) then
FirstCargo = Cargo
break
end
end
return FirstCargo
end
function SET_CARGO:FirstCargoWithStateAndNotDeployed( State )
local FirstCargo = nil
for CargoName, Cargo in pairs( self.Set ) do
if Cargo:Is( State ) and not Cargo:IsDeployed() then
FirstCargo = Cargo
break
end
end
return FirstCargo
end
--- Iterate the SET_CARGO while identifying the first @{Cargo#CARGO} that is UnLoaded.
-- @param #SET_CARGO self
-- @return Cargo.Cargo#CARGO The first @{Cargo#CARGO}.
function SET_CARGO:FirstCargoUnLoaded()
local FirstCargo = self:FirstCargoWithState( "UnLoaded" )
return FirstCargo
end
--- Iterate the SET_CARGO while identifying the first @{Cargo#CARGO} that is UnLoaded and not Deployed.
-- @param #SET_CARGO self
-- @return Cargo.Cargo#CARGO The first @{Cargo#CARGO}.
function SET_CARGO:FirstCargoUnLoadedAndNotDeployed()
local FirstCargo = self:FirstCargoWithStateAndNotDeployed( "UnLoaded" )
return FirstCargo
end
--- Iterate the SET_CARGO while identifying the first @{Cargo#CARGO} that is Loaded.
-- @param #SET_CARGO self
-- @return Cargo.Cargo#CARGO The first @{Cargo#CARGO}.
function SET_CARGO:FirstCargoLoaded()
local FirstCargo = self:FirstCargoWithState( "Loaded" )
return FirstCargo
end
--- Iterate the SET_CARGO while identifying the first @{Cargo#CARGO} that is Deployed.
-- @param #SET_CARGO self
-- @return Cargo.Cargo#CARGO The first @{Cargo#CARGO}.
function SET_CARGO:FirstCargoDeployed()
local FirstCargo = self:FirstCargoWithState( "Deployed" )
return FirstCargo
end
--- (R2.1)
@@ -4336,7 +4424,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1
MCargoCoalition = true
end
end
self:T( { "Evaluated Coalition", MCargoCoalition } )
self:F( { "Evaluated Coalition", MCargoCoalition } )
MCargoInclude = MCargoInclude and MCargoCoalition
end
@@ -4348,7 +4436,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1
MCargoType = true
end
end
self:T( { "Evaluated Type", MCargoType } )
self:F( { "Evaluated Type", MCargoType } )
MCargoInclude = MCargoInclude and MCargoType
end
@@ -4360,7 +4448,7 @@ function SET_CARGO:IsIncludeObject( MCargo ) --R2.1
MCargoPrefix = true
end
end
self:T( { "Evaluated Prefix", MCargoPrefix } )
self:F( { "Evaluated Prefix", MCargoPrefix } )
MCargoInclude = MCargoInclude and MCargoPrefix
end
end
@@ -4374,6 +4462,8 @@ end
-- @param Core.Event#EVENTDATA EventData
function SET_CARGO:OnEventNewCargo( EventData ) --R2.1
self:F( { "New Cargo", EventData } )
if EventData.Cargo then
if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then
self:Add( EventData.Cargo.Name , EventData.Cargo )
@@ -4390,8 +4480,249 @@ function SET_CARGO:OnEventDeleteCargo( EventData ) --R2.1
if EventData.Cargo then
local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name )
if Cargo and Cargo.Name then
self:Remove( Cargo.Name )
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_CARGOs.
-- To prevent this from happening, the Cargo object has a flag NoDestroy.
-- When true, the SET_CARGO won't Remove the Cargo object from the set.
-- This flag is switched off after the event handlers have been called in the EVENT class.
self:F( { CargoNoDestroy=Cargo.NoDestroy } )
if Cargo.NoDestroy then
else
self:Remove( Cargo.Name )
end
end
end
end
--- @type SET_ZONE
-- @extends Core.Set#SET_BASE
--- # SET_ZONE class, extends @{Set#SET_BASE}
--
-- Mission designers can use the @{Set#SET_ZONE} class to build sets of zones of various types.
--
-- ## SET_ZONE constructor
--
-- Create a new SET_ZONE object with the @{#SET_ZONE.New} method:
--
-- * @{#SET_ZONE.New}: Creates a new SET_ZONE object.
--
-- ## Add or Remove ZONEs from SET_ZONE
--
-- ZONEs can be added and removed using the @{Set#SET_ZONE.AddZonesByName} and @{Set#SET_ZONE.RemoveZonesByName} respectively.
-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_ZONE.
--
-- ## 5.3) SET_ZONE filter criteria
--
-- You can set filter criteria to build the collection of zones in SET_ZONE.
-- Filter criteria are defined by:
--
-- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix.
--
-- Once the filter criteria have been set for the SET_ZONE, you can start filtering using:
--
-- * @{#SET_ZONE.FilterStart}: Starts the filtering of the zones within the SET_ZONE.
--
-- ## 5.4) SET_ZONE iterators
--
-- Once the filters have been defined and the SET_ZONE has been built, you can iterate the SET_ZONE with the available iterator methods.
-- The iterator methods will walk the SET_ZONE set, and call for each airbase within the set a function that you provide.
-- The following iterator methods are currently available within the SET_ZONE:
--
-- * @{#SET_ZONE.ForEachZone}: Calls a function for each zone it finds within the SET_ZONE.
--
-- ===
-- @field #SET_ZONE SET_ZONE
SET_ZONE = {
ClassName = "SET_ZONE",
Zones = {},
Filter = {
Prefixes = nil,
},
FilterMeta = {
},
}
--- Creates a new SET_ZONE object, building a set of zones.
-- @param #SET_ZONE self
-- @return #SET_ZONE self
-- @usage
-- -- Define a new SET_ZONE Object. The DatabaseSet will contain a reference to all Zones.
-- DatabaseSet = SET_ZONE:New()
function SET_ZONE:New()
-- Inherits from BASE
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.ZONES ) )
return self
end
--- Add ZONEs to SET_ZONE.
-- @param Core.Set#SET_ZONE self
-- @param #string AddZoneNames A single name or an array of ZONE_BASE names.
-- @return self
function SET_ZONE:AddZonesByName( AddZoneNames )
local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames }
for AddAirbaseID, AddZoneName in pairs( AddZoneNamesArray ) do
self:Add( AddZoneName, ZONE:FindByName( AddZoneName ) )
end
return self
end
--- Remove ZONEs from SET_ZONE.
-- @param Core.Set#SET_ZONE self
-- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names.
-- @return self
function SET_ZONE:RemoveZonesByName( RemoveZoneNames )
local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames }
for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do
self:Remove( RemoveZoneName )
end
return self
end
--- Finds a Zone based on the Zone Name.
-- @param #SET_ZONE self
-- @param #string ZoneName
-- @return Core.Zone#ZONE_BASE The found Zone.
function SET_ZONE:FindZone( ZoneName )
local ZoneFound = self.Set[ZoneName]
return ZoneFound
end
--- Get a random zone from the set.
-- @param #SET_ZONE self
-- @return Core.Zone#ZONE_BASE The random Zone.
function SET_ZONE:GetRandomZone()
local Index = self.Index
local ZoneFound = nil
while not ZoneFound do
local ZoneRandom = math.random( 1, #Index )
ZoneFound = self.Set[Index[ZoneRandom]]
end
return ZoneFound
end
--- Builds a set of zones of defined zone prefixes.
-- All the zones starting with the given prefixes will be included within the set.
-- @param #SET_ZONE self
-- @param #string Prefixes The prefix of which the zone name starts with.
-- @return #SET_ZONE self
function SET_ZONE:FilterPrefixes( Prefixes )
if not self.Filter.Prefixes then
self.Filter.Prefixes = {}
end
if type( Prefixes ) ~= "table" then
Prefixes = { Prefixes }
end
for PrefixID, Prefix in pairs( Prefixes ) do
self.Filter.Prefixes[Prefix] = Prefix
end
return self
end
--- Starts the filtering.
-- @param #SET_ZONE self
-- @return #SET_ZONE self
function SET_ZONE:FilterStart()
if _DATABASE then
-- We initialize the first set.
for ObjectName, Object in pairs( self.Database ) do
if self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object )
else
self:RemoveZonesByName( ObjectName )
end
end
end
return self
end
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
-- @param #SET_ZONE self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_ZONE:AddInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Handles the Database to check on any event that Object exists in the Database.
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
-- @param #SET_ZONE self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_ZONE:FindInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Iterate the SET_ZONE and call an interator function for each ZONE, providing the ZONE and optional parameters.
-- @param #SET_ZONE self
-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE. The function needs to accept a AIRBASE parameter.
-- @return #SET_ZONE self
function SET_ZONE:ForEachZone( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self:GetSet() )
return self
end
---
-- @param #SET_ZONE self
-- @param Core.Zone#ZONE_BASE MZone
-- @return #SET_ZONE self
function SET_ZONE:IsIncludeObject( MZone )
self:F2( MZone )
local MZoneInclude = true
if MZone then
local MZoneName = MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do
self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } )
if string.find( MZoneName, ZonePrefix, 1 ) then
MZonePrefix = true
end
end
self:T( { "Evaluated Prefix", MZonePrefix } )
MZoneInclude = MZoneInclude and MZonePrefix
end
end
self:T2( MZoneInclude )
return MZoneInclude
end

View File

@@ -288,20 +288,20 @@ function SPAWN:New( SpawnTemplatePrefix )
self.SpawnIndex = 0
self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil -- No grouping
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil -- No grouping
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else
@@ -334,19 +334,19 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.SpawnIndex = 0
self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
@@ -361,6 +361,55 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
end
--- Creates a new SPAWN instance to create new groups based on the provided template.
-- @param #SPAWN self
-- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure!
-- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn.
-- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime.
-- @return #SPAWN
-- @usage
-- -- Create a new SPAWN object based on a Group Template defined from scratch.
-- Spawn_BE_KA50 = SPAWN:NewWithAlias( 'BE KA-50@RAMP-Ground Defense', 'Helicopter Attacking a City' )
-- @usage
-- -- Create a new CSAR_Spawn object based on a normal Group Template to spawn a soldier.
-- local CSAR_Spawn = SPAWN:NewWithFromTemplate( Template, "CSAR", "Pilot" )
function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix )
local self = BASE:Inherit( self, BASE:New() )
self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } )
if SpawnTemplate then
self.SpawnTemplate = SpawnTemplate -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.SpawnAliasPrefix = SpawnAliasPrefix
self.SpawnIndex = 0
self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnInitLimit = false -- By default, no InitLimit.
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
self.SpawnRandomize = false -- Sets the randomization flag of new Spawned units to false.
self.SpawnVisible = false -- Flag that indicates if all the Groups of the SpawnGroup need to be visible when Spawned.
self.AIOnOff = true -- The AI is on by default when spawning a group.
self.SpawnUnControlled = false
self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name.
self.DelayOnOff = false -- No intial delay when spawning the first group.
self.Grouping = nil
self.SpawnGroups = {} -- Array containing the descriptions of each Group to be Spawned.
else
error( "There is no template provided for SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
end
self:SetEventPriority( 5 )
self.SpawnHookScheduler = SCHEDULER:New( nil )
return self
end
--- Limits the Maximum amount of Units that can be alive at the same time, and the maximum amount of groups that can be spawned.
-- Note that this method is exceptionally important to balance the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this method should be used...
@@ -404,6 +453,57 @@ function SPAWN:InitKeepUnitNames()
return self
end
--- Defines the Heading for the new spawned units.
-- The heading can be given as one fixed degree, or can be randomized between minimum and maximum degrees.
-- @param #SPAWN self
-- @param #number HeadingMin The minimum or fixed heading in degrees.
-- @param #number HeadingMax (optional) The maximum heading in degrees. This there is no maximum heading, then the heading will be fixed for all units using minimum heading.
-- @return #SPAWN self
-- @usage
--
-- Spawn = SPAWN:New( ... )
--
-- -- Spawn the units pointing to 100 degrees.
-- Spawn:InitHeading( 100 )
--
-- -- Spawn the units pointing between 100 and 150 degrees.
-- Spawn:InitHeading( 100, 150 )
--
function SPAWN:InitHeading( HeadingMin, HeadingMax )
self:F( )
self.SpawnInitHeadingMin = HeadingMin
self.SpawnInitHeadingMax = HeadingMax
return self
end
function SPAWN:InitCoalition( Coalition )
self:F( )
self.SpawnInitCoalition = Coalition
return self
end
function SPAWN:InitCountry( Country )
self:F( )
self.SpawnInitCountry = Country
return self
end
function SPAWN:InitCategory( Category )
self:F( )
self.SpawnInitCategory = Category
return self
end
--- Randomizes the defined route of the SpawnTemplatePrefix group in the ME. This is very useful to define extra variation of the behaviour of groups.
-- @param #SPAWN self
@@ -941,6 +1041,18 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
end
end
-- If Heading is given, point all the units towards the given Heading.
if self.SpawnInitHeadingMin then
for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].heading = self.SpawnInitHeadingMax and math.random( self.SpawnInitHeadingMin, self.SpawnInitHeadingMax ) or self.SpawnInitHeadingMin
end
end
SpawnTemplate.CategoryID = self.SpawnInitCategory or SpawnTemplate.CategoryID
SpawnTemplate.CountryID = self.SpawnInitCountry or SpawnTemplate.CountryID
SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID
if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then
if SpawnTemplate.route.points[1].type == "TakeOffParking" then
SpawnTemplate.uncontrolled = self.SpawnUnControlled
@@ -1247,14 +1359,20 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
self:T( { "Current point of ", self.SpawnTemplatePrefix, Vec3 } )
local TemplateHeight = SpawnTemplate.route.points[1].alt
local TemplateHeight = SpawnTemplate.route and SpawnTemplate.route.points[1].alt or nil
SpawnTemplate.route = SpawnTemplate.route or {}
SpawnTemplate.route.points = SpawnTemplate.route.points or {}
SpawnTemplate.route.points[1] = SpawnTemplate.route.points[1] or {}
SpawnTemplate.route.points[1].x = SpawnTemplate.route.points[1].x or 0
SpawnTemplate.route.points[1].y = SpawnTemplate.route.points[1].y or 0
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
--self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
local UnitTemplate = SpawnTemplate.units[UnitID]
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local SX = UnitTemplate.x or 0
local SY = UnitTemplate.y or 0
local BX = SpawnTemplate.route.points[1].x
local BY = SpawnTemplate.route.points[1].y
local TX = Vec3.x + ( SX - BX )
@@ -1266,7 +1384,6 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
end
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
end
SpawnTemplate.route.points[1].x = Vec3.x
SpawnTemplate.route.points[1].y = Vec3.z
if SpawnTemplate.CategoryID ~= Group.Category.SHIP then
@@ -1283,6 +1400,47 @@ function SPAWN:SpawnFromVec3( Vec3, SpawnIndex )
return nil
end
--- Will spawn a group from a Coordinate in 3D space.
-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Core.Point#Coordinate Coordinate The Coordinate coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
function SPAWN:SpawnFromCoordinate( Coordinate, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
return self:SpawnFromVec3( Coordinate:GetVec3(), SpawnIndex )
end
--- Will spawn a group from a PointVec3 in 3D space.
-- This method is mostly advisable to be used if you want to simulate spawning units in the air, like helicopters or airplanes.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 coordinates where to spawn the group.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @usage
--
-- local SpawnPointVec3 = ZONE:New( ZoneName ):GetPointVec3( 2000 ) -- Get the center of the ZONE object at 2000 meters from the ground.
--
-- -- Spawn at the zone center position at 2000 meters from the ground!
-- SpawnAirplanes:SpawnFromPointVec3( SpawnPointVec3 )
--
function SPAWN:SpawnFromPointVec3( PointVec3, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
return self:SpawnFromVec3( PointVec3:GetVec3(), SpawnIndex )
end
--- Will spawn a group from a Vec2 in 3D space.
-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
@@ -1317,6 +1475,35 @@ function SPAWN:SpawnFromVec2( Vec2, MinHeight, MaxHeight, SpawnIndex )
end
--- Will spawn a group from a POINT_VEC2 in 3D space.
-- This method is mostly advisable to be used if you want to simulate spawning groups on the ground from air units, like vehicles.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
-- @param #SPAWN self
-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 coordinates where to spawn the group.
-- @param #number MinHeight (optional) The minimum height to spawn an airborne group into the zone.
-- @param #number MaxHeight (optional) The maximum height to spawn an airborne group into the zone.
-- @param #number SpawnIndex (optional) The index which group to spawn within the given zone.
-- @return Wrapper.Group#GROUP that was spawned.
-- @return #nil Nothing was spawned.
-- @usage
--
-- local SpawnPointVec2 = ZONE:New( ZoneName ):GetPointVec2()
--
-- -- Spawn at the zone center position at the height specified in the ME of the group template!
-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2 )
--
-- -- Spawn from the static position at the height randomized between 2000 and 4000 meters.
-- SpawnAirplanes:SpawnFromPointVec2( SpawnPointVec2, 2000, 4000 )
--
function SPAWN:SpawnFromPointVec2( PointVec2, MinHeight, MaxHeight, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
return self:SpawnFromVec2( PointVec2:GetVec2(), MinHeight, MaxHeight, SpawnIndex )
end
--- Will spawn a group from a hosting unit. This method is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
-- You can use the returned group to further define the route to be followed.
@@ -1693,7 +1880,10 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix )
local SpawnTemplate = nil
SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
local Template = _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template
self:F( { Template = Template } )
SpawnTemplate = UTILS.DeepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template )
if SpawnTemplate == nil then
error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix )
@@ -1715,7 +1905,12 @@ end
function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
-- if not self.SpawnTemplate then
-- self.SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
-- end
local SpawnTemplate = self:_GetTemplate( SpawnTemplatePrefix )
--local SpawnTemplate = self.SpawnTemplate
SpawnTemplate.name = self:SpawnGroupName( SpawnIndex )
SpawnTemplate.groupId = nil
@@ -1808,7 +2003,7 @@ function SPAWN:_RandomizeTemplate( SpawnIndex )
if self.SpawnRandomizeTemplate then
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefixTable[ math.random( 1, #self.SpawnTemplatePrefixTable ) ]
self.SpawnGroups[SpawnIndex].SpawnTemplate = self:_Prepare( self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix, SpawnIndex )
self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
self.SpawnGroups[SpawnIndex].SpawnTemplate.route = UTILS.DeepCopy( self.SpawnTemplate.route )
self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time

View File

@@ -81,14 +81,16 @@ SPAWNSTATIC = {
-- @param #SPAWNSTATIC self
-- @param #string SpawnTemplatePrefix is the name of the Group in the ME that defines the Template. Each new group will have the name starting with SpawnTemplatePrefix.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, CountryID ) --R2.1
function SPAWNSTATIC:NewFromStatic( SpawnTemplatePrefix, SpawnCountryID ) --R2.1
local self = BASE:Inherit( self, BASE:New() ) -- #SPAWNSTATIC
self:F( { SpawnTemplatePrefix } )
local TemplateStatic = StaticObject.getByName( SpawnTemplatePrefix )
local TemplateStatic, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticUnitTemplate( SpawnTemplatePrefix )
if TemplateStatic then
self.SpawnTemplatePrefix = SpawnTemplatePrefix
self.CountryID = CountryID
self.CountryID = SpawnCountryID or CountryID
self.CategoryID = CategoryID
self.CoalitionID = CoalitionID
self.SpawnIndex = 0
else
error( "SPAWNSTATIC:New: There is no group declared in the mission editor with SpawnTemplatePrefix = '" .. SpawnTemplatePrefix .. "'" )
@@ -116,6 +118,7 @@ function SPAWNSTATIC:NewFromType( SpawnTypeName, SpawnShapeName, SpawnCategory,
return self
end
--- Creates a new @{Static} at the original position.
-- @param #SPAWNSTATIC self
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
@@ -124,22 +127,28 @@ end
function SPAWNSTATIC:Spawn( Heading, NewName ) --R2.3
self:F( { Heading, NewName } )
local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID]
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
if StaticTemplate then
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
local Static = coalition.addStaticObject( self.CountryID, StaticTemplate )
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
self.SpawnIndex = self.SpawnIndex + 1
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
return Static
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
return nil
end
@@ -153,32 +162,101 @@ end
function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1
self:F( { PointVec2, Heading, NewName } )
local CountryName = _DATABASE.COUNTRY_NAME[self.CountryID]
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
StaticTemplate.x = PointVec2.x
StaticTemplate.y = PointVec2.z
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
self.SpawnIndex = self.SpawnIndex + 1
return Static
end
return nil
end
--- Creates the original @{Static} at a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @param #string (optional) The name of the new static.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawn()
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
StaticTemplate.x = PointVec2.x
StaticTemplate.y = PointVec2.z
if StaticTemplate then
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
local CountryID = self.CountryID
local CountryName = _DATABASE.COUNTRY_NAME[CountryID]
StaticTemplate.units = nil
StaticTemplate.route = nil
StaticTemplate.groupId = nil
StaticTemplate.name = NewName or string.format("%s#%05d", self.SpawnTemplatePrefix, self.SpawnIndex )
StaticTemplate.heading = ( Heading / 180 ) * math.pi
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
local Static = coalition.addStaticObject( self.CountryID, StaticTemplate )
return Static
end
self.SpawnIndex = self.SpawnIndex + 1
return Static
return nil
end
--- Creates the original @{Static} at a POINT_VEC2.
-- @param #SPAWNSTATIC self
-- @param Core.Point#COORDINATE Coordinate The 2D coordinate where to spawn the static.
-- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360.
-- @return #SPAWNSTATIC
function SPAWNSTATIC:ReSpawnAt( Coordinate, Heading )
local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix )
if StaticTemplate then
local CountryID = self.CountryID
StaticTemplate.x = Coordinate.x
StaticTemplate.y = Coordinate.z
StaticTemplate.heading = Heading and ( ( Heading / 180 ) * math.pi ) or StaticTemplate.heading
StaticTemplate.CountryID = nil
StaticTemplate.CoalitionID = nil
StaticTemplate.CategoryID = nil
local Static = coalition.addStaticObject( CountryID, StaticTemplate )
return Static
end
return nil
end
--- Creates a new @{Static} from a @{Zone}.
-- @param #SPAWNSTATIC self
-- @param Core.Zone#ZONE_BASE Zone The Zone where to spawn the static.

View File

@@ -114,6 +114,8 @@ function ZONE_BASE:New( ZoneName )
return self
end
--- Returns the name of the zone.
-- @param #ZONE_BASE self
-- @return #string The name of the zone.
@@ -149,10 +151,16 @@ end
-- @param Dcs.DCSTypes#Vec3 Vec3 The point to test.
-- @return #boolean true if the Vec3 is within the zone.
function ZONE_BASE:IsVec3InZone( Vec3 )
self:F2( Vec3 )
local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } )
return InZone
end
--- Returns if a Coordinate is within the zone.
-- @param #ZONE_BASE self
-- @param Core.Point#COORDINATE Coordinate The coordinate to test.
-- @return #boolean true if the coordinate is within the zone.
function ZONE_BASE:IsCoordinateInZone( Coordinate )
local InZone = self:IsVec2InZone( Coordinate:GetVec2() )
return InZone
end
@@ -161,10 +169,7 @@ end
-- @param Core.Point#POINT_VEC2 PointVec2 The PointVec2 to test.
-- @return #boolean true if the PointVec2 is within the zone.
function ZONE_BASE:IsPointVec2InZone( PointVec2 )
self:F2( PointVec2 )
local InZone = self:IsVec2InZone( PointVec2:GetVec2() )
return InZone
end
@@ -173,10 +178,7 @@ end
-- @param Core.Point#POINT_VEC3 PointVec3 The PointVec3 to test.
-- @return #boolean true if the PointVec3 is within the zone.
function ZONE_BASE:IsPointVec3InZone( PointVec3 )
self:F2( PointVec3 )
local InZone = self:IsPointVec2InZone( PointVec3 )
return InZone
end
@@ -185,8 +187,6 @@ end
-- @param #ZONE_BASE self
-- @return #nil.
function ZONE_BASE:GetVec2()
self:F2( self.ZoneName )
return nil
end
@@ -603,6 +603,7 @@ function ZONE_RADIUS:Scan( ObjectCategories )
self.ScanData = {}
self.ScanData.Coalitions = {}
self.ScanData.Scenery = {}
self.ScanData.Units = {}
local ZoneCoord = self:GetCoordinate()
local ZoneRadius = self:GetRadius()
@@ -625,6 +626,7 @@ function ZONE_RADIUS:Scan( ObjectCategories )
(ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self.ScanData.Units[ZoneObject] = ZoneObject
self:F( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
end
if ObjectCategory == Object.Category.SCENERY then
@@ -643,6 +645,12 @@ function ZONE_RADIUS:Scan( ObjectCategories )
end
function ZONE_RADIUS:GetScannedUnits()
return self.ScanData.Units
end
function ZONE_RADIUS:CountScannedCoalitions()
local Count = 0
@@ -954,6 +962,17 @@ function ZONE:New( ZoneName )
return self
end
--- Find a zone in the _DATABASE using the name of the zone.
-- @param #ZONE_BASE self
-- @param #string ZoneName The name of the zone.
-- @return #ZONE_BASE self
function ZONE:FindByName( ZoneName )
local ZoneFound = _DATABASE:FindZone( ZoneName )
return ZoneFound
end
--- @type ZONE_UNIT
-- @field Wrapper.Unit#UNIT ZoneUNIT

View File

@@ -4,7 +4,7 @@
--- @type CoalitionObject
-- @extends Dcs.DCSWrapper.Object#Object
coalition = {} --#coalition
--- Returns coalition of the object.
-- @function [parent=#CoalitionObject] getCoalition

View File

@@ -12,3 +12,5 @@
--- @function [parent=#coalition] getCountryCoalition
-- @param #number countryId
-- @return #number coalitionId
coalition = coalition -- #coalition

File diff suppressed because it is too large Load Diff

View File

@@ -510,7 +510,6 @@ do -- DETECTION_BASE
-- @param #string Event The Event string.
-- @param #string To The To State string.
function DETECTION_BASE:onafterDetect(From,Event,To)
self:F( { From, Event, To } )
local DetectDelay = 0.1
self.DetectionCount = 0
@@ -533,7 +532,6 @@ do -- DETECTION_BASE
-- @param #string To The To State string.
-- @param Wrapper.Group#GROUP DetectionGroup The Group detecting.
function DETECTION_BASE:onafterDetectionGroup( From, Event, To, DetectionGroup, DetectionTimeStamp )
self:F( { From, Event, To } )
self.DetectionRun = self.DetectionRun + 1
@@ -541,7 +539,7 @@ do -- DETECTION_BASE
if DetectionGroup:IsAlive() then
self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
--self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
local DetectionGroupName = DetectionGroup:GetName()
local DetectionUnit = DetectionGroup:GetUnit(1)
@@ -557,7 +555,7 @@ do -- DETECTION_BASE
self.DetectDLINK
)
self:F( DetectedTargets )
--self:F( DetectedTargets )
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
local DetectedObject = Detection.object -- Dcs.DCSWrapper.Object#Object
@@ -574,7 +572,7 @@ do -- DETECTION_BASE
self.DetectDLINK
)
self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } )
--self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } )
-- Only process if the target is visible. Detection also returns invisible units.
--if Detection.visible == true then
@@ -596,7 +594,7 @@ do -- DETECTION_BASE
local DetectedUnitCategory = DetectedObject:getDesc().category
self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
--self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
-- Calculate Acceptance
@@ -644,7 +642,7 @@ do -- DETECTION_BASE
local DistanceProbability = 1 - DistanceProbabilityReversed
DistanceProbability = DistanceProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
self:T( { Probability, DistanceProbability } )
--self:T( { Probability, DistanceProbability } )
if Probability > DistanceProbability then
DetectionAccepted = false
end
@@ -660,7 +658,7 @@ do -- DETECTION_BASE
AlphaAngleProbability = AlphaAngleProbability * 30 / 300
local Probability = math.random() -- Selects a number between 0 and 1
self:T( { Probability, AlphaAngleProbability } )
--self:T( { Probability, AlphaAngleProbability } )
if Probability > AlphaAngleProbability then
DetectionAccepted = false
end
@@ -677,7 +675,7 @@ do -- DETECTION_BASE
if ZoneObject:IsPointVec2InZone( DetectedObjectVec2 ) == true then
local Probability = math.random() -- Selects a number between 0 and 1
self:T( { Probability, ZoneProbability } )
--self:T( { Probability, ZoneProbability } )
if Probability > ZoneProbability then
DetectionAccepted = false
break
@@ -702,7 +700,7 @@ do -- DETECTION_BASE
self.DetectedObjects[DetectedObjectName].Distance = Distance
self.DetectedObjects[DetectedObjectName].DetectionTimeStamp = DetectionTimeStamp
self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } )
--self:F( { DetectedObject = self.DetectedObjects[DetectedObjectName] } )
local DetectedUnit = UNIT:FindByName( DetectedObjectName )
@@ -716,7 +714,7 @@ do -- DETECTION_BASE
--end
end
self:T2( self.DetectedObjects )
--self:T2( self.DetectedObjects )
end
if HasDetectedObjects then
@@ -726,7 +724,6 @@ do -- DETECTION_BASE
end
if self.DetectionCount > 0 and self.DetectionRun == self.DetectionCount then
self:T( "--> Create Detection Sets" )
-- First check if all DetectedObjects were detected.
-- This is important. When there are DetectedObjects in the list, but were not detected,
@@ -766,7 +763,6 @@ do -- DETECTION_BASE
local DetectedSet = DetectedItem.Set
if DetectedSet:Count() == 0 then
self:F3( { DetectedItemID = DetectedItemID } )
self:RemoveDetectedItem( DetectedItemID )
end
@@ -778,7 +774,6 @@ do -- DETECTION_BASE
-- @param #string UnitName The UnitName that needs to be forgotten from the DetectionItem Sets.
-- @return #DETECTION_BASE
function DETECTION_BASE:ForgetDetectedUnit( UnitName )
self:F2()
local DetectedItems = self:GetDetectedItems()
@@ -796,7 +791,6 @@ do -- DETECTION_BASE
-- @param #DETECTION_BASE self
-- @return #DETECTION_BASE
function DETECTION_BASE:CreateDetectionItems()
self:F2()
self:F( "Error, in DETECTION_BASE class..." )
return self
@@ -1179,8 +1173,7 @@ do -- DETECTION_BASE
-- @param Dcs.DCSUnit#Unit.Category Category The category of the unit.
-- @return #boolean true if there are friendlies nearby
function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category )
self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } )
--self:F( { "FriendliesNearBy Test", DetectedItem.FriendliesNearBy } )
return ( DetectedItem.FriendliesNearBy and DetectedItem.FriendliesNearBy[Category] ~= nil ) or false
end
@@ -1237,7 +1230,7 @@ do -- DETECTION_BASE
--- Background worker function to determine if there are friendlies nearby ...
-- @param #DETECTION_BASE self
function DETECTION_BASE:ReportFriendliesNearBy( TargetData )
self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } )
--self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } )
local DetectedItem = TargetData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = TargetData.DetectedItem.Set
@@ -1286,7 +1279,7 @@ do -- DETECTION_BASE
if FoundUnitInReportSetGroup == true then
-- If the recce was part of the friendlies found, then check if the recce is part of the allowed friendly unit prefixes.
for PrefixID, Prefix in pairs( self.FriendlyPrefixes or {} ) do
self:F( { "Friendly Prefix:", Prefix = Prefix } )
--self:F( { "Friendly Prefix:", Prefix = Prefix } )
-- In case a match is found (so a recce unit name is part of the friendly prefixes), then report that recce to be part of the friendlies.
-- This is important if CAP planes (so planes using their own radar) to be scanning for targets as part of the EWR network.
-- But CAP planes are also attackers, so they need to be considered friendlies too!
@@ -1298,7 +1291,7 @@ do -- DETECTION_BASE
end
end
self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } )
--self:F( { "Friendlies near Target:", FoundUnitName, FoundUnitCoalition, EnemyUnitName, EnemyCoalition, FoundUnitInReportSetGroup } )
if FoundUnitCoalition ~= EnemyCoalition and FoundUnitInReportSetGroup == false then
local FriendlyUnit = UNIT:Find( FoundDCSUnit )
@@ -1313,7 +1306,7 @@ do -- DETECTION_BASE
local Distance = DetectedUnitCoord:Get2DDistance( FriendlyUnit:GetCoordinate() )
DetectedItem.FriendliesDistance = DetectedItem.FriendliesDistance or {}
DetectedItem.FriendliesDistance[Distance] = FriendlyUnit
self:T( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } )
--self:F( { "Friendlies Found:", FriendlyUnitName = FriendlyUnitName, Distance = Distance, FriendlyUnitCategory = FriendlyUnitCategory, FriendliesCategory = self.FriendliesCategory } )
return true
end
@@ -1355,6 +1348,9 @@ do -- DETECTION_BASE
end
)
end
self:F( { Friendlies = DetectedItem.FriendliesNearBy, Players = DetectedItem.PlayersNearBy } )
end
end
@@ -1886,7 +1882,6 @@ do -- DETECTION_UNITS
-- @param #DETECTION_UNITS self
-- @return #DETECTION_UNITS self
function DETECTION_UNITS:CreateDetectionItems()
self:F2( #self.DetectedObjects )
-- Loop the current detected items, and check if each object still exists and is detected.
@@ -2137,7 +2132,6 @@ do -- DETECTION_TYPES
-- @param #DETECTION_TYPES self
-- @return #DETECTION_TYPES self
function DETECTION_TYPES:CreateDetectionItems()
self:F2( #self.DetectedObjects )
-- Loop the current detected items, and check if each object still exists and is detected.
@@ -2525,10 +2519,9 @@ do -- DETECTION_AREAS
-- @param #DETECTION_AREAS self
-- @return #DETECTION_AREAS self
function DETECTION_AREAS:CreateDetectionItems()
self:F2()
self:T( "Checking Detected Items for new Detected Units ..." )
self:T2( "Checking Detected Items for new Detected Units ..." )
-- First go through all detected sets, and check if there are new detected units, match all existing detected units and identify undetected units.
-- Regroup when needed, split groups when needed.
for DetectedItemID, DetectedItemData in pairs( self.DetectedItems ) do
@@ -2537,8 +2530,7 @@ do -- DETECTION_AREAS
if DetectedItem then
self:T( { "Detected Item ID:", DetectedItemID } )
self:T2( { "Detected Item ID: ", DetectedItemID } )
local DetectedSet = DetectedItem.Set
@@ -2667,7 +2659,6 @@ do -- DETECTION_AREAS
local DetectedItem = DetectedItemData -- #DETECTION_BASE.DetectedItem
if DetectedItem then
self:T( "Detection Area #" .. DetectedItem.ID )
local DetectedSet = DetectedItem.Set
if not self:IsDetectedObjectIdentified( DetectedObject ) and DetectedUnit:IsInZone( DetectedItem.Zone ) then
self:IdentifyDetectedObject( DetectedObject )

File diff suppressed because it is too large Load Diff

View File

@@ -42,20 +42,21 @@
--
-- # Demo Missions
--
-- ### [RAT Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/Release/RAT%20-%20Random%20Air%20Traffic)
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
-- ### [MOOSE - RAT Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/RAT%20-%20Random%20Air%20Traffic)
--
-- ===
--
-- # YouTube Channel
--
-- ### [DCS WORLD - MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO)
-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg)
-- ### [MOOSE - RAT - Random Air Traffic](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0u4Zxywtg-mx_ov4vi68CO)
--
-- ===
--
-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)**
--
-- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))**
-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536)
--
-- ===
-- @module Rat
@@ -116,7 +117,7 @@
-- @field #boolean continuejourney Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.
-- @field #number ngroups Number of groups to be spawned in total.
-- @field #number alive Number of groups which are alive.
-- @field #boolean f10menu Add an F10 menu for RAT.
-- @field #boolean f10menu If true, add an F10 radiomenu for RAT. Default is false.
-- @field #table Menu F10 menu items for this RAT object.
-- @field #string SubMenuName Submenu name for RAT object.
-- @field #boolean respawn_at_landing Respawn aircraft the moment they land rather than at engine shutdown.
@@ -350,7 +351,7 @@ RAT={
continuejourney=false, -- Aircraft will continue their journey, i.e. get respawned at their destination with a new random destination.
alive=0, -- Number of groups which are alive.
ngroups=nil, -- Number of groups to be spawned in total.
f10menu=true, -- Add an F10 menu for RAT.
f10menu=false, -- Add an F10 menu for RAT.
Menu={}, -- F10 menu items for this RAT object.
SubMenuName=nil, -- Submenu name for RAT object.
respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown.
@@ -506,7 +507,7 @@ RAT.id="RAT | "
--- RAT version.
-- @list version
RAT.version={
version = "2.2.1",
version = "2.2.2",
print = true,
}
@@ -1280,7 +1281,7 @@ end
--- Check if aircraft have accidentally been spawned on the runway. If so they will be removed immediatly.
-- @param #RAT self
-- @param #booblen switch If true, check is performed. If false, this check is omitted.
-- @param #boolean switch If true, check is performed. If false, this check is omitted.
function RAT:CheckOnRunway(switch)
self:F2(switch)
if switch==nil then
@@ -1291,7 +1292,7 @@ end
--- Check if aircraft have accidentally been spawned on top of each other. If yes, they will be removed immediately.
-- @param #RAT self
-- @param #booblen switch If true, check is performed. If false, this check is omitted.
-- @param #boolean switch If true, check is performed. If false, this check is omitted.
function RAT:CheckOnTop(switch)
self:F2(switch)
if switch==nil then
@@ -1302,7 +1303,7 @@ end
--- Put parking spot coordinates in a data base for future use of aircraft.
-- @param #RAT self
-- @param #booblen switch If true, parking spots are memorized. This is also the default setting.
-- @param #boolean switch If true, parking spots are memorized. This is also the default setting.
function RAT:ParkingSpotDB(switch)
self:F2(switch)
if switch==nil then
@@ -1363,6 +1364,21 @@ function RAT:Immortal()
self.immortal=true
end
--- Radio menu On. Default is off.
-- @param #RAT self
function RAT:RadioMenuON()
self:F2()
self.f10menu=true
end
--- Radio menu Off. This is the default setting.
-- @param #RAT self
function RAT:RadioMenuOFF()
self:F2()
self.f10menu=false
end
--- Activate uncontrolled aircraft.
-- @param #RAT self
-- @param #number maxactivated Maximal numnber of activated aircraft. Absolute maximum will be the number of spawned groups. Default is 1.
@@ -4265,10 +4281,10 @@ end
function RAT:_CommandImmortal(group, switch)
-- Command structure for setting groups to invisible.
local SetInvisible = {id = 'SetImmortal', params = {value = switch}}
local SetImmortal = {id = 'SetImmortal', params = {value = switch}}
-- Execute command.
group:SetCommand(SetInvisible)
group:SetCommand(SetImmortal)
end
--- Adds a parking spot at an airport when it has been used by a spawned RAT aircraft to the RAT parking data base.

View File

@@ -32,12 +32,13 @@
--
-- # Demo Missions
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
-- ### [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
--
-- ===
--
-- # YouTube Channel
--
-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg)
-- ### [MOOSE - On the Range - Demonstration Video](https://www.youtube.com/watch?v=kIXcxNB9_3M)
--
-- ===
@@ -211,6 +212,9 @@
--
-- The function @{#RANGE.DebugON}() can be used to send messages on screen. It also smokes all defined strafe and bombing targets, the strafe pit approach boxes and the range zone.
--
-- Note that it can happen that the RANGE radio menu is not shown. Check that the range object is defined as a **global** variable rather than a local one.
-- The could avoid the lua garbage collection to accidentally/falsely deallocate the RANGE objects.
--
--
--
-- @field #RANGE
@@ -279,7 +283,7 @@ RANGE.id="RANGE | "
--- Range script version.
-- @field #number version
RANGE.version="1.1.0"
RANGE.version="1.1.1"
--TODO list:
--TODO: Add custom weapons, which can be specified by the user.
@@ -1537,6 +1541,80 @@ function RANGE:_DisplayRangeInfo(_unitname)
end
end
--- Display bombing target locations to player.
-- @param #RANGE self
-- @param #string _unitname Name of the player unit.
function RANGE:_DisplayBombTargets(_unitname)
self:F(_unitname)
-- Get player unit and player name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if _unit and _playername then
-- Player settings.
local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS
-- Message text.
local _text="Bomb Target Locations:"
for _,_bombtarget in pairs(self.bombingTargets) do
local _target=_bombtarget.target --Wrapper.Positionable#POSITIONABLE
if _target and _target:IsAlive() then
-- Core.Point#COORDINATE
local coord=_target:GetCoordinate() --Core.Point#COORDINATE
local mycoord=coord:ToStringA2G(_unit, _settings)
_text=_text..string.format("\n- %s: %s",_bombtarget.name, mycoord)
end
end
self:_DisplayMessageToGroup(_unit,_text, nil, true)
end
end
--- Display pit location and heading to player.
-- @param #RANGE self
-- @param #string _unitname Name of the player unit.
function RANGE:_DisplayStrafePits(_unitname)
self:F(_unitname)
-- Get player unit and player name.
local _unit, _playername = self:_GetPlayerUnitAndName(_unitname)
-- Check if we have a player.
if _unit and _playername then
-- Player settings.
local _settings=_DATABASE:GetPlayerSettings(_playername) or _SETTINGS --Core.Settings#SETTINGS
-- Message text.
local _text="Strafe Target Locations:"
for _,_strafepit in pairs(self.strafeTargets) do
local _target=_strafepit --Wrapper.Positionable#POSITIONABLE
-- Pit parameters.
local coord=_strafepit.coordinate --Core.Point#COORDINATE
local heading=_strafepit.heading
-- Turn heading around ==> approach heading.
if heading>180 then
heading=heading-180
else
heading=heading+180
end
local mycoord=coord:ToStringA2G(_unit, _settings)
_text=_text..string.format("\n- %s: %s - heading %03d",_strafepit.name, mycoord, heading)
end
self:_DisplayMessageToGroup(_unit,_text, nil, true)
end
end
--- Report weather conditions at range. Temperature, QFE pressure and wind data.
-- @param #RANGE self
-- @param #string _unitname Name of the player unit.
@@ -1792,11 +1870,11 @@ function RANGE:_AddF10Commands(_unitName)
local _statsPath = missionCommands.addSubMenuForGroup(_gid, "Statistics", _rangePath)
local _markPath = missionCommands.addSubMenuForGroup(_gid, "Mark Targets", _rangePath)
local _settingsPath = missionCommands.addSubMenuForGroup(_gid, "My Settings", _rangePath)
local _infoPath = missionCommands.addSubMenuForGroup(_gid, "Range Info", _rangePath)
-- F10/On the Range/<Range Name>/My Settings/
local _mysmokePath = missionCommands.addSubMenuForGroup(_gid, "Smoke Color", _settingsPath)
local _myflarePath = missionCommands.addSubMenuForGroup(_gid, "Flare Color", _settingsPath)
--TODO: Convert to MOOSE menu.
-- F10/On the Range/<Range Name>/Mark Targets/
missionCommands.addCommandForGroup(_gid, "Mark On Map", _markPath, self._MarkTargetsOnMap, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Illuminate Range", _markPath, self._IlluminateBombTargets, self, _unitName)
@@ -1824,9 +1902,11 @@ function RANGE:_AddF10Commands(_unitName)
missionCommands.addCommandForGroup(_gid, "Smoke Delay On/Off", _settingsPath, self._SmokeBombDelayOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Smoke Impact On/Off", _settingsPath, self._SmokeBombImpactOnOff, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Flare Hits On/Off", _settingsPath, self._FlareDirectHitsOnOff, self, _unitName)
-- F10/On the Range/<Range Name>/
missionCommands.addCommandForGroup(_gid, "Range Information", _rangePath, self._DisplayRangeInfo, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Weather Report", _rangePath, self._DisplayRangeWeather, self, _unitName)
-- F10/On the Range/<Range Name>/Range Information
missionCommands.addCommandForGroup(_gid, "General Info", _infoPath, self._DisplayRangeInfo, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Weather Report", _infoPath, self._DisplayRangeWeather, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Bombing Targets", _infoPath, self._DisplayBombTargets, self, _unitName)
missionCommands.addCommandForGroup(_gid, "Strafe Pits", _infoPath, self._DisplayStrafePits, self, _unitName)
end
else
self:T(RANGE.id.."Could not find group or group ID in AddF10Menu() function. Unit name: ".._unitName)

View File

@@ -858,7 +858,7 @@ function SCORING:OnEventBirth( Event )
if Event.IniUnit then
if Event.IniObjectCategory == 1 then
local PlayerName = Event.IniUnit:GetPlayerName()
if PlayerName ~= "" then
if PlayerName then
self:_AddPlayerFromUnit( Event.IniUnit )
self:SetScoringMenu( Event.IniGroup )
end

File diff suppressed because it is too large Load Diff

View File

@@ -13,3 +13,5 @@ _DATABASE = DATABASE:New() -- Core.Database#DATABASE
_SETTINGS = SETTINGS:Set()
_SETTINGS:SetPlayerMenuOn()
_DATABASE:RegisterCargos()

View File

@@ -86,12 +86,14 @@ COMMANDCENTER = {
-- @return #COMMANDCENTER
function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
local self = BASE:Inherit( self, BASE:New() )
local self = BASE:Inherit( self, BASE:New() ) -- #COMMANDCENTER
self.CommandCenterPositionable = CommandCenterPositionable
self.CommandCenterName = CommandCenterName or CommandCenterPositionable:GetName()
self.CommandCenterCoalition = CommandCenterPositionable:GetCoalition()
self.AutoAssignTasks = false
self.Missions = {}
self:HandleEvent( EVENTS.Birth,
@@ -171,7 +173,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
end
)
-- Handle when a player leaves a slot and goes back to spectators ...
-- Handle when a player crashes ...
-- The PlayerUnit will be UnAssigned from the Task.
-- When there is no Unit left running the Task, the Task goes into Abort...
self:HandleEvent( EVENTS.Crash,
@@ -220,6 +222,14 @@ function COMMANDCENTER:GetShortText()
end
--- Gets the coalition of the command center.
-- @param #COMMANDCENTER self
-- @return DCScoalition#coalition
function COMMANDCENTER:GetCoalition()
return self.CommandCenterCoalition
end
--- Gets the POSITIONABLE of the HQ command center.
-- @param #COMMANDCENTER self
@@ -233,7 +243,7 @@ end
-- @return #list<Tasking.Mission#MISSION>
function COMMANDCENTER:GetMissions()
return self.Missions
return self.Missions or {}
end
--- Add a MISSION to be governed by the HQ command center.
@@ -322,7 +332,7 @@ end
--- Sets the menu structure of the Missions governed by the HQ command center.
-- @param #COMMANDCENTER self
function COMMANDCENTER:SetMenu()
self:F()
self:F2()
local MenuTime = timer.getTime()
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
@@ -331,7 +341,7 @@ function COMMANDCENTER:SetMenu()
end
for MissionID, Mission in pairs( self:GetMissions() or {} ) do
local Mission = Mission -- Tasking.Mission#MISSION
Mission = Mission -- Tasking.Mission#MISSION
Mission:RemoveMenu( MenuTime )
end
@@ -340,10 +350,133 @@ end
--- Gets the commandcenter menu structure governed by the HQ command center.
-- @param #COMMANDCENTER self
-- @return Core.Menu#MENU_COALITION
function COMMANDCENTER:GetMenu()
return self.CommandCenterMenu
function COMMANDCENTER:GetMenu( TaskGroup )
local MenuTime = timer.getTime()
self.CommandCenterMenus = self.CommandCenterMenus or {}
local CommandCenterMenu
local CommandCenterText = self:GetText()
CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText ):SetTime(MenuTime)
self.CommandCenterMenus[TaskGroup] = CommandCenterMenu
if self.AutoAssignTasks == false then
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task On", CommandCenterMenu, self.SetAutoAssignTasks, self, true ):SetTime(MenuTime):SetTag("AutoTask")
local AssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task", CommandCenterMenu, self.AssignRandomTask, self, TaskGroup ):SetTime(MenuTime):SetTag("AutoTask")
else
local AutoAssignTaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, "Assign Task Off", CommandCenterMenu, self.SetAutoAssignTasks, self, false ):SetTime(MenuTime):SetTag("AutoTask")
end
CommandCenterMenu:Remove( MenuTime, "AutoTask" )
return self.CommandCenterMenus[TaskGroup]
end
--- Assigns a random task to a TaskGroup.
-- @param #COMMANDCENTER self
-- @return #COMMANDCENTER
function COMMANDCENTER:AssignRandomTask( TaskGroup )
local Tasks = {}
for MissionID, Mission in pairs( self:GetMissions() ) do
local Mission = Mission -- Tasking.Mission#MISSION
local MissionTasks = Mission:GetGroupTasks( TaskGroup )
for MissionTaskName, MissionTask in pairs( MissionTasks or {} ) do
Tasks[#Tasks+1] = MissionTask
end
end
local Task = Tasks[ math.random( 1, #Tasks ) ] -- Tasking.Task#TASK
Task:SetAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) )
Task:AssignToGroup( TaskGroup )
end
--- Automatically assigns tasks to all TaskGroups.
-- @param #COMMANDCENTER self
-- @param #boolean AutoAssign true for ON and false or nil for OFF.
-- @return #COMMANDCENTER
function COMMANDCENTER:SetAutoAssignTasks( AutoAssign )
self.AutoAssignTasks = AutoAssign or false
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
self:GetMenu( TaskGroup )
end
if self.AutoAssignTasks == true then
self:ScheduleRepeat( 10, 30, 0, nil, self.AssignTasks, self )
else
self:ScheduleStop( self.AssignTasks )
end
end
--- Automatically assigns tasks to all TaskGroups.
-- @param #COMMANDCENTER self
function COMMANDCENTER:AssignTasks()
local GroupSet = self:AddGroups()
for GroupID, TaskGroup in pairs( GroupSet:GetSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if self:IsGroupAssigned( TaskGroup ) then
else
-- Only groups with planes or helicopters will receive automatic tasks.
-- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696
if TaskGroup:IsAir() then
self:AssignRandomTask( TaskGroup )
end
end
end
end
--- Get all the Groups active within the command center.
-- @param #COMMANDCENTER self
-- @return Core.Set#SET_GROUP
function COMMANDCENTER:AddGroups()
local GroupSet = SET_GROUP:New()
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
GroupSet = Mission:AddGroups( GroupSet )
end
return GroupSet
end
--- Checks of the TaskGroup has a Task.
-- @param #COMMANDCENTER self
-- @return #boolean
function COMMANDCENTER:IsGroupAssigned( TaskGroup )
local Assigned = false
for MissionID, Mission in pairs( self.Missions ) do
local Mission = Mission -- Tasking.Mission#MISSION
if Mission:IsGroupAssigned( TaskGroup ) then
Assigned = true
break
end
end
return Assigned
end
--- Checks of the COMMANDCENTER has a GROUP.
-- @param #COMMANDCENTER self
-- @param Wrapper.Group#GROUP
@@ -399,7 +532,7 @@ function COMMANDCENTER:MessageToCoalition( Message )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition )
self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() )
end
@@ -413,7 +546,7 @@ function COMMANDCENTER:MessageTypeToCoalition( Message, MessageType )
local CCCoalition = self:GetPositionable():GetCoalition()
--TODO: Fix coalition bug!
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition )
self:GetPositionable():MessageTypeToCoalition( Message, MessageType, CCCoalition, self:GetShortText() )
end

View File

@@ -29,7 +29,7 @@ MISSION = {
-- @param #string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players.
-- @param #string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field.
-- @param #string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}.
-- @param Dcs.DCSCoalitionWrapper.Object#coalition MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...
-- @param #string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...
-- @return #MISSION self
function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefing, MissionCoalition )
@@ -265,6 +265,8 @@ function MISSION:New( CommandCenter, MissionName, MissionPriority, MissionBriefi
end
--- FSM function for a MISSION
-- @param #MISSION self
-- @param #string From
@@ -375,24 +377,30 @@ function MISSION:GetScoring()
return self.Scoring
end
--- Get the groups for which TASKS are given in the mission
--- Gets the groups for which TASKS are given in the mission
-- @param #MISSION self
-- @param Core.Set#SET_GROUP GroupSet
-- @return Core.Set#SET_GROUP
function MISSION:GetGroups()
local SetGroup = SET_GROUP:New()
return self:AddGroups()
end
--- Adds the groups for which TASKS are given in the mission
-- @param #MISSION self
-- @param Core.Set#SET_GROUP GroupSet
-- @return Core.Set#SET_GROUP
function MISSION:AddGroups( GroupSet )
GroupSet = GroupSet or SET_GROUP:New()
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
local GroupSet = Task:GetGroups()
GroupSet:ForEachGroup(
function( TaskGroup )
SetGroup:Add( TaskGroup, TaskGroup )
end
)
GroupSet = Task:AddGroups( GroupSet )
end
return SetGroup
return GroupSet
end
@@ -441,11 +449,11 @@ do -- Group Assignment
local MissionGroupName = MissionGroup:GetName()
if self.AssignedGroups[MissionGroupName] == MissionGroup then
self:T( { "Mission is assigned to:", MissionGroup:GetName() } )
self:T2( { "Mission is assigned to:", MissionGroup:GetName() } )
return true
end
self:T( { "Mission is not assigned to:", MissionGroup:GetName() } )
self:T2( { "Mission is not assigned to:", MissionGroup:GetName() } )
return false
end
@@ -522,18 +530,13 @@ end
function MISSION:GetMenu( TaskGroup ) -- R2.1 -- Changed Menu Structure
local CommandCenter = self:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
--local MissionMenu = CommandCenterMenu:GetMenu( MissionName )
local CommandCenterMenu = CommandCenter:GetMenu( TaskGroup )
self.MissionGroupMenu = self.MissionGroupMenu or {}
self.MissionGroupMenu[TaskGroup] = self.MissionGroupMenu[TaskGroup] or {}
local GroupMenu = self.MissionGroupMenu[TaskGroup]
local CommandCenterText = CommandCenter:GetText()
CommandCenterMenu = MENU_GROUP:New( TaskGroup, CommandCenterText )
local MissionText = self:GetText()
self.MissionMenu = MENU_GROUP:New( TaskGroup, MissionText, CommandCenterMenu )
@@ -562,7 +565,7 @@ end
-- @param #string TaskName The Name of the @{Task} within the @{Mission}.
-- @return Tasking.Task#TASK The Task
-- @return #nil Returns nil if no task was found.
function MISSION:GetTask( TaskName )
function MISSION:GetTask( TaskName )
self:F( { TaskName } )
return self.Tasks[TaskName]
@@ -1003,9 +1006,28 @@ end
-- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
function MISSION:GetTasks()
return self.Tasks
return self.Tasks or {}
end
--- Get the relevant tasks of a TaskGroup.
-- @param #MISSION
-- @param Wrapper.Group#GROUP TaskGroup
-- @return #list<Tasking.Task#TASK>
function MISSION:GetGroupTasks( TaskGroup )
local Tasks = {}
for TaskID, Task in pairs( self:GetTasks() ) do
local Task = Task -- Tasking.Task#TASK
if Task:HasGroup( TaskGroup ) then
Tasks[#Tasks+1] = Task
end
end
return Tasks
end
--- Reports the briefing.
-- @param #MISSION self
-- @param Wrapper.Group#GROUP ReportGroup The group to which the report needs to be sent.

View File

@@ -161,7 +161,7 @@ TASK = {
-- @return #TASK self
function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
local self = BASE:Inherit( self, FSM_TASK:New() ) -- Tasking.Task#TASK
local self = BASE:Inherit( self, FSM_TASK:New( TaskName ) ) -- Tasking.Task#TASK
self:SetStartState( "Planned" )
self:AddTransition( "Planned", "Assign", "Assigned" )
@@ -169,10 +169,17 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
self:AddTransition( "Assigned", "Success", "Success" )
self:AddTransition( "Assigned", "Hold", "Hold" )
self:AddTransition( "Assigned", "Fail", "Failed" )
self:AddTransition( "Assigned", "Abort", "Aborted" )
self:AddTransition( { "Planned", "Assigned" }, "Abort", "Aborted" )
self:AddTransition( "Assigned", "Cancel", "Cancelled" )
self:AddTransition( "Assigned", "Goal", "*" )
self.Fsm = {}
local Fsm = self:GetUnitProcess()
Fsm:SetStartState( "Planned" )
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "Assigned", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "Assigned", "*" )
--- Goal Handler OnBefore for TASK
-- @function [parent=#TASK] OnBeforeGoal
-- @param #TASK self
@@ -209,6 +216,7 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
self:AddTransition( "*", "PlayerCrashed", "*" )
self:AddTransition( "*", "PlayerAborted", "*" )
self:AddTransition( "*", "PlayerRejected", "*" )
self:AddTransition( "*", "PlayerDead", "*" )
self:AddTransition( { "Failed", "Aborted", "Cancelled" }, "Replan", "Planned" )
self:AddTransition( "*", "TimeOut", "Cancelled" )
@@ -216,7 +224,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
self:F( "New TASK " .. TaskName )
self.Processes = {}
self.Fsm = {}
self.Mission = Mission
self.CommandCenter = Mission:GetCommandCenter()
@@ -229,7 +236,6 @@ function TASK:New( Mission, SetGroupAssign, TaskName, TaskType, TaskBriefing )
self:SetBriefing( TaskBriefing )
self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New()
self.TaskInfo = TASKINFO:New( self )
@@ -246,6 +252,7 @@ function TASK:GetUnitProcess( TaskUnit )
if TaskUnit then
return self:GetStateMachine( TaskUnit )
else
self.FsmTemplate = self.FsmTemplate or FSM_PROCESS:New()
return self.FsmTemplate
end
end
@@ -295,34 +302,61 @@ function TASK:JoinUnit( PlayerUnit, PlayerGroup )
return PlayerUnitAdded
end
--- Abort a PlayerUnit from a Task.
-- If the Unit was not part of the Task, false is returned.
-- If the Unit is part of the Task, true is returned.
--- A group rejecting a planned task.
-- @param #TASK self
-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task.
-- @param Wrapper.Group#GROUP PlayerGroup The group rejecting the task.
-- @return #TASK
function TASK:AbortGroup( PlayerGroup )
self:F( { PlayerGroup = PlayerGroup } )
function TASK:RejectGroup( PlayerGroup )
local PlayerGroups = self:GetGroups()
-- Is the PlayerGroup part of the PlayerGroups?
if PlayerGroups:IsIncludeObject( PlayerGroup ) then
-- Check if the PlayerGroup is already assigned to the Task. If yes, the PlayerGroup is aborted from the Task.
-- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task.
-- If yes, the PlayerGroup is aborted from the Task.
-- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group.
if self:IsStateAssigned() then
if self:IsStatePlanned() then
local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup )
if IsGroupAssigned then
local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName()
self:GetMission():GetCommandCenter():MessageToGroup( "Task " .. self:GetName() .. " has been rejected! We will select another task.", PlayerGroup )
self:UnAssignFromGroup( PlayerGroup )
self:PlayerRejected( PlayerGroup:GetUnit(1) )
end
end
end
return self
end
--- A group aborting the task.
-- @param #TASK self
-- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task.
-- @return #TASK
function TASK:AbortGroup( PlayerGroup )
local PlayerGroups = self:GetGroups()
-- Is the PlayerGroup part of the PlayerGroups?
if PlayerGroups:IsIncludeObject( PlayerGroup ) then
-- Check if the PlayerGroup is already assigned or is planned to be assigned to the Task.
-- If yes, the PlayerGroup is aborted from the Task.
-- If the PlayerUnit was the last unit of the PlayerGroup, the menu needs to be removed from the Group.
if self:IsStateAssigned() then
local IsGroupAssigned = self:IsGroupAssigned( PlayerGroup )
self:F( { IsGroupAssigned = IsGroupAssigned } )
if IsGroupAssigned then
local PlayerName = PlayerGroup:GetUnit(1):GetPlayerName()
--self:MessageToGroups( PlayerName .. " aborted Task " .. self:GetName() )
self:UnAssignFromGroup( PlayerGroup )
--self:Abort()
-- Now check if the task needs to go to hold...
-- It will go to hold, if there are no players in the mission...
PlayerGroups:Flush( self )
local IsRemaining = false
for GroupName, AssignedGroup in pairs( PlayerGroups:GetSet() or {} ) do
@@ -347,11 +381,10 @@ function TASK:AbortGroup( PlayerGroup )
return self
end
--- A PlayerUnit crashed in a Task. Abort the Player.
-- If the Unit was not part of the Task, false is returned.
-- If the Unit is part of the Task, true is returned.
--- A group crashing and thus aborting from the task.
-- @param #TASK self
-- @param Wrapper.Unit#UNIT PlayerUnit The CLIENT or UNIT of the Player aborting the Task.
-- @param Wrapper.Group#GROUP PlayerGroup The group aborting the task.
-- @return #TASK
function TASK:CrashGroup( PlayerGroup )
self:F( { PlayerGroup = PlayerGroup } )
@@ -413,9 +446,29 @@ end
-- @param #TASK self
-- @return Core.Set#SET_GROUP
function TASK:GetGroups()
return self.SetGroup
end
--- Gets the SET_GROUP assigned to the TASK.
-- @param #TASK self
-- @param Core.Set#SET_GROUP GroupSet
-- @return Core.Set#SET_GROUP
function TASK:AddGroups( GroupSet )
GroupSet = GroupSet or SET_GROUP:New()
self.SetGroup:ForEachGroup(
--- @param Wrapper.Group#GROUP GroupSet
function( GroupItem )
GroupSet:Add( GroupItem:GetName(), GroupItem)
end
)
return GroupSet
end
do -- Group Assignment
--- Returns if the @{Task} is assigned to the Group.
@@ -500,6 +553,16 @@ end
do -- Group Assignment
--- @param #TASK self
-- @param Actions.Act_Assign#ACT_ASSIGN AcceptClass
function TASK:SetAssignMethod( AcceptClass )
local ProcessTemplate = self:GetUnitProcess()
ProcessTemplate:SetProcess( "Planned", "Accept", AcceptClass ) -- Actions.Act_Assign#ACT_ASSIGN
end
--- Assign the @{Task} to a @{Group}.
-- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
@@ -598,6 +661,8 @@ function TASK:UnAssignFromUnit( TaskUnit )
self:RemoveStateMachine( TaskUnit )
-- If a Task Control Menu had been set, then this will be removed.
self:RemoveTaskControlMenu( TaskUnit )
return self
end
@@ -620,9 +685,11 @@ function TASK:MessageToGroups( Message )
local Mission = self:GetMission()
local CC = Mission:GetCommandCenter()
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() )
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do
TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() == true then
CC:MessageToGroup( Message, TaskGroup, TaskGroup:GetName() )
end
end
end
@@ -632,10 +699,11 @@ end
function TASK:SendBriefingToAssignedGroups()
self:F2()
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do
if self:IsGroupAssigned( TaskGroup ) then
TaskGroup:Message( self.TaskBriefing, 60 )
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if TaskGroup:IsAlive() then
if self:IsGroupAssigned( TaskGroup ) then
TaskGroup:Message( self.TaskBriefing, 60 )
end
end
end
end
@@ -646,9 +714,11 @@ end
function TASK:UnAssignFromGroups()
self:F2()
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do
if self:IsGroupAssigned(TaskGroup) then
self:UnAssignFromGroup( TaskGroup )
for TaskGroupName, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if TaskGroup:IsAlive() == true then
if self:IsGroupAssigned(TaskGroup) then
self:UnAssignFromGroup( TaskGroup )
end
end
end
end
@@ -661,13 +731,15 @@ end
function TASK:HasAliveUnits()
self:F()
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do
if self:IsStateAssigned() then
if self:IsGroupAssigned( TaskGroup ) then
for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do
if TaskUnit:IsAlive() then
self:T( { HasAliveUnits = true } )
return true
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if TaskGroup:IsAlive() == true then
if self:IsStateAssigned() then
if self:IsGroupAssigned( TaskGroup ) then
for TaskUnitID, TaskUnit in pairs( TaskGroup:GetUnits() ) do
if TaskUnit:IsAlive() then
self:T( { HasAliveUnits = true } )
return true
end
end
end
end
@@ -686,7 +758,8 @@ function TASK:SetMenu( MenuTime ) --R2.1 Mission Reports and Task Reports added.
self:F( { self:GetName(), MenuTime } )
--self.SetGroup:Flush()
for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetAliveSet() ) do
--for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetAliveSet() ) do
for TaskGroupID, TaskGroupData in pairs( self.SetGroup:GetSet() ) do
local TaskGroup = TaskGroupData -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then
@@ -729,21 +802,14 @@ function TASK:SetPlannedMenuForGroup( TaskGroup, MenuTime )
local Mission = self:GetMission()
local MissionName = Mission:GetName()
local CommandCenter = Mission:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
local MissionMenu = Mission:GetMenu( TaskGroup )
local TaskType = self:GetType()
local TaskPlayerCount = self:GetPlayerCount()
local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount )
-- local TaskText = string.format( "%s%s", self:GetName(), TaskPlayerString ) --, TaskThreatLevelString )
local TaskText = string.format( "%s", self:GetName() )
local TaskName = string.format( "%s", self:GetName() )
local MissionMenu = Mission:GetMenu( TaskGroup )
--local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime )
--local MissionMenu = Mission:GetMenu( TaskGroup )
self.MenuPlanned = self.MenuPlanned or {}
self.MenuPlanned[TaskGroup] = MENU_GROUP_DELAYED:New( TaskGroup, "Join Planned Task", MissionMenu, Mission.MenuReportTasksPerStatus, Mission, TaskGroup, "Planned" ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_DELAYED:New( TaskGroup, TaskType, self.MenuPlanned[TaskGroup] ):SetTime( MenuTime ):SetTag( "Tasking" )
@@ -768,26 +834,24 @@ end
function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime )
self:F( { TaskGroup:GetName(), MenuTime } )
local Mission = self:GetMission()
local MissionName = Mission:GetName()
local CommandCenter = Mission:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
local TaskType = self:GetType()
local TaskPlayerCount = self:GetPlayerCount()
local TaskPlayerString = string.format( " (%dp)", TaskPlayerCount )
local TaskText = string.format( "%s%s", self:GetName(), TaskPlayerString ) --, TaskThreatLevelString )
local TaskName = string.format( "%s", self:GetName() )
local MissionMenu = Mission:GetMenu( TaskGroup )
-- local MissionMenu = MENU_GROUP:New( TaskGroup, MissionName, CommandCenterMenu ):SetTime( MenuTime )
-- local MissionMenu = Mission:GetMenu( TaskGroup )
self.MenuAssigned = self.MenuAssigned or {}
self.MenuAssigned[TaskGroup] = MENU_GROUP_DELAYED:New( TaskGroup, string.format( "Assigned Task %s", TaskName ), MissionMenu ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Abort Task" ), self.MenuAssigned[TaskGroup], self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
local MarkMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Mark Task Location on Map" ), self.MenuAssigned[TaskGroup], self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_COMMAND_DELAYED:New( TaskGroup, string.format( "Report Task Details" ), self.MenuAssigned[TaskGroup], self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
for UnitName, TaskUnit in pairs( TaskGroup:GetPlayerUnits() ) do
local TaskUnit = TaskUnit -- Wrapper.Unit#UNIT
if TaskUnit then
local MenuControl = self:GetTaskControlMenu( TaskUnit )
local TaskControl = MENU_GROUP:New( TaskGroup, "Control Task", MenuControl ):SetTime( MenuTime ):SetTag( "Tasking" )
if self:IsStateAssigned() then
local TaskMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Abort Task" ), TaskControl, self.MenuTaskAbort, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
end
local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
end
end
return self
end
@@ -799,10 +863,12 @@ end
function TASK:RemoveMenu( MenuTime )
self:F( { self:GetName(), MenuTime } )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetAliveSet() ) do
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then
self:RefreshMenus( TaskGroup, MenuTime )
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if TaskGroup:IsAlive() == true then
local TaskGroup = TaskGroup -- Wrapper.Group#GROUP
if TaskGroup:IsAlive() == true and TaskGroup:GetPlayerNames() then
self:RefreshMenus( TaskGroup, MenuTime )
end
end
end
end
@@ -818,9 +884,6 @@ function TASK:RefreshMenus( TaskGroup, MenuTime )
local Mission = self:GetMission()
local MissionName = Mission:GetName()
local CommandCenter = Mission:GetCommandCenter()
local CommandCenterMenu = CommandCenter:GetMenu()
local MissionMenu = Mission:GetMenu( TaskGroup )
local TaskName = self:GetName()
@@ -852,7 +915,6 @@ function TASK:RemoveAssignedMenuForGroup( TaskGroup )
local Mission = self:GetMission()
local MissionName = Mission:GetName()
local MissionMenu = Mission:GetMenu( TaskGroup )
if MissionMenu then
@@ -1210,12 +1272,16 @@ function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName )
--- This test is required, because the state transition will be fired also when the state does not change in case of an event.
if From ~= "Assigned" then
self:F( { From, Event, To, PlayerUnit:GetName(), PlayerName } )
self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned." )
local PlayerNames = self:GetPlayerNames()
local PlayerText = REPORT:New()
for PlayerName, TaskName in pairs( PlayerNames ) do
PlayerText:Add( PlayerName )
end
self:GetMission():GetCommandCenter():MessageToCoalition( "Task " .. self:GetName() .. " is assigned to players " .. PlayerText:Text(",") .. ". Good Luck!" )
-- Set the total Progress to be achieved.
self:SetGoalTotal() -- Polymorphic to set the initial goal total!
if self.Dispatcher then
@@ -1231,7 +1297,7 @@ function TASK:onenterAssigned( From, Event, To, PlayerUnit, PlayerName )
self:SetMenu()
self:F( { "--> Task Assigned", TaskName = self:GetName(), Mission = self:GetMission():GetName() } )
self:F( { "--> Task Player Names", PlayerNames = self:GetPlayerNames() } )
self:F( { "--> Task Player Names", PlayerNames = PlayerNames } )
end
end
@@ -1273,6 +1339,7 @@ function TASK:onenterAborted( From, Event, To )
end
--- FSM function for a TASK
-- @param #TASK self
-- @param #string From
@@ -1441,11 +1508,13 @@ function TASK:GetPlayerCount() --R2.1 Get a count of the players.
local PlayerCount = 0
-- Loop each Unit active in the Task, and find Player Names.
for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetAliveSet() ) do
for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do
local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
PlayerCount = PlayerCount + #PlayerNames
if PlayerGroup:IsAlive() == true then
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
PlayerCount = PlayerCount + #PlayerNames
end
end
end
@@ -1461,12 +1530,14 @@ function TASK:GetPlayerNames() --R2.1 Get a map of the players.
local PlayerNameMap = {}
-- Loop each Unit active in the Task, and find Player Names.
for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetAliveSet() ) do
for TaskGroupID, PlayerGroup in pairs( self:GetGroups():GetSet() ) do
local PlayerGroup = PlayerGroup -- Wrapper.Group#GROUP
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
for PlayerNameID, PlayerName in pairs( PlayerNames ) do
PlayerNameMap[PlayerName] = PlayerGroup
if PlayerGroup:IsAlive() == true then
if self:IsGroupAssigned( PlayerGroup ) then
local PlayerNames = PlayerGroup:GetPlayerNames()
for PlayerNameID, PlayerName in pairs( PlayerNames ) do
PlayerNameMap[PlayerName] = PlayerGroup
end
end
end
end
@@ -1499,7 +1570,7 @@ function TASK:ReportDetails( ReportGroup )
local PlayerReport = REPORT:New()
for PlayerName, PlayerGroup in pairs( PlayerNames ) do
PlayerReport:Add( "Group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName )
PlayerReport:Add( "Players group " .. PlayerGroup:GetCallsign() .. ": " .. PlayerName )
end
local Players = PlayerReport:Text()
@@ -1595,3 +1666,65 @@ do -- Additional Task Scoring and Task Progress
end
end
do -- Task Control Menu
-- The Task Control Menu is a menu attached to the task at the main menu to quickly be able to do actions in the task.
-- The Task Control Menu can only be shown when the task is assigned to the player.
-- The Task Control Menu is linked to the process executing the task, so no task menu can be set to the main static task definition.
--- Init Task Control Menu
-- @param #TASK self
-- @param Wrapper.Unit#UNIT TaskUnit The @{Unit} that contains a player.
-- @return Task Control Menu Refresh ID
function TASK:InitTaskControlMenu( TaskUnit )
self.TaskControlMenuTime = timer.getTime()
return self.TaskControlMenuTime
end
--- Get Task Control Menu
-- @param #TASK self
-- @param Wrapper.Unit#UNIT TaskUnit The @{Unit} that contains a player.
-- @return Core.Menu#MENU_GROUP TaskControlMenu The Task Control Menu
function TASK:GetTaskControlMenu( TaskUnit, TaskName )
TaskName = TaskName or ""
local TaskGroup = TaskUnit:GetGroup()
local TaskPlayerCount = TaskGroup:GetPlayerCount()
if TaskPlayerCount <= 1 then
self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control" ):SetTime( self.TaskControlMenuTime )
else
self.TaskControlMenu = MENU_GROUP:New( TaskUnit:GetGroup(), "Task " .. self:GetName() .. " control for " .. TaskUnit:GetPlayerName() ):SetTime( self.TaskControlMenuTime )
end
return self.TaskControlMenu
end
--- Remove Task Control Menu
-- @param #TASK self
-- @param Wrapper.Unit#UNIT TaskUnit The @{Unit} that contains a player.
function TASK:RemoveTaskControlMenu( TaskUnit )
if self.TaskControlMenu then
self.TaskControlMenu:Remove()
self.TaskControlMenu = nil
end
end
--- Refresh Task Control Menu
-- @param #TASK self
-- @param Wrapper.Unit#UNIT TaskUnit The @{Unit} that contains a player.
-- @param MenuTime The refresh time that was used to refresh the Task Control Menu items.
-- @param MenuTag The tag.
function TASK:RefreshTaskControlMenu( TaskUnit, MenuTime, MenuTag )
if self.TaskControlMenu then
self.TaskControlMenu:Remove( MenuTime, MenuTag )
end
end
end

View File

@@ -249,17 +249,15 @@ end
function TASKINFO:AddCargoSet( SetCargo, Order, Detail, Keep )
local CargoReport = REPORT:New()
CargoReport:Add( "" )
SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo )
local CargoType = Cargo:GetType()
local CargoName = Cargo:GetName()
local CargoCoordinate = Cargo:GetCoordinate()
CargoReport:Add( string.format( '"%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) )
CargoReport:Add( string.format( ' - %s (%s) %s - status %s ', Cargo:GetName(), Cargo:GetType(), Cargo:GetTransportationMethod(), Cargo:GetCurrentState() ) )
end
)
self:AddInfo( "CargoSet", CargoReport:Text(), Order, Detail, Keep )
self:AddInfo( "Cargo", CargoReport:Text(), Order, Detail, Keep )
return self
end
@@ -319,7 +317,7 @@ function TASKINFO:Report( Report, Detail, ReportGroup )
local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToStringWind( ReportGroup:GetUnit(1), nil, self )
end
if Key == "CargoSet" then
if Key == "Cargo" then
local DataText = Data.Data -- #string
Text = DataText
end

View File

@@ -62,8 +62,6 @@ do -- TASK_A2A
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
@@ -85,6 +83,15 @@ do -- TASK_A2A
Fsm:AddTransition( "Failed", "Fail", "Failed" )
---- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #TASK_CARGO Task
function Fsm:OnLeaveAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:SelectAction()
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit

View File

@@ -61,9 +61,6 @@ do -- TASK_A2G
local Fsm = self:GetUnitProcess()
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToRendezVous", Rejected = "Reject" } )
Fsm:AddTransition( "Assigned", "RouteToRendezVous", "RoutingToRendezVous" )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtRendezVous" } )
Fsm:AddProcess ( "RoutingToRendezVous", "RouteToRendezVousZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtRendezVous" } )
@@ -85,6 +82,18 @@ do -- TASK_A2G
Fsm:AddTransition( "Failed", "Fail", "Failed" )
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_A2G#TASK_A2G Task
function Fsm:onafterAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
-- Determine the first Unit from the self.RendezVousSetUnit
self:RouteToRendezVous()
end
--- Test
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit

View File

@@ -13,6 +13,7 @@
-- The following classes are important to consider:
--
-- * @{#TASK_CARGO_TRANSPORT}: Defines a task for a human player to transport a set of cargo between various zones.
-- * @{#TASK_CARGO_CSAR}: Defines a task for a human player to Search and Rescue wounded pilots.
--
-- ===
--
@@ -22,7 +23,7 @@
--
-- ===
--
-- @module Task_Cargo
-- @module Tasking.Task_Cargo
do -- TASK_CARGO
@@ -166,24 +167,69 @@ do -- TASK_CARGO
self.DeployZones = {} -- setmetatable( {}, { __mode = "v" } ) -- weak table on value
self:AddTransition( "*", "CargoDeployed", "*" )
--- CargoDeployed Handler OnBefore for TASK_CARGO
-- @function [parent=#TASK_CARGO] OnBeforeCargoDeployed
-- @param #TASK_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
-- @return #boolean
--- CargoDeployed Handler OnAfter for TASK_CARGO
-- @function [parent=#TASK_CARGO] OnAfterCargoDeployed
-- @param #TASK_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
self:AddTransition( "*", "CargoPickedUp", "*" )
--- CargoPickedUp Handler OnBefore for TASK_CARGO
-- @function [parent=#TASK_CARGO] OnBeforeCargoPickedUp
-- @param #TASK_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @return #boolean
--- CargoPickedUp Handler OnAfter for TASK_CARGO
-- @function [parent=#TASK_CARGO] OnAfterCargoPickedUp
-- @param #TASK_CARGO self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
local Fsm = self:GetUnitProcess()
Fsm:SetStartState( "Planned" )
-- Fsm:SetStartState( "Planned" )
--
-- Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } )
Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } )
Fsm:AddTransition( { "Planned", "Assigned", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded", "Landed", "Boarding" }, "SelectAction", "*" )
Fsm:AddTransition( { "Planned", "Assigned", "Cancelled", "WaitingForCommand", "ArrivedAtPickup", "ArrivedAtDeploy", "Boarded", "UnBoarded", "Loaded", "UnLoaded", "Landed", "Boarding" }, "SelectAction", "*" )
Fsm:AddTransition( "*", "RouteToPickup", "RoutingToPickup" )
Fsm:AddProcess ( "RoutingToPickup", "RouteToPickupPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtPickup", Cancelled = "CancelRouteToPickup" } )
Fsm:AddTransition( "Arrived", "ArriveAtPickup", "ArrivedAtPickup" )
Fsm:AddTransition( "Cancelled", "CancelRouteToPickup", "WaitingForCommand" )
Fsm:AddTransition( "Cancelled", "CancelRouteToPickup", "Cancelled" )
Fsm:AddTransition( "*", "RouteToDeploy", "RoutingToDeploy" )
Fsm:AddProcess ( "RoutingToDeploy", "RouteToDeployZone", ACT_ROUTE_ZONE:New(), { Arrived = "ArriveAtDeploy", Cancelled = "CancelRouteToDeploy" } )
Fsm:AddTransition( "Arrived", "ArriveAtDeploy", "ArrivedAtDeploy" )
Fsm:AddTransition( "Cancelled", "CancelRouteToDeploy", "WaitingForCommand" )
Fsm:AddTransition( "Cancelled", "CancelRouteToDeploy", "Cancelled" )
Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy", "Landing" }, "Land", "Landing" )
Fsm:AddTransition( "Landing", "Landed", "Landed" )
@@ -192,10 +238,14 @@ do -- TASK_CARGO
Fsm:AddTransition( "AwaitBoarding", "Board", "Boarding" )
Fsm:AddTransition( "Boarding", "Boarded", "Boarded" )
Fsm:AddTransition( "*", "Load", "Loaded" )
Fsm:AddTransition( "*", "PrepareUnBoarding", "AwaitUnBoarding" )
Fsm:AddTransition( "AwaitUnBoarding", "UnBoard", "UnBoarding" )
Fsm:AddTransition( "UnBoarding", "UnBoarded", "UnBoarded" )
Fsm:AddTransition( "*", "Unload", "Unloaded" )
Fsm:AddTransition( "*", "Planned", "Planned" )
@@ -204,28 +254,31 @@ do -- TASK_CARGO
Fsm:AddTransition( "Failed", "Fail", "Failed" )
---- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #TASK_CARGO Task
function Fsm:OnAfterAssigned( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
self:SelectAction()
end
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_CARGO#TASK_CARGO Task
-- @param #TASK_CARGO Task
function Fsm:onafterSelectAction( TaskUnit, Task )
local TaskUnitName = TaskUnit:GetName()
self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
local MenuTime = timer.getTime()
TaskUnit.Menu = MENU_GROUP:New( TaskUnit:GetGroup(), Task:GetName() .. " @ " .. TaskUnit:GetName() )
local MenuTime = Task:InitTaskControlMenu( TaskUnit )
local MenuControl = Task:GetTaskControlMenu( TaskUnit )
local CargoItemCount = TaskUnit:CargoItemCount()
--Task:GetMission():GetCommandCenter():MessageToGroup( "Cargo in carrier: " .. CargoItemCount, TaskUnit:GetGroup() )
Task.SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo )
if Cargo:IsAlive() then
@@ -234,18 +287,20 @@ do -- TASK_CARGO
-- MENU_GROUP_COMMAND:New(
-- TaskUnit:GetGroup(),
-- "Cancel Route " .. Cargo.Name,
-- TaskUnit.Menu,
-- MenuControl,
-- self.MenuRouteToPickupCancel,
-- self,
-- Cargo
-- ):SetTime(MenuTime)
-- end
self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } )
--self:F( { CargoUnloaded = Cargo:IsUnLoaded(), CargoLoaded = Cargo:IsLoaded(), CargoItemCount = CargoItemCount } )
local TaskGroup = TaskUnit:GetGroup()
if Cargo:IsUnLoaded() then
if CargoItemCount <= Task.CargoLimit then
if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if CargoItemCount < 1 then
if Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then
local NotInDeployZones = true
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if Cargo:IsInZone( DeployZone ) then
@@ -254,28 +309,82 @@ do -- TASK_CARGO
end
if NotInDeployZones then
if not TaskUnit:InAir() then
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Board cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime)
TaskUnit.Menu:SetTime( MenuTime )
if Cargo:CanBoard() == true then
if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then
Cargo:Report( "Ready for boarding.", "board", TaskUnit:GetGroup() )
local BoardMenu = MENU_GROUP:New( TaskGroup, "Board cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, BoardMenu, self.MenuBoardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
Cargo:Report( "Board at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() .. "." ), "reporting", TaskUnit:GetGroup() )
end
else
if Cargo:CanLoad() == true then
if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then
Cargo:Report( "Ready for loading.", "load", TaskUnit:GetGroup() )
local LoadMenu = MENU_GROUP:New( TaskGroup, "Load cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, LoadMenu, self.MenuLoadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
Cargo:Report( "Load at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. " within " .. Cargo.NearRadius .. ".", "reporting", TaskUnit:GetGroup() )
end
else
if Cargo:CanSlingload() == true then
if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then
Cargo:Report( "Ready for slingloading.", "slingload", TaskUnit:GetGroup() )
else
Cargo:Report( "Slingload at " .. Cargo:GetCoordinate():ToString( TaskUnit:GetGroup() ) .. ".", "reporting", TaskUnit:GetGroup() )
end
end
end
end
else
Cargo:ReportResetAll( TaskUnit:GetGroup() )
end
end
else
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Pickup cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime)
TaskUnit.Menu:SetTime( MenuTime )
if not Cargo:IsDeployed() == true then
local RouteToPickupMenu = MENU_GROUP:New( TaskGroup, "Route to pickup cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, RouteToPickupMenu, self.MenuRouteToPickup, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
Cargo:ReportResetAll( TaskUnit:GetGroup() )
end
end
end
-- Cargo in deployzones are flagged as deployed.
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if Cargo:IsInZone( DeployZone ) then
Task:E( { CargoIsDeployed = Task.CargoDeployed and "true" or "false" } )
if Cargo:IsDeployed() == false then
Cargo:SetDeployed( true )
-- Now we call a callback method to handle the CargoDeployed event.
Task:E( { CargoIsAlive = Cargo:IsAlive() and "true" or "false" } )
if Cargo:IsAlive() then
Task:CargoDeployed( TaskUnit, Cargo, DeployZone )
end
end
end
end
end
if Cargo:IsLoaded() == true and Cargo:IsLoadedInCarrier( TaskUnit ) == true then
if not TaskUnit:InAir() then
if Cargo:CanUnboard() == true then
local UnboardMenu = MENU_GROUP:New( TaskGroup, "Unboard cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnboardMenu, self.MenuUnboardCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
else
if Cargo:CanUnload() == true then
local UnloadMenu = MENU_GROUP:New( TaskGroup, "Unload cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), Cargo.Name, UnloadMenu, self.MenuUnloadCargo, self, Cargo ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
end
end
end
if Cargo:IsLoaded() then
if not TaskUnit:InAir() then
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Unboard cargo " .. Cargo.Name, TaskUnit.Menu, self.MenuUnBoardCargo, self, Cargo ):SetTime(MenuTime)
TaskUnit.Menu:SetTime( MenuTime )
end
-- Deployzones are optional zones that can be selected to request routing information.
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if not Cargo:IsInZone( DeployZone ) then
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Route to Deploy cargo at " .. DeployZoneName, TaskUnit.Menu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime)
TaskUnit.Menu:SetTime( MenuTime )
end
-- Deployzones are optional zones that can be selected to request routing information.
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if not Cargo:IsInZone( DeployZone ) then
local RouteToDeployMenu = MENU_GROUP:New( TaskGroup, "Route to deploy cargo", MenuControl ):SetTime( MenuTime ):SetTag( "Cargo" )
MENU_GROUP_COMMAND:New( TaskUnit:GetGroup(), "Zone " .. DeployZoneName, RouteToDeployMenu, self.MenuRouteToDeploy, self, DeployZone ):SetTime(MenuTime):SetTag("Cargo"):SetRemoveParent()
end
end
end
@@ -283,10 +392,9 @@ do -- TASK_CARGO
end
)
TaskUnit.Menu:Remove( MenuTime )
Task:RefreshTaskControlMenu( TaskUnit, MenuTime, "Cargo" )
self:__SelectAction( -15 )
self:__SelectAction( -1 )
end
@@ -294,21 +402,31 @@ do -- TASK_CARGO
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
-- @param #TASK_CARGO Task
function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
TaskUnit.Menu:Remove()
--local MenuControl = Task:GetTaskControlMenu( TaskUnit )
--MenuControl:Remove()
end
function Fsm:MenuBoardCargo( Cargo )
self:__PrepareBoarding( 1.0, Cargo )
end
function Fsm:MenuUnBoardCargo( Cargo, DeployZone )
function Fsm:MenuLoadCargo( Cargo )
self:__Load( 1.0, Cargo )
end
function Fsm:MenuUnboardCargo( Cargo, DeployZone )
self:__PrepareUnBoarding( 1.0, Cargo, DeployZone )
end
function Fsm:MenuUnloadCargo( Cargo, DeployZone )
self:__Unload( 1.0, Cargo, DeployZone )
end
function Fsm:MenuRouteToPickup( Cargo )
self:__RouteToPickup( 1.0, Cargo )
end
@@ -335,7 +453,7 @@ do -- TASK_CARGO
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if Cargo:IsAlive() then
self.Cargo = Cargo -- Core.Cargo#CARGO
self.Cargo = Cargo -- Cargo.Cargo#CARGO
Task:SetCargoPickup( self.Cargo, TaskUnit )
self:__RouteToPickupPoint( -0.1 )
end
@@ -350,7 +468,6 @@ do -- TASK_CARGO
function Fsm:onafterArriveAtPickup( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsAlive() then
self.Cargo:Smoke( Task:GetSmokeColor(), 15 )
if TaskUnit:IsAir() then
Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup() )
self:__Land( -0.1, "Pickup" )
@@ -367,6 +484,7 @@ do -- TASK_CARGO
function Fsm:onafterCancelRouteToPickup( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to Cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() )
self:__SelectAction( -0.1 )
end
@@ -404,6 +522,7 @@ do -- TASK_CARGO
function Fsm:onafterCancelRouteToDeploy( TaskUnit, Task )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
Task:GetMission():GetCommandCenter():MessageToGroup( "Cancelled routing to deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() )
self:__SelectAction( -0.1 )
end
@@ -415,19 +534,30 @@ do -- TASK_CARGO
function Fsm:onafterLand( TaskUnit, Task, From, Event, To, Action )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsAlive() then
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
self:__Land( -10, Action )
if Action == "Pickup" then
if self.Cargo:IsAlive() then
if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
self:__Land( -10, Action )
else
Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at pickup location...", TaskUnit:GetGroup() )
self:__Landed( -0.1, Action )
end
else
Task:GetMission():GetCommandCenter():MessageToGroup( "Landed ...", TaskUnit:GetGroup() )
self:__Landed( -0.1, Action )
self:__RouteToPickup( -0.1, self.Cargo )
end
else
if Action == "Pickup" then
self:__RouteToPickupZone( -0.1 )
end
else
if TaskUnit:IsAlive() then
if TaskUnit:IsInZone( self.DeployZone ) then
if TaskUnit:InAir() then
self:__Land( -10, Action )
else
Task:GetMission():GetCommandCenter():MessageToGroup( "Landed at deploy zone " .. self.DeployZone:GetName(), TaskUnit:GetGroup() )
self:__Landed( -0.1, Action )
end
else
self:__RouteToDeployZone( -0.1 )
self:__RouteToDeploy( -0.1, self.Cargo )
end
end
end
@@ -439,18 +569,28 @@ do -- TASK_CARGO
function Fsm:onafterLanded( TaskUnit, Task, From, Event, To, Action )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if self.Cargo:IsAlive() then
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
self:__Land( -0.1, Action )
if Action == "Pickup" then
if self.Cargo:IsAlive() then
if self.Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
self:__Land( -0.1, Action )
else
self:__SelectAction( -0.1 )
end
else
self:__SelectAction( -0.1 )
self:__RouteToPickup( -0.1, self.Cargo )
end
else
if Action == "Pickup" then
self:__RouteToPickupZone( -0.1 )
end
else
if TaskUnit:IsAlive() then
if TaskUnit:IsInZone( self.DeployZone ) then
if TaskUnit:InAir() then
self:__Land( -10, Action )
else
self:__SelectAction( -0.1 )
end
else
self:__RouteToDeployZone( -0.1 )
self:__RouteToDeploy( -0.1, self.Cargo )
end
end
end
@@ -463,30 +603,30 @@ do -- TASK_CARGO
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
if Cargo and Cargo:IsAlive() then
self.Cargo = Cargo -- Core.Cargo#CARGO_GROUP
self:__Board( -0.1 )
self:__Board( -0.1, Cargo )
end
end
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoard( TaskUnit, Task )
function Fsm:onafterBoard( TaskUnit, Task, From, Event, To, Cargo )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } )
function self.Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess )
function Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess )
self:F({From, Event, To, TaskUnit, TaskProcess })
TaskProcess:__Boarded( 0.1 )
TaskProcess:__Boarded( 0.1, self )
end
if self.Cargo:IsAlive() then
if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then
if Cargo:IsAlive() then
if Cargo:IsInLoadRadius( TaskUnit:GetPointVec2() ) then
if TaskUnit:InAir() then
--- ABORT the boarding. Split group if any and go back to select action.
else
self.Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() )
if not self.Cargo:IsBoarding() then
self.Cargo:Board( TaskUnit, 20, self )
Cargo:MessageToGroup( "Boarding ...", TaskUnit:GetGroup() )
if not Cargo:IsBoarding() then
Cargo:Board( TaskUnit, 20, self )
end
end
else
@@ -499,25 +639,37 @@ do -- TASK_CARGO
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterBoarded( TaskUnit, Task )
function Fsm:onafterBoarded( TaskUnit, Task, From, Event, To, Cargo )
local TaskUnitName = TaskUnit:GetName()
self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "Boarded ...", TaskUnit:GetGroup() )
Cargo:MessageToGroup( "Boarded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() )
TaskUnit:AddCargo( self.Cargo )
self:__Load( -0.1, Cargo )
self:__SelectAction( 1 )
end
-- TODO:I need to find a more decent solution for this.
Task:E( { CargoPickedUp = Task.CargoPickedUp } )
if self.Cargo:IsAlive() then
if Task.CargoPickedUp then
Task:CargoPickedUp( TaskUnit, self.Cargo )
end
--- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterLoad( TaskUnit, Task, From, Event, To, Cargo )
local TaskUnitName = TaskUnit:GetName()
self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
if not Cargo:IsLoaded() then
Cargo:Load( TaskUnit )
end
Cargo:MessageToGroup( "Loaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() )
TaskUnit:AddCargo( Cargo )
Task:CargoPickedUp( TaskUnit, Cargo )
self:SelectAction( -1 )
end
@@ -530,7 +682,7 @@ do -- TASK_CARGO
-- @param To
-- @param Cargo
-- @param Core.Zone#ZONE_BASE DeployZone
function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo )
function Fsm:onafterPrepareUnBoarding( TaskUnit, Task, From, Event, To, Cargo )
self:F( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID(), From, Event, To, Cargo } )
self.Cargo = Cargo
@@ -568,9 +720,9 @@ do -- TASK_CARGO
if self.Cargo:IsAlive() then
self.Cargo:MessageToGroup( "UnBoarding ...", TaskUnit:GetGroup() )
if DeployZone then
self.Cargo:UnBoard( DeployZone:GetPointVec2(), 400, self )
self.Cargo:UnBoard( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self )
else
self.Cargo:UnBoard( TaskUnit:GetPointVec2():AddX(60), 400, self )
self.Cargo:UnBoard( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self )
end
end
end
@@ -585,35 +737,35 @@ do -- TASK_CARGO
local TaskUnitName = TaskUnit:GetName()
self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
self.Cargo:MessageToGroup( "UnBoarded ...", TaskUnit:GetGroup() )
self.Cargo:MessageToGroup( "UnBoarded cargo " .. self.Cargo:GetName(), TaskUnit:GetGroup() )
TaskUnit:RemoveCargo( self.Cargo )
self:Unload( self.Cargo )
end
local NotInDeployZones = true
for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do
if self.Cargo:IsInZone( DeployZone ) then
NotInDeployZones = false
---
-- @param #FSM_PROCESS self
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param Tasking.Task_Cargo#TASK_CARGO Task
function Fsm:onafterUnload( TaskUnit, Task, From, Event, To, Cargo, DeployZone )
local TaskUnitName = TaskUnit:GetName()
self:F( { TaskUnit = TaskUnitName, Task = Task and Task:GetClassNameAndID() } )
if not Cargo:IsUnLoaded() then
if DeployZone then
Cargo:UnLoad( DeployZone:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self )
else
Cargo:UnLoad( TaskUnit:GetCoordinate():GetRandomCoordinateInRadius( 25, 10 ), 400, self )
end
end
TaskUnit:RemoveCargo( Cargo )
if NotInDeployZones == false then
self.Cargo:SetDeployed( true )
end
-- TODO:I need to find a more decent solution for this.
Task:E( { CargoDeployed = Task.CargoDeployed and "true" or "false" } )
Task:E( { CargoIsAlive = self.Cargo:IsAlive() and "true" or "false" } )
if self.Cargo:IsAlive() then
if Task.CargoDeployed then
Task:CargoDeployed( TaskUnit, self.Cargo, self.DeployZone )
end
end
Cargo:MessageToGroup( "Unloaded cargo " .. Cargo:GetName(), TaskUnit:GetGroup() )
self:Planned()
self:__SelectAction( 1 )
end
return self
end
@@ -676,12 +828,16 @@ do -- TASK_CARGO
self:F({Cargo, TaskUnit})
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local MenuTime = self:InitTaskControlMenu( TaskUnit )
local MenuControl = self:GetTaskControlMenu( TaskUnit )
local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToPickup", "RouteToPickupPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT
ActRouteCargo:Reset()
ActRouteCargo:SetCoordinate( Cargo:GetCoordinate() )
ActRouteCargo:SetRange( Cargo:GetBoardingRange() )
ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), TaskUnit.Menu )
ActRouteCargo:SetRange( Cargo:GetLoadRadius() )
ActRouteCargo:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Cargo " .. Cargo:GetName(), MenuControl, MenuTime, "Cargo" )
ActRouteCargo:Start()
return self
end
@@ -694,11 +850,15 @@ do -- TASK_CARGO
local ProcessUnit = self:GetUnitProcess( TaskUnit )
local MenuTime = self:InitTaskControlMenu( TaskUnit )
local MenuControl = self:GetTaskControlMenu( TaskUnit )
local ActRouteDeployZone = ProcessUnit:GetProcess( "RoutingToDeploy", "RouteToDeployZone" ) -- Actions.Act_Route#ACT_ROUTE_ZONE
ActRouteDeployZone:Reset()
ActRouteDeployZone:SetZone( DeployZone )
ActRouteDeployZone:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Deploy Zone" .. DeployZone:GetName(), TaskUnit.Menu )
ActRouteDeployZone:SetMenuCancel( TaskUnit:GetGroup(), "Cancel Routing to Deploy Zone" .. DeployZone:GetName(), MenuControl, MenuTime, "Cargo" )
ActRouteDeployZone:Start()
return self
end
@@ -726,12 +886,12 @@ do -- TASK_CARGO
end
--- @param #TASK_CARGO self
-- @param @list<Core.Zone#ZONE> DeployZones
-- @param #list<Core.Zone#ZONE> DeployZones
-- @param Wrapper.Unit#UNIT TaskUnit
-- @return #TASK_CARGO
function TASK_CARGO:SetDeployZones( DeployZones, TaskUnit )
for DeployZoneID, DeployZone in pairs( DeployZones ) do
for DeployZoneID, DeployZone in pairs( DeployZones or {} ) do
self.DeployZones[DeployZone:GetName()] = DeployZone
end
@@ -813,7 +973,6 @@ do -- TASK_CARGO
function TASK_CARGO:UpdateTaskInfo( DetectedItem )
if self:IsStatePlanned() or self:IsStateAssigned() then
self.TaskInfo:AddTaskName( 0, "MSOD" )
self.TaskInfo:AddCargoSet( self.SetCargo, 10, "SOD", true )
end
end
@@ -823,186 +982,8 @@ do -- TASK_CARGO
return 0
end
end
do -- TASK_CARGO_TRANSPORT
--- The TASK_CARGO_TRANSPORT class
-- @type TASK_CARGO_TRANSPORT
-- @extends #TASK_CARGO
TASK_CARGO_TRANSPORT = {
ClassName = "TASK_CARGO_TRANSPORT",
}
--- Instantiates a new TASK_CARGO_TRANSPORT.
-- @param #TASK_CARGO_TRANSPORT self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported.
-- @param #string TaskBriefing The Cargo Task briefing.
-- @return #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing )
local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport", TaskBriefing ) ) -- #TASK_CARGO_TRANSPORT
self:F()
Mission:AddTask( self )
-- Events
self:AddTransition( "*", "CargoPickedUp", "*" )
self:AddTransition( "*", "CargoDeployed", "*" )
self:F( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } )
--- OnBefore Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Synchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] CargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Asynchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_TRANSPORT] __CargoPickedUp
-- @param #TASK_CARGO_TRANSPORT self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- OnBefore Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnBeforeCargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] OnAfterCargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Synchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] CargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Asynchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_TRANSPORT] __CargoDeployed
-- @param #TASK_CARGO_TRANSPORT self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
local Fsm = self:GetUnitProcess()
local CargoReport = REPORT:New( "Transport Cargo. The following cargo needs to be transported including initial positions:")
SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
function( Cargo )
local CargoType = Cargo:GetType()
local CargoName = Cargo:GetName()
local CargoCoordinate = Cargo:GetCoordinate()
CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) )
end
)
self:SetBriefing(
TaskBriefing or
CargoReport:Text()
)
return self
end
function TASK_CARGO_TRANSPORT:ReportOrder( ReportGroup )
return 0
end
---
-- @param #TASK_CARGO_TRANSPORT self
-- @return #boolean
function TASK_CARGO_TRANSPORT:IsAllCargoTransported()
local CargoSet = self:GetCargoSet()
local Set = CargoSet:GetSet()
local DeployZones = self:GetDeployZones()
local CargoDeployed = true
-- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ).
for CargoID, CargoData in pairs( Set ) do
local Cargo = CargoData -- Core.Cargo#CARGO
self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } )
if Cargo:IsDeployed() then
-- -- Loop the DeployZones set for the TASK_CARGO_TRANSPORT.
-- for DeployZoneID, DeployZone in pairs( DeployZones ) do
--
-- -- If all cargo is in one of the deploy zones, then all is good.
-- self:T( { Cargo.CargoObject } )
-- if Cargo:IsInZone( DeployZone ) == false then
-- CargoDeployed = false
-- end
-- end
else
CargoDeployed = false
end
end
self:F( { CargoDeployed = CargoDeployed } )
return CargoDeployed
end
--- @param #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To )
local CargoSet = self.CargoSet
if self:IsAllCargoTransported() then
self:Success()
end
self:__Goal( -10 )
end
end

View File

@@ -0,0 +1,187 @@
--- **Tasking** -- Models tasks for players to execute CSAR @{Cargo} downed pilots.
--
-- ![Banner Image](..\Presentations\TASK_CARGO\Dia1.JPG)
--
-- ===
do -- TASK_CARGO_CSAR
--- The TASK_CARGO_CSAR class
-- @type TASK_CARGO_CSAR
-- @extends Tasking.Task_Cargo#TASK_CARGO
TASK_CARGO_CSAR = {
ClassName = "TASK_CARGO_CSAR",
}
--- Instantiates a new TASK_CARGO_CSAR.
-- @param #TASK_CARGO_CSAR self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported.
-- @param #string TaskBriefing The Cargo Task briefing.
-- @return #TASK_CARGO_CSAR self
function TASK_CARGO_CSAR:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing )
local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "CSAR", TaskBriefing ) ) -- #TASK_CARGO_CSAR
self:F()
Mission:AddTask( self )
-- Events
self:AddTransition( "*", "CargoPickedUp", "*" )
self:AddTransition( "*", "CargoDeployed", "*" )
self:F( { CargoDeployed = self.CargoDeployed ~= nil and "true" or "false" } )
--- OnBefore Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_CSAR] OnBeforeCargoPickedUp
-- @param #TASK_CARGO_CSAR self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoPickedUp
-- @param #TASK_CARGO_CSAR self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Synchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_CSAR] CargoPickedUp
-- @param #TASK_CARGO_CSAR self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- Asynchronous Event Trigger for Event CargoPickedUp.
-- @function [parent=#TASK_CARGO_CSAR] __CargoPickedUp
-- @param #TASK_CARGO_CSAR self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that PickedUp the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
--- OnBefore Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_CSAR] OnBeforeCargoDeployed
-- @param #TASK_CARGO_CSAR self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_CSAR] OnAfterCargoDeployed
-- @param #TASK_CARGO_CSAR self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Synchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_CSAR] CargoDeployed
-- @param #TASK_CARGO_CSAR self
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
--- Asynchronous Event Trigger for Event CargoDeployed.
-- @function [parent=#TASK_CARGO_CSAR] __CargoDeployed
-- @param #TASK_CARGO_CSAR self
-- @param #number Delay The delay in seconds.
-- @param Wrapper.Unit#UNIT TaskUnit The Unit (Client) that Deployed the cargo. You can use this to retrieve the PlayerName etc.
-- @param Core.Cargo#CARGO Cargo The Cargo that got PickedUp by the TaskUnit. You can use this to check Cargo Status.
-- @param Core.Zone#ZONE DeployZone The zone where the Cargo got Deployed or UnBoarded.
local Fsm = self:GetUnitProcess()
local CargoReport = REPORT:New( "Rescue a downed pilot from the following position:")
SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
function( Cargo )
local CargoType = Cargo:GetType()
local CargoName = Cargo:GetName()
local CargoCoordinate = Cargo:GetCoordinate()
CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) )
end
)
self:SetBriefing(
TaskBriefing or
CargoReport:Text()
)
return self
end
function TASK_CARGO_CSAR:ReportOrder( ReportGroup )
return 0
end
---
-- @param #TASK_CARGO_CSAR self
-- @return #boolean
function TASK_CARGO_CSAR:IsAllCargoTransported()
local CargoSet = self:GetCargoSet()
local Set = CargoSet:GetSet()
local DeployZones = self:GetDeployZones()
local CargoDeployed = true
-- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ).
for CargoID, CargoData in pairs( Set ) do
local Cargo = CargoData -- Core.Cargo#CARGO
self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } )
if Cargo:IsDeployed() then
-- -- Loop the DeployZones set for the TASK_CARGO_CSAR.
-- for DeployZoneID, DeployZone in pairs( DeployZones ) do
--
-- -- If all cargo is in one of the deploy zones, then all is good.
-- self:T( { Cargo.CargoObject } )
-- if Cargo:IsInZone( DeployZone ) == false then
-- CargoDeployed = false
-- end
-- end
else
CargoDeployed = false
end
end
self:F( { CargoDeployed = CargoDeployed } )
return CargoDeployed
end
--- @param #TASK_CARGO_CSAR self
function TASK_CARGO_CSAR:onafterGoal( TaskUnit, From, Event, To )
local CargoSet = self.CargoSet
if self:IsAllCargoTransported() then
self:Success()
end
self:__Goal( -10 )
end
end

View File

@@ -0,0 +1,601 @@
--- **Tasking** - Creates and manages player TASK_CARGO tasks.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ### Contributions:
--
-- ===
--
-- @module Task_Cargo_Dispatcher
do -- TASK_CARGO_DISPATCHER
--- TASK_CARGO_DISPATCHER class.
-- @type TASK_CARGO_DISPATCHER
-- @extends Tasking.Task_Manager#TASK_MANAGER
-- @field TASK_CARGO_DISPATCHER.CSAR CSAR
--- @type TASK_CARGO_DISPATCHER.CSAR
-- @field Wrapper.Unit#UNIT PilotUnit
-- @field Tasking.Task#TASK Task
--- # TASK_CARGO_DISPATCHER class, extends @{Task_Manager#TASK_MANAGER}
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia1.JPG)
--
-- The @{#TASK_CARGO_DISPATCHER} class implements the dynamic dispatching of cargo tasks.
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia3.JPG)
--
-- The EWR will detect units, will group them, and will dispatch @{Task}s to groups. Depending on the type of target detected, different tasks will be dispatched.
-- Find a summary below describing for which situation a task type is created:
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia9.JPG)
--
-- * **CSAR Task**: Is created when a fiendly pilot has ejected from a plane, and needs to be rescued (sometimes behind enemy lines).
--
-- ## 1. TASK\_A2A\_DISPATCHER constructor:
--
-- The @{#TASK_CARGO_DISPATCHER.New}() method creates a new TASK\_A2A\_DISPATCHER instance.
--
-- ### 1.1. Define or set the **Mission**:
--
-- Tasking is executed to accomplish missions. Therefore, a MISSION object needs to be given as the first parameter.
--
-- local HQ = GROUP:FindByName( "HQ", "Bravo" )
-- local CommandCenter = COMMANDCENTER:New( HQ, "Lima" )
-- local Mission = MISSION:New( CommandCenter, "A2A Mission", "High", "Watch the air enemy units being detected.", coalition.side.RED )
--
-- Missions are governed by COMMANDCENTERS, so, ensure you have a COMMANDCENTER object installed and setup within your mission.
-- Create the MISSION object, and hook it under the command center.
--
-- ### 1.2. Build a set of the groups seated by human players:
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia6.JPG)
--
-- A set or collection of the groups wherein human players can be seated, these can be clients or units that can be joined as a slot or jumping into.
--
-- local AttackGroups = SET_GROUP:New():FilterCoalitions( "red" ):FilterPrefixes( "Defender" ):FilterStart()
--
-- The set is built using the SET_GROUP class. Apply any filter criteria to identify the correct groups for your mission.
-- Only these slots or units will be able to execute the mission and will receive tasks for this mission, once available.
--
-- ### 1.3. Define the **EWR network**:
--
-- As part of the TASK\_A2A\_DISPATCHER constructor, an EWR network must be given as the third parameter.
-- An EWR network, or, Early Warning Radar network, is used to early detect potential airborne targets and to understand the position of patrolling targets of the enemy.
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia5.JPG)
--
-- Typically EWR networks are setup using 55G6 EWR, 1L13 EWR, Hawk sr and Patriot str ground based radar units.
-- These radars have different ranges and 55G6 EWR and 1L13 EWR radars are Eastern Bloc units (eg Russia, Ukraine, Georgia) while the Hawk and Patriot radars are Western (eg US).
-- Additionally, ANY other radar capable unit can be part of the EWR network! Also AWACS airborne units, planes, helicopters can help to detect targets, as long as they have radar.
-- The position of these units is very important as they need to provide enough coverage
-- to pick up enemy aircraft as they approach so that CAP and GCI flights can be tasked to intercept them.
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia7.JPG)
--
-- Additionally in a hot war situation where the border is no longer respected the placement of radars has a big effect on how fast the war escalates.
-- For example if they are a long way forward and can detect enemy planes on the ground and taking off
-- they will start to vector CAP and GCI flights to attack them straight away which will immediately draw a response from the other coalition.
-- Having the radars further back will mean a slower escalation because fewer targets will be detected and
-- therefore less CAP and GCI flights will spawn and this will tend to make just the border area active rather than a melee over the whole map.
-- It all depends on what the desired effect is.
--
-- EWR networks are **dynamically constructed**, that is, they form part of the @{Functional#DETECTION_BASE} object that is given as the input parameter of the TASK\_A2A\_DISPATCHER class.
-- By defining in a **smart way the names or name prefixes of the groups** with EWR capable units, these groups will be **automatically added or deleted** from the EWR network,
-- increasing or decreasing the radar coverage of the Early Warning System.
--
-- See the following example to setup an EWR network containing EWR stations and AWACS.
--
-- local EWRSet = SET_GROUP:New():FilterPrefixes( "EWR" ):FilterCoalitions("red"):FilterStart()
--
-- local EWRDetection = DETECTION_AREAS:New( EWRSet, 6000 )
-- EWRDetection:SetFriendliesRange( 10000 )
-- EWRDetection:SetRefreshTimeInterval(30)
--
-- -- Setup the A2A dispatcher, and initialize it.
-- A2ADispatcher = TASK_CARGO_DISPATCHER:New( Mission, AttackGroups, EWRDetection )
--
-- The above example creates a SET_GROUP instance, and stores this in the variable (object) **EWRSet**.
-- **EWRSet** is then being configured to filter all active groups with a group name starting with **EWR** to be included in the Set.
-- **EWRSet** is then being ordered to start the dynamic filtering. Note that any destroy or new spawn of a group with the above names will be removed or added to the Set.
-- Then a new **EWRDetection** object is created from the class DETECTION_AREAS. A grouping radius of 6000 is choosen, which is 6km.
-- The **EWRDetection** object is then passed to the @{#TASK_CARGO_DISPATCHER.New}() method to indicate the EWR network configuration and setup the A2A tasking and detection mechanism.
--
-- ### 2. Define the detected **target grouping radius**:
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia8.JPG)
--
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.
-- Fast planes like in the 80s, need a larger radius than WWII planes.
-- Typically I suggest to use 30000 for new generation planes and 10000 for older era aircraft.
--
-- Note that detected targets are constantly re-grouped, that is, when certain detected aircraft are moving further than the group radius, then these aircraft will become a separate
-- group being detected. This may result in additional GCI being started by the dispatcher! So don't make this value too small!
--
-- ## 3. Set the **Engage radius**:
--
-- Define the radius to engage any target by airborne friendlies, which are executing cap or returning from an intercept mission.
--
-- ![Banner Image](..\Presentations\TASK_CARGO_DISPATCHER\Dia11.JPG)
--
-- So, if there is a target area detected and reported,
-- then any friendlies that are airborne near this target area,
-- will be commanded to (re-)engage that target when available (if no other tasks were commanded).
-- For example, if 100000 is given as a value, then any friendly that is airborne within 100km from the detected target,
-- will be considered to receive the command to engage that target area.
-- You need to evaluate the value of this parameter carefully.
-- If too small, more intercept missions may be triggered upon detected target areas.
-- If too large, any airborne cap may not be able to reach the detected target area in time, because it is too far.
--
-- ## 4. Set **Scoring** and **Messages**:
--
-- The TASK\_A2A\_DISPATCHER is a state machine. It triggers the event Assign when a new player joins a @{Task} dispatched by the TASK\_A2A\_DISPATCHER.
-- An _event handler_ can be defined to catch the **Assign** event, and add **additional processing** to set _scoring_ and to _define messages_,
-- when the player reaches certain achievements in the task.
--
-- The prototype to handle the **Assign** event needs to be developed as follows:
--
-- TaskDispatcher = TASK_CARGO_DISPATCHER:New( ... )
--
-- --- @param #TaskDispatcher self
-- -- @param #string From Contains the name of the state from where the Event was triggered.
-- -- @param #string Event Contains the name of the event that was triggered. In this case Assign.
-- -- @param #string To Contains the name of the state that will be transitioned to.
-- -- @param Tasking.Task_A2A#TASK_A2A Task The Task object, which is any derived object from TASK_A2A.
-- -- @param Wrapper.Unit#UNIT TaskUnit The Unit or Client that contains the Player.
-- -- @param #string PlayerName The name of the Player that joined the TaskUnit.
-- function TaskDispatcher:OnAfterAssign( From, Event, To, Task, TaskUnit, PlayerName )
-- Task:SetScoreOnProgress( PlayerName, 20, TaskUnit )
-- Task:SetScoreOnSuccess( PlayerName, 200, TaskUnit )
-- Task:SetScoreOnFail( PlayerName, -100, TaskUnit )
-- end
--
-- The **OnAfterAssign** method (function) is added to the TaskDispatcher object.
-- This method will be called when a new player joins a unit in the set of groups in scope of the dispatcher.
-- So, this method will be called only **ONCE** when a player joins a unit in scope of the task.
--
-- The TASK class implements various methods to additional **set scoring** for player achievements:
--
-- * @{Tasking.Task#TASK.SetScoreOnProgress}() will add additional scores when a player achieves **Progress** while executing the task.
-- Examples of **task progress** can be destroying units, arriving at zones etc.
--
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional scores when the task goes into **Success** state.
-- This means the **task has been successfully completed**.
--
-- * @{Tasking.Task#TASK.SetScoreOnSuccess}() will add additional (negative) scores when the task goes into **Failed** state.
-- This means the **task has not been successfully completed**, and the scores must be given with a negative value!
--
-- @field #TASK_CARGO_DISPATCHER
TASK_CARGO_DISPATCHER = {
ClassName = "TASK_CARGO_DISPATCHER",
Mission = nil,
Tasks = {},
CSAR = {},
CSARSpawned = 0,
Transport = {},
TransportCount = 0,
}
--- TASK_CARGO_DISPATCHER constructor.
-- @param #TASK_CARGO_DISPATCHER self
-- @param Tasking.Mission#MISSION Mission The mission for which the task dispatching is done.
-- @param Set#SET_GROUP SetGroup The set of groups that can join the tasks within the mission.
-- @return #TASK_CARGO_DISPATCHER self
function TASK_CARGO_DISPATCHER:New( Mission, SetGroup )
-- Inherits from DETECTION_MANAGER
local self = BASE:Inherit( self, TASK_MANAGER:New( SetGroup ) ) -- #TASK_CARGO_DISPATCHER
self.Mission = Mission
self:AddTransition( "Started", "Assign", "Started" )
--- OnAfter Transition Handler for Event Assign.
-- @function [parent=#TASK_CARGO_DISPATCHER] OnAfterAssign
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Tasking.Task_A2A#TASK_A2A Task
-- @param Wrapper.Unit#UNIT TaskUnit
-- @param #string PlayerName
self:SetCSARRadius()
self:__StartTasks( 5 )
-- For CSAR missions, we process the event when a pilot ejects.
self:HandleEvent( EVENTS.Ejection )
return self
end
--- Handle the event when a pilot ejects.
-- @param #TASK_CARGO_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData
function TASK_CARGO_DISPATCHER:OnEventEjection( EventData )
self:F( { EventData = EventData } )
if self.CSARTasks == true then
local CSARCoordinate = EventData.IniUnit:GetCoordinate()
local CSARCoalition = EventData.IniUnit:GetCoalition()
local CSARCountry = EventData.IniUnit:GetCountry()
local CSARHeading = EventData.IniUnit:GetHeading()
-- Only add a CSAR task if the coalition of the mission is equal to the coalition of the ejected unit.
if CSARCoalition == self.Mission:GetCommandCenter():GetCoalition() then
local CSARTaskName = self:AddCSARTask( self.CSARTaskName, CSARCoordinate, CSARHeading, CSARCountry, self.CSARBriefing )
self:SetCSARDeployZones( CSARTaskName, self.CSARDeployZones )
end
end
return self
end
--- Define one default deploy zone for all the cargo tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param DefaultDeployZone A default deploy zone.
-- @return #TASK_CARGO_DISPATCHER
function TASK_CARGO_DISPATCHER:SetDefaultDeployZone( DefaultDeployZone )
self.DefaultDeployZones = { DefaultDeployZone }
return self
end
--- Define the deploy zones for all the cargo tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param DefaultDeployZones A list of the deploy zones.
-- @return #TASK_CARGO_DISPATCHER
--
function TASK_CARGO_DISPATCHER:SetDefaultDeployZones( DefaultDeployZones )
self.DefaultDeployZones = DefaultDeployZones
return self
end
--- Start the generation of CSAR tasks to retrieve a downed pilots.
-- You need to specify a task briefing, a task name, default deployment zone(s).
-- This method can only be used once!
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string CSARTaskName The CSAR task name.
-- @param #string CSARDeployZones The zones to where the CSAR deployment should be directed.
-- @param #string CSARBriefing The briefing of the CSAR tasks.
-- @return #TASK_CARGO_DISPATCHER
function TASK_CARGO_DISPATCHER:StartCSARTasks( CSARTaskName, CSARDeployZones, CSARBriefing)
if not self.CSARTasks then
self.CSARTasks = true
self.CSARTaskName = CSARTaskName
self.CSARDeployZones = CSARDeployZones
self.CSARBriefing = CSARBriefing
else
error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks has already started." )
end
return self
end
--- Stop the generation of CSAR tasks to retrieve a downed pilots.
-- @param #TASK_CARGO_DISPATCHER self
-- @return #TASK_CARGO_DISPATCHER
function TASK_CARGO_DISPATCHER:StopCSARTasks()
if self.CSARTasks then
self.CSARTasks = nil
self.CSARTaskName = nil
self.CSARDeployZones = nil
self.CSARBriefing = nil
else
error( "TASK_CARGO_DISPATCHER: The generation of CSAR tasks was not yet started." )
end
return self
end
--- Add a CSAR task to retrieve a downed pilot.
-- You need to specify a coordinate from where the pilot will be spawned to be rescued.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string CSARTaskPrefix (optional) The prefix of the CSAR task.
-- @param Core.Point#COORDINATE CSARCoordinate The coordinate where a downed pilot will be spawned.
-- @param #number CSARHeading The heading of the pilot in degrees.
-- @param DCSCountry#Country CSARCountry The country ID of the pilot that will be spawned.
-- @param #string CSARBriefing The briefing of the CSAR task.
-- @return #string The CSAR Task Name as a string. The Task Name is the main key and is shown in the task list of the Mission Tasking menu.
-- @usage
--
-- -- Add a CSAR task to rescue a downed pilot from within a coordinate.
-- local Coordinate = PlaneUnit:GetPointVec2()
-- TaskA2ADispatcher:AddCSARTask( Coordinate )
--
-- -- Add a CSAR task to rescue a downed pilot from within a coordinate of country RUSSIA, which is pointing to the west (270°).
-- local Coordinate = PlaneUnit:GetPointVec2()
-- TaskA2ADispatcher:AddCSARTask( Coordinate, 270, Country.RUSSIA )
--
function TASK_CARGO_DISPATCHER:AddCSARTask( CSARTaskPrefix, CSARCoordinate, CSARHeading, CSARCountry, CSARBriefing )
local CSARCoalition = self.Mission:GetCommandCenter():GetCoalition()
CSARHeading = CSARHeading or 0
CSARCountry = CSARCountry or self.Mission:GetCommandCenter():GetCountry()
self.CSARSpawned = self.CSARSpawned + 1
local CSARTaskName = string.format( ( CSARTaskPrefix or "CSAR" ) .. ".%03d", self.CSARSpawned )
-- Create the CSAR Pilot SPAWN object.
-- Let us create the Template for the replacement Pilot :-)
local Template = {
["visible"] = false,
["hidden"] = false,
["task"] = "Ground Nothing",
["name"] = string.format( "CSAR Pilot#%03d", self.CSARSpawned ),
["x"] = CSARCoordinate.x,
["y"] = CSARCoordinate.z,
["units"] =
{
[1] =
{
["type"] = ( CSARCoalition == coalition.side.BLUE ) and "Soldier M4" or "Infantry AK",
["name"] = string.format( "CSAR Pilot#%03d-01", self.CSARSpawned ),
["skill"] = "Excellent",
["playerCanDrive"] = false,
["x"] = CSARCoordinate.x,
["y"] = CSARCoordinate.z,
["heading"] = CSARHeading,
}, -- end of [1]
}, -- end of ["units"]
}
local CSARGroup = GROUP:NewTemplate( Template, CSARCoalition, Group.Category.GROUND, CSARCountry )
self.CSAR[CSARTaskName] = {}
self.CSAR[CSARTaskName].PilotGroup = CSARGroup
self.CSAR[CSARTaskName].Briefing = CSARBriefing
self.CSAR[CSARTaskName].Task = nil
return CSARTaskName
end
--- Define the radius to when a CSAR task will be generated for any downed pilot within range of the nearest CSAR airbase.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #number CSARRadius (Optional, Default = 50000) The radius in meters to decide whether a CSAR needs to be created.
-- @return #TASK_CARGO_DISPATCHER
-- @usage
--
-- -- Set 20km as the radius to CSAR any downed pilot within range of the nearest CSAR airbase.
-- TaskA2ADispatcher:SetEngageRadius( 20000 )
--
-- -- Set 50km as the radius to to CSAR any downed pilot within range of the nearest CSAR airbase.
-- TaskA2ADispatcher:SetEngageRadius() -- 50000 is the default value.
--
function TASK_CARGO_DISPATCHER:SetCSARRadius( CSARRadius )
self.CSARRadius = CSARRadius or 50000
return self
end
--- Define one deploy zone for the CSAR tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string CSARTaskName (optional) The name of the CSAR task.
-- @param CSARDeployZone A CSAR deploy zone.
-- @return #TASK_CARGO_DISPATCHER
function TASK_CARGO_DISPATCHER:SetCSARDeployZone( CSARTaskName, CSARDeployZone )
if CSARTaskName then
self.CSAR[CSARTaskName].DeployZones = { CSARDeployZone }
end
return self
end
--- Define the deploy zones for the CSAR tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string CSARTaskName (optional) The name of the CSAR task.
-- @param CSARDeployZones A list of the CSAR deploy zones.
-- @return #TASK_CARGO_DISPATCHER
--
function TASK_CARGO_DISPATCHER:SetCSARDeployZones( CSARTaskName, CSARDeployZones )
if CSARTaskName and self.CSAR[CSARTaskName] then
self.CSAR[CSARTaskName].DeployZones = CSARDeployZones
end
return self
end
--- Add a Transport task to transport cargo from fixed locations to a deployment zone.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string TaskName (optional) The name of the transport task.
-- @param Core.SetCargo#SET_CARGO SetCargo The SetCargo to be transported.
-- @param #string Briefing The briefing of the task transport to be shown to the player.
-- @return #TASK_CARGO_DISPATCHER
-- @usage
--
-- -- Add a Transport task to transport cargo of different types to a Transport Deployment Zone.
function TASK_CARGO_DISPATCHER:AddTransportTask( TaskName, SetCargo, Briefing )
self.TransportCount = self.TransportCount + 1
local TaskName = string.format( ( TaskName or "Transport" ) .. ".%03d", self.TransportCount )
self.Transport[TaskName] = {}
self.Transport[TaskName].SetCargo = SetCargo
self.Transport[TaskName].Briefing = Briefing
self.Transport[TaskName].Task = nil
return TaskName
end
--- Add a Transport task to transport cargo from fixed locations to a deployment zone.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string TaskName (optional) The name of the transport task.
-- @return Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT
function TASK_CARGO_DISPATCHER:GetTransportTask( TaskName )
self:ManageTasks()
return self.Transport[TaskName] and self.Transport[TaskName].Task
end
--- Define one deploy zone for the Transport tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string TaskName (optional) The name of the Transport task.
-- @param TransportDeployZone A Transport deploy zone.
-- @return #TASK_CARGO_DISPATCHER
function TASK_CARGO_DISPATCHER:SetTransportDeployZone( TaskName, TransportDeployZone )
if self.Transport[TaskName] then
self.Transport[TaskName].DeployZones = { TransportDeployZone }
else
error( "TaskName does not exist" )
end
return self
end
--- Define the deploy zones for the Transport tasks.
-- @param #TASK_CARGO_DISPATCHER self
-- @param #string TaskName (optional) The name of the Transport task.
-- @param TransportDeployZones A list of the Transport deploy zones.
-- @return #TASK_CARGO_DISPATCHER
--
function TASK_CARGO_DISPATCHER:SetTransportDeployZones( TaskName, TransportDeployZones )
if self.Transport[TaskName] then
self.Transport[TaskName].DeployZones = TransportDeployZones
else
error( "TaskName does not exist" )
end
return self
end
--- Evaluates of a CSAR task needs to be started.
-- @param #TASK_CARGO_DISPATCHER self
-- @return Set#SET_CARGO The SetCargo to be rescued.
-- @return #nil If there is no CSAR task required.
function TASK_CARGO_DISPATCHER:EvaluateCSAR( CSARUnit )
local CSARCargo = CARGO_GROUP:New( CSARUnit, "Pilot", CSARUnit:GetName(), 80, 1500, 10 )
local SetCargo = SET_CARGO:New()
SetCargo:AddCargosByName( CSARUnit:GetName() )
SetCargo:Flush(self)
return SetCargo
end
--- Assigns tasks to the @{Set#SET_GROUP}.
-- @param #TASK_CARGO_DISPATCHER self
-- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop.
function TASK_CARGO_DISPATCHER:ManageTasks()
self:F()
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
local Mission = self.Mission
if Mission:IsIDLE() or Mission:IsENGAGED() then
local TaskReport = REPORT:New()
-- Checking the task queue for the dispatcher, and removing any obsolete task!
for TaskIndex, TaskData in pairs( self.Tasks ) do
local Task = TaskData -- Tasking.Task#TASK
if Task:IsStatePlanned() then
-- Here we need to check if the pilot is still existing.
-- local DetectedItem = Detection:GetDetectedItemByIndex( TaskIndex )
-- if not DetectedItem then
-- local TaskText = Task:GetName()
-- for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
-- Mission:GetCommandCenter():MessageToGroup( string.format( "Obsolete A2A task %s for %s removed.", TaskText, Mission:GetShortText() ), TaskGroup )
-- end
-- Task = self:RemoveTask( TaskIndex )
-- end
end
end
-- Now that all obsolete tasks are removed, loop through the CSAR pilots.
for CSARName, CSAR in pairs( self.CSAR ) do
if not CSAR.Task then
-- New CSAR Task
local SetCargo = self:EvaluateCSAR( CSAR.PilotGroup )
CSAR.Task = TASK_CARGO_CSAR:New( Mission, self.SetGroup, CSARName, SetCargo, CSAR.Briefing )
Mission:AddTask( CSAR.Task )
TaskReport:Add( CSARName )
if CSAR.DeployZones then
CSAR.Task:SetDeployZones( CSAR.DeployZones or {} )
else
CSAR.Task:SetDeployZones( self.DefaultDeployZones or {} )
end
end
end
-- Now that all obsolete tasks are removed, loop through the Transport tasks.
for TransportName, Transport in pairs( self.Transport ) do
if not Transport.Task then
-- New Transport Task
Transport.Task = TASK_CARGO_TRANSPORT:New( Mission, self.SetGroup, TransportName, Transport.SetCargo, Transport.Briefing )
Mission:AddTask( Transport.Task )
TaskReport:Add( TransportName )
if Transport.DeployZones then
Transport.Task:SetDeployZones( Transport.DeployZones or {} )
else
Transport.Task:SetDeployZones( self.DefaultDeployZones or {} )
end
end
end
-- TODO set menus using the HQ coordinator
Mission:GetCommandCenter():SetMenu()
local TaskText = TaskReport:Text(", ")
for TaskGroupID, TaskGroup in pairs( self.SetGroup:GetSet() ) do
if ( not Mission:IsGroupAssigned(TaskGroup) ) and TaskText ~= "" then
Mission:GetCommandCenter():MessageToGroup( string.format( "%s has tasks %s. Subscribe to a task using the radio menu.", Mission:GetShortText(), TaskText ), TaskGroup )
end
end
end
return true
end
end

View File

@@ -0,0 +1,112 @@
--- **Tasking** -- The TASK_CARGO models tasks for players to transport @{Cargo}.
--
-- ![Banner Image](..\Presentations\TASK_CARGO\Dia1.JPG)
--
-- ===
-- @module
do -- TASK_CARGO_TRANSPORT
--- The TASK_CARGO_TRANSPORT class
-- @type TASK_CARGO_TRANSPORT
-- @extends Tasking.Task_CARGO#TASK_CARGO
TASK_CARGO_TRANSPORT = {
ClassName = "TASK_CARGO_TRANSPORT",
}
--- Instantiates a new TASK_CARGO_TRANSPORT.
-- @param #TASK_CARGO_TRANSPORT self
-- @param Tasking.Mission#MISSION Mission
-- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned.
-- @param #string TaskName The name of the Task.
-- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported.
-- @param #string TaskBriefing The Cargo Task briefing.
-- @return #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo, TaskBriefing )
local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport", TaskBriefing ) ) -- #TASK_CARGO_TRANSPORT
self:F()
Mission:AddTask( self )
local Fsm = self:GetUnitProcess()
local CargoReport = REPORT:New( "Transport Cargo. The following cargo needs to be transported including initial positions:")
SetCargo:ForEachCargo(
--- @param Core.Cargo#CARGO Cargo
function( Cargo )
local CargoType = Cargo:GetType()
local CargoName = Cargo:GetName()
local CargoCoordinate = Cargo:GetCoordinate()
CargoReport:Add( string.format( '- "%s" (%s) at %s', CargoName, CargoType, CargoCoordinate:ToStringMGRS() ) )
end
)
self:SetBriefing(
TaskBriefing or
CargoReport:Text()
)
return self
end
function TASK_CARGO_TRANSPORT:ReportOrder( ReportGroup )
return 0
end
---
-- @param #TASK_CARGO_TRANSPORT self
-- @return #boolean
function TASK_CARGO_TRANSPORT:IsAllCargoTransported()
local CargoSet = self:GetCargoSet()
local Set = CargoSet:GetSet()
local DeployZones = self:GetDeployZones()
local CargoDeployed = true
-- Loop the CargoSet (so evaluate each Cargo in the SET_CARGO ).
for CargoID, CargoData in pairs( Set ) do
local Cargo = CargoData -- Core.Cargo#CARGO
self:F( { Cargo = Cargo:GetName(), CargoDeployed = Cargo:IsDeployed() } )
if Cargo:IsDeployed() then
-- -- Loop the DeployZones set for the TASK_CARGO_TRANSPORT.
-- for DeployZoneID, DeployZone in pairs( DeployZones ) do
--
-- -- If all cargo is in one of the deploy zones, then all is good.
-- self:T( { Cargo.CargoObject } )
-- if Cargo:IsInZone( DeployZone ) == false then
-- CargoDeployed = false
-- end
-- end
else
CargoDeployed = false
end
end
self:F( { CargoDeployed = CargoDeployed } )
return CargoDeployed
end
--- @param #TASK_CARGO_TRANSPORT self
function TASK_CARGO_TRANSPORT:onafterGoal( TaskUnit, From, Event, To )
local CargoSet = self.CargoSet
if self:IsAllCargoTransported() then
self:Success()
end
self:__Goal( -10 )
end
end

View File

@@ -0,0 +1,150 @@
--- This module contains the TASK_MANAGER class and derived classes.
--
-- ===
--
-- 1) @{Task_Manager#TASK_MANAGER} class, extends @{Fsm#FSM}
-- ===
-- The @{Task_Manager#TASK_MANAGER} class defines the core functions to report tasks to groups.
-- Reportings can be done in several manners, and it is up to the derived classes if TASK_MANAGER to model the reporting behaviour.
--
-- 1.1) TASK_MANAGER constructor:
-- -----------------------------------
-- * @{Task_Manager#TASK_MANAGER.New}(): Create a new TASK_MANAGER instance.
--
-- 1.2) TASK_MANAGER reporting:
-- ---------------------------------
-- Derived TASK_MANAGER classes will manage tasks using the method @{Task_Manager#TASK_MANAGER.ManageTasks}(). This method implements polymorphic behaviour.
--
-- The time interval in seconds of the task management can be changed using the methods @{Task_Manager#TASK_MANAGER.SetRefreshTimeInterval}().
-- To control how long a reporting message is displayed, use @{Task_Manager#TASK_MANAGER.SetReportDisplayTime}().
-- Derived classes need to implement the method @{Task_Manager#TASK_MANAGER.GetReportDisplayTime}() to use the correct display time for displayed messages during a report.
--
-- Task management can be started and stopped using the methods @{Task_Manager#TASK_MANAGER.StartTasks}() and @{Task_Manager#TASK_MANAGER.StopTasks}() respectively.
-- If an ad-hoc report is requested, use the method @{Task_Manager#TASK_MANAGER#ManageTasks}().
--
-- The default task management interval is every 60 seconds.
--
-- ===
--
-- ### Contributions: Mechanist, Prof_Hilactic, FlightControl - Concept & Testing
-- ### Author: FlightControl - Framework Design & Programming
--
-- @module Task_Manager
do -- TASK_MANAGER
--- TASK_MANAGER class.
-- @type TASK_MANAGER
-- @field Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed.
-- @extends Core.Fsm#FSM
TASK_MANAGER = {
ClassName = "TASK_MANAGER",
SetGroup = nil,
}
--- TASK\_MANAGER constructor.
-- @param #TASK_MANAGER self
-- @param Set#SET_GROUP SetGroup The set of group objects containing players for which tasks are managed.
-- @return #TASK_MANAGER self
function TASK_MANAGER:New( SetGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM:New() ) -- #TASK_MANAGER
self.SetGroup = SetGroup
self:SetStartState( "Stopped" )
self:AddTransition( "Stopped", "StartTasks", "Started" )
--- StartTasks Handler OnBefore for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] OnBeforeStartTasks
-- @param #TASK_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- StartTasks Handler OnAfter for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] OnAfterStartTasks
-- @param #TASK_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- StartTasks Trigger for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] StartTasks
-- @param #TASK_MANAGER self
--- StartTasks Asynchronous Trigger for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] __StartTasks
-- @param #TASK_MANAGER self
-- @param #number Delay
self:AddTransition( "Started", "StopTasks", "Stopped" )
--- StopTasks Handler OnBefore for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] OnBeforeStopTasks
-- @param #TASK_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- StopTasks Handler OnAfter for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] OnAfterStopTasks
-- @param #TASK_MANAGER self
-- @param #string From
-- @param #string Event
-- @param #string To
--- StopTasks Trigger for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] StopTasks
-- @param #TASK_MANAGER self
--- StopTasks Asynchronous Trigger for TASK_MANAGER
-- @function [parent=#TASK_MANAGER] __StopTasks
-- @param #TASK_MANAGER self
-- @param #number Delay
self:AddTransition( "Started", "Manage", "Started" )
self:SetRefreshTimeInterval( 30 )
return self
end
function TASK_MANAGER:onafterStartTasks( From, Event, To )
self:Manage()
end
function TASK_MANAGER:onafterManage( From, Event, To )
self:__Manage( -self._RefreshTimeInterval )
self:ManageTasks()
end
--- Set the refresh time interval in seconds when a new task management action needs to be done.
-- @param #TASK_MANAGER self
-- @param #number RefreshTimeInterval The refresh time interval in seconds when a new task management action needs to be done.
-- @return #TASK_MANAGER self
function TASK_MANAGER:SetRefreshTimeInterval( RefreshTimeInterval )
self:F2()
self._RefreshTimeInterval = RefreshTimeInterval
end
--- Manages the tasks for the @{Set#SET_GROUP}.
-- @param #TASK_MANAGER self
-- @return #TASK_MANAGER self
function TASK_MANAGER:ManageTasks()
self:E()
end
end

View File

@@ -29,6 +29,19 @@ SMOKECOLOR = trigger.smokeColor -- #SMOKECOLOR
FLARECOLOR = trigger.flareColor -- #FLARECOLOR
--- Big smoke preset enum.
-- @type BIGSMOKEPRESET
BIGSMOKEPRESET = {
SmallSmokeAndFire=0,
MediumSmokeAndFire=1,
LargeSmokeAndFire=2,
HugeSmokeAndFire=3,
SmallSmoke=4,
MediumSmoke=5,
LargeSmoke=6,
HugeSmoke=7,
}
--- Utilities static class.
-- @type UTILS
UTILS = {

View File

@@ -179,11 +179,10 @@ function CLIENT:ShowBriefing()
if not self.ClientBriefingShown then
self.ClientBriefingShown = true
local Briefing = ""
if self.ClientBriefing then
if self.ClientBriefing and self.ClientBriefing ~= "" then
Briefing = Briefing .. self.ClientBriefing
self:Message( Briefing, 60, "Briefing" )
end
Briefing = Briefing .. " Press [LEFT ALT]+[B] to view the complete mission briefing."
self:Message( Briefing, 60, "Briefing" )
end
return self

View File

@@ -8,7 +8,7 @@
--
-- ===
--
-- @module Controllable
-- @module Wrapper.Controllable
@@ -202,26 +202,6 @@ end
-- Get methods
--- Returns the UNITs wrappers of the DCS Units of the Controllable (default is a GROUP).
-- @param #CONTROLLABLE self
-- @return #list<Wrapper.Unit#UNIT> The UNITs wrappers.
function CONTROLLABLE:GetUnits()
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local DCSUnits = DCSControllable:getUnits()
local Units = {}
for Index, UnitData in pairs( DCSUnits ) do
Units[#Units+1] = UNIT:Find( UnitData )
end
self:T3( Units )
return Units
end
return nil
end
--- Returns the health. Dead controllables have health <= 1.0.
-- @param #CONTROLLABLE self
@@ -377,9 +357,11 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
local function SetTask( Controller, DCSTask )
if self and self:IsAlive() then
local Controller = self:_GetController()
self:I( "Before SetTask" )
Controller:setTask( DCSTask )
self:I( "After SetTask" )
else
BASE:E( DCSControllableName .. " is not alive anymore. Cannot set DCSTask " .. DCSTask )
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end
end
@@ -516,7 +498,7 @@ function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task )
Waypoint.task = self:TaskCombo( { Task } )
self:T3( { Waypoint.task } )
self:F( { Waypoint.task } )
return Waypoint.task
end
@@ -816,15 +798,16 @@ end
-- @param #CONTROLLABLE self
-- @param #number Altitude The altitude to hold the position.
-- @param #number Speed The speed flying when holding the position.
-- @param Core.Point#COORDINATE Coordinate The coordinate where to orbit.
-- @return #CONTROLLABLE self
function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed )
function CONTROLLABLE:TaskOrbitCircle( Altitude, Speed, Coordinate )
self:F2( { self.ControllableName, Altitude, Speed } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
return self:TaskOrbitCircleAtVec2( ControllablePoint, Altitude, Speed )
local OrbitVec2 = Coordinate and Coordinate:GetVec2() or self:GetVec2()
return self:TaskOrbitCircleAtVec2( OrbitVec2, Altitude, Speed )
end
return nil
@@ -1068,9 +1051,10 @@ end
-- @param Dcs.DCSTypes#Vec2 Vec2 The point to fire at.
-- @param Dcs.DCSTypes#Distance Radius The radius of the zone to deploy the fire at.
-- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted).
-- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag
-- @return Dcs.DCSTasking.Task#Task The DCS task structure.
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount )
self:F2( { self.ControllableName, Vec2, Radius, AmmoCount } )
function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType )
self:F2( { self.ControllableName, Vec2, Radius, AmmoCount, WeaponType } )
-- FireAtPoint = {
-- id = 'FireAtPoint',
@@ -1097,6 +1081,10 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount )
DCSTask.params.expendQtyEnabled = true
end
if WeaponType then
DCSTask.params.weaponType=WeaponType
end
self:T3( { DCSTask } )
return DCSTask
end
@@ -1781,263 +1769,49 @@ function CONTROLLABLE:TaskRoute( Points )
return DCSTask
end
--- (AIR + GROUND) Make the Controllable move to fly to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec2( Point, Speed )
self:F2( { Point, Speed } )
do -- Route methods
local ControllablePoint = self:GetUnit( 1 ):GetVec2()
--- (AIR + GROUND) Make the Controllable move to fly to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec2( Point, Speed )
self:F2( { Point, Speed } )
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Point.x
PointTo.y = Point.y
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
--- (AIR + GROUND) Make the Controllable move to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec3( Point, Speed )
self:F2( { Point, Speed } )
local ControllableVec3 = self:GetUnit( 1 ):GetVec3()
local PointFrom = {}
PointFrom.x = ControllableVec3.x
PointFrom.y = ControllableVec3.z
PointFrom.alt = ControllableVec3.y
PointFrom.alt_type = "BARO"
PointFrom.type = "Turning Point"
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Point.x
PointTo.y = Point.z
PointTo.alt = Point.y
PointTo.alt_type = "BARO"
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
--- Make the controllable to follow a given route.
-- @param #CONTROLLABLE self
-- @param #table Route A table of Route Points.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:Route( Route, DelaySeconds )
self:F2( Route )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route.
self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1).
return self
end
return nil
end
--- Make the GROUND Controllable to drive towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointGround()
local ToWP = ToCoordinate:WaypointGround( Speed, Formation )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- Make the GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad(ToCoordinate)
-- Route, ground waypoints along roads.
local route={}
table.insert(route, FromCoordinate:WaypointGround(Speed, Formation))
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert(route, coord:WaypointGround(Speed, Formation))
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert(route, ToCoordinate:WaypointGround(Speed, "Vee"))
end
-- Route controllable to destination.
self:Route(route, DelaySeconds)
return self
end
--- Make the AIR Controllable fly towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type.
-- @param Core.Point#COORDINATE.RoutePointType Type The route point type.
-- @param Core.Point#COORDINATE.RoutePointAction Action The route point action.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointAir()
local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- (AIR + GROUND) Route the controllable to a given zone.
-- The controllable final destination point can be randomized.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to route to.
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
self:F2( Zone )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local ControllablePoint = self:GetUnit( 1 ):GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
local ZonePoint
if Randomize then
ZonePoint = Zone:GetRandomVec2()
else
ZonePoint = Zone:GetVec2()
end
PointTo.x = ZonePoint.x
PointTo.y = ZonePoint.y
PointTo.x = Point.x
PointTo.y = Point.y
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 20 / 1.6
end
local Points = { PointFrom, PointTo }
@@ -2048,49 +1822,51 @@ function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
return self
end
return nil
end
--- (AIR + GROUND) Make the Controllable move to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec3( Point, Speed )
self:F2( { Point, Speed } )
--- (GROUND) Route the controllable to a given Vec2.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param #Vec2 Vec2 The Vec2 where to route to.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local ControllableVec3 = self:GetUnit( 1 ):GetVec3()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.x = ControllableVec3.x
PointFrom.y = ControllableVec3.z
PointFrom.alt = ControllableVec3.y
PointFrom.alt_type = "BARO"
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Vec2.x
PointTo.y = Vec2.y
PointTo.x = Point.x
PointTo.y = Point.z
PointTo.alt = Point.y
PointTo.alt_type = "BARO"
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 60 / 3.6
end
local Points = { PointFrom, PointTo }
@@ -2101,10 +1877,283 @@ function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
return self
end
return nil
end
--- Make the controllable to follow a given route.
-- @param #CONTROLLABLE self
-- @param #table Route A table of Route Points.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:Route( Route, DelaySeconds )
self:F2( Route )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route.
self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1).
return self
end
return nil
end
--- Stops the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteStop()
self:F("RouteStop")
local CommandStop = self:CommandStopRoute( true )
self:SetCommand( CommandStop )
end
--- Resumes the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteResume()
self:F("RouteResume")
local CommandResume = self:CommandStopRoute( false )
self:SetCommand( CommandResume )
end
--- Make the GROUND Controllable to drive towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointGround()
local ToWP = ToCoordinate:WaypointGround( Speed, Formation )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- Make the GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad(ToCoordinate)
-- Route, ground waypoints along roads.
local route={}
table.insert(route, FromCoordinate:WaypointGround(Speed, Formation))
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert(route, coord:WaypointGround(Speed, Formation))
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert(route, ToCoordinate:WaypointGround(Speed, "Vee"))
end
-- Route controllable to destination.
self:Route( route, DelaySeconds )
return self
end
--- Make a task for a GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string EndPointFormation The formation to achieve at the end point.
-- @return Task
function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, EndPointFormation )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad( ToCoordinate )
-- Route, ground waypoints along roads.
local Route = {}
table.insert( Route, FromCoordinate:WaypointGround( Speed, Formation ) )
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert( Route, coord:WaypointGround( Speed, Formation ) )
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert( Route, ToCoordinate:WaypointGround( Speed, EndPointFormation ) )
end
return Route
end
--- Make the AIR Controllable fly towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type.
-- @param Core.Point#COORDINATE.RoutePointType Type The route point type.
-- @param Core.Point#COORDINATE.RoutePointAction Action The route point action.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointAir()
local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- (AIR + GROUND) Route the controllable to a given zone.
-- The controllable final destination point can be randomized.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to route to.
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
self:F2( Zone )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
local PointTo = {}
local ZonePoint
if Randomize then
ZonePoint = Zone:GetRandomVec2()
else
ZonePoint = Zone:GetVec2()
end
PointTo.x = ZonePoint.x
PointTo.y = ZonePoint.y
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 20 / 1.6
end
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
return nil
end
--- (GROUND) Route the controllable to a given Vec2.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param #Vec2 Vec2 The Vec2 where to route to.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
local PointTo = {}
PointTo.x = Vec2.x
PointTo.y = Vec2.y
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 60 / 3.6
end
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
return nil
end
end -- Route methods
-- Commands
--- Do Script command
@@ -2221,7 +2270,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil
local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil
self:T( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } )
self:T2( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } )
return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK )
end
@@ -2781,16 +2830,6 @@ function CONTROLLABLE:IsAirPlane()
return nil
end
function CONTROLLABLE:GetSize()
local DCSObject = self:GetDCSObject()
if DCSObject then
return 1
else
return 0
end
end
-- Message APIs

View File

@@ -23,7 +23,7 @@
--
-- ===
--
-- @module Group
-- @module Wrapper.Group
--- @type GROUP
@@ -121,7 +121,7 @@ GROUPTEMPLATE.Takeoff = {
-- @return #GROUP self
function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID )
local GroupName = GroupTemplate.name
_DATABASE:_RegisterGroupTemplate( GroupTemplate, CategoryID, CountryID, CoalitionSide, GroupName )
_DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName )
self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) )
self:F2( GroupName )
self.GroupName = GroupName
@@ -236,17 +236,37 @@ function GROUP:IsAlive()
end
--- Destroys the DCS Group and all of its DCS Units.
-- Note that this destroy method also raises a destroy event at run-time.
-- So all event listeners will catch the destroy event of this DCS Group.
-- Note that this destroy method also can raise a destroy event at run-time.
-- So all event listeners will catch the destroy event of this group for each unit in the group.
-- To raise these events, provide the `GenerateEvent` parameter.
-- @param #GROUP self
function GROUP:Destroy()
-- @param #boolean GenerateEvent true if you want to generate a crash or dead event for each unit.
-- @usage
-- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group.
-- Helicopter = GROUP:FindByName( "Helicopter" )
-- Helicopter:Destroy( true )
-- @usage
-- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group.
-- Tanks = GROUP:FindByName( "Tanks" )
-- Tanks:Destroy( true )
-- @usage
-- -- Ship unit example: destroy the Ship silently.
-- Ship = GROUP:FindByName( "Ship" )
-- Ship:Destroy( true )
function GROUP:Destroy( GenerateEvent )
self:F2( self.GroupName )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
self:CreateEventCrash( timer.getTime(), UnitData )
if GenerateEvent and GenerateEvent == true then
for Index, UnitData in pairs( DCSGroup:getUnits() ) do
if self:IsAir() then
self:CreateEventCrash( timer.getTime(), UnitData )
else
self:CreateEventDead( timer.getTime(), UnitData )
end
end
end
USERFLAG:New( self:GetName() ):Set( 100 )
DCSGroup:destroy()
@@ -330,13 +350,58 @@ function GROUP:GetCountry()
return nil
end
--- Returns a list of @{Unit} objects of the @{Group}.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of @{Unit} objects of the @{Group}.
function GROUP:GetUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnits = DCSGroup:getUnits()
local Units = {}
for Index, UnitData in pairs( DCSUnits ) do
Units[#Units+1] = UNIT:Find( UnitData )
end
self:T3( Units )
return Units
end
return nil
end
--- Returns a list of @{Unit} objects of the @{Group} that are occupied by a player.
-- @param #GROUP self
-- @return #list<Wrapper.Unit#UNIT> The list of player occupied @{Unit} objects of the @{Group}.
function GROUP:GetPlayerUnits()
self:F2( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
local DCSUnits = DCSGroup:getUnits()
local Units = {}
for Index, UnitData in pairs( DCSUnits ) do
local PlayerUnit = UNIT:Find( UnitData )
if PlayerUnit:GetPlayerName() then
Units[#Units+1] = PlayerUnit
end
end
self:T3( Units )
return Units
end
return nil
end
--- Returns the UNIT wrapper class with number UnitNumber.
-- If the underlying DCS Unit does not exist, the method will return nil. .
-- @param #GROUP self
-- @param #number UnitNumber The number of the UNIT wrapper class to be returned.
-- @return Wrapper.Unit#UNIT The UNIT wrapper class.
function GROUP:GetUnit( UnitNumber )
self:F2( { self.GroupName, UnitNumber } )
self:F3( { self.GroupName, UnitNumber } )
local DCSGroup = self:GetDCSObject()
@@ -356,7 +421,7 @@ end
-- @param #number UnitNumber The number of the DCS Unit to be returned.
-- @return Dcs.DCSWrapper.Unit#Unit The DCS Unit.
function GROUP:GetDCSUnit( UnitNumber )
self:F2( { self.GroupName, UnitNumber } )
self:F3( { self.GroupName, UnitNumber } )
local DCSGroup = self:GetDCSObject()
@@ -374,7 +439,7 @@ end
-- @param #GROUP self
-- @return #number The DCS Group size.
function GROUP:GetSize()
self:F2( { self.GroupName } )
self:F3( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
@@ -397,7 +462,7 @@ end
-- @param #GROUP self
-- @return #number The DCS Group initial size.
function GROUP:GetInitialSize()
self:F2( { self.GroupName } )
self:F3( { self.GroupName } )
local DCSGroup = self:GetDCSObject()
if DCSGroup then
@@ -1093,7 +1158,7 @@ function GROUP:Respawn( Template, Reset )
else
for UnitID, TemplateUnitData in pairs( Template.units ) do
self:F( "Reset" )
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.z }
local GroupUnitVec3 = { x = TemplateUnitData.x, y = TemplateUnitData.alt, z = TemplateUnitData.y }
if Zone then
if self.InitRespawnRandomizePositionZone then
GroupUnitVec3 = Zone:GetRandomVec3()
@@ -1121,10 +1186,106 @@ function GROUP:Respawn( Template, Reset )
self:ResetEvents()
return self
end
--- @param Wrapper.Group#GROUP self
function GROUP:RespawnAtAirbase( AirbaseRespawn, Takeoff, TakeoffAltitude ) -- R2.4
self:F( { AirbaseRespawn, Takeoff, TakeoffAltitude } )
local PointVec3 = AirbaseRespawn:GetPointVec3()
Takeoff = Takeoff or SPAWN.Takeoff.Hot
local SpawnTemplate = self:GetTemplate()
if SpawnTemplate then
local SpawnPoint = SpawnTemplate.route.points[1]
-- These are only for ships.
SpawnPoint.linkUnit = nil
SpawnPoint.helipadId = nil
SpawnPoint.airdromeId = nil
local AirbaseID = AirbaseRespawn:GetID()
local AirbaseCategory = AirbaseRespawn:GetDesc().category
self:F( { AirbaseCategory = AirbaseCategory, Ship = Airbase.Category.SHIP, Helipad = Airbase.Category.HELIPAD, Airdrome = Airbase.Category.AIRDROME } )
if AirbaseCategory == Airbase.Category.SHIP then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.HELIPAD then
SpawnPoint.linkUnit = AirbaseID
SpawnPoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then
SpawnPoint.airdromeId = AirbaseID
end
SpawnPoint.alt = 0
SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
-- Translate the position of the Group Template to the Vec3.
for UnitID = 1, #SpawnTemplate.units do
self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
-- These cause a lot of confusion.
local UnitTemplate = SpawnTemplate.units[UnitID]
UnitTemplate.parking = 15
UnitTemplate.parking_id = "30"
UnitTemplate.alt = 0
local SX = UnitTemplate.x
local SY = UnitTemplate.y
local BX = SpawnPoint.x
local BY = SpawnPoint.y
local TX = PointVec3.x + ( SX - BX )
local TY = PointVec3.z + ( SY - BY )
UnitTemplate.x = TX
UnitTemplate.y = TY
if Takeoff == GROUP.Takeoff.Air then
UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- UnitTemplate.alt = PointVec3.y + 10
end
self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y )
end
SpawnPoint.x = PointVec3.x
SpawnPoint.y = PointVec3.z
if Takeoff == GROUP.Takeoff.Air then
SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 )
--else
-- SpawnPoint.alt = PointVec3.y + 10
end
SpawnTemplate.x = PointVec3.x
SpawnTemplate.y = PointVec3.z
local GroupSpawned = self:Respawn( SpawnTemplate )
-- When spawned in the air, we need to generate a Takeoff Event
if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 )
end
end
return GroupSpawned
end
return nil
end
--- Return the mission template of the group.
@@ -1250,7 +1411,7 @@ do -- Route methods
-- @param #number Speed (optional) The Speed, if no Speed is given, the maximum Speed of the first unit is selected.
-- @return #GROUP
function GROUP:RouteRTB( RTBAirbase, Speed )
self:F2( { RTBAirbase, Speed } )
self:F( { RTBAirbase:GetName(), Speed } )
local DCSGroup = self:GetDCSObject()
@@ -1291,9 +1452,7 @@ do -- Route methods
Template.route.points = Points
self:Respawn( Template )
self:Route( Points )
self:Respawn(Template)
--self:Route( Points )
else
self:ClearTasks()
end
@@ -1358,6 +1517,8 @@ do -- Players
-- @return #nil The group has no players
function GROUP:GetPlayerNames()
local HasPlayers = false
local PlayerNames = {}
local Units = self:GetUnits()
@@ -1367,11 +1528,36 @@ do -- Players
if PlayerName and PlayerName ~= "" then
PlayerNames = PlayerNames or {}
table.insert( PlayerNames, PlayerName )
HasPlayers = true
end
end
self:F2( PlayerNames )
return PlayerNames
if HasPlayers == true then
self:F2( PlayerNames )
return PlayerNames
end
return nil
end
--- Get the active player count in the group.
-- @param #GROUP self
-- @return #number The amount of players.
function GROUP:GetPlayerCount()
local PlayerCount = 0
local Units = self:GetUnits()
for UnitID, UnitData in pairs( Units or {} ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
local PlayerName = Unit:GetPlayerName()
if PlayerName and PlayerName ~= "" then
PlayerCount = PlayerCount + 1
end
end
return PlayerCount
end
end

View File

@@ -229,6 +229,26 @@ function IDENTIFIABLE:GetDesc()
return nil
end
--- Check if the Object has the attribute.
-- @param #IDENTIFIABLE self
-- @param #string AttributeName The attribute name.
-- @return #boolean true if the attribute exists.
-- @return #nil The DCS Identifiable is not existing or alive.
function IDENTIFIABLE:HasAttribute( AttributeName )
self:F2( self.IdentifiableName )
local DCSIdentifiable = self:GetDCSObject()
if DCSIdentifiable then
local IdentifiableHasAttribute = DCSIdentifiable:hasAttribute( AttributeName )
self:T2( IdentifiableHasAttribute )
return IdentifiableHasAttribute
end
self:F( self.ClassName .. " " .. self.IdentifiableName .. " not found!" )
return nil
end
--- Gets the CallSign of the IDENTIFIABLE, which is a blank by default.
-- @param #IDENTIFIABLE self
-- @return #string The CallSign of the IDENTIFIABLE.

View File

@@ -73,6 +73,7 @@ end
--- Destroys the OBJECT.
-- @param #OBJECT self
-- @return #boolean true if the object is destroyed.
-- @return #nil The DCS Unit is not existing or alive.
function OBJECT:Destroy()
@@ -80,7 +81,8 @@ function OBJECT:Destroy()
if DCSObject then
--BASE:CreateEventCrash( timer.getTime(), DCSObject )
DCSObject:destroy()
DCSObject:destroy( false )
return true
end
BASE:E( { "Cannot Destroy", Name = self.ObjectName, Class = self:GetClassName() } )
@@ -91,3 +93,5 @@ end

View File

@@ -129,7 +129,7 @@ function POSITIONABLE:GetPointVec2()
local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 )
self:T2( PositionablePointVec2 )
--self:F( PositionablePointVec2 )
return PositionablePointVec2
end
@@ -309,6 +309,18 @@ function POSITIONABLE:IsAboveRunway()
end
function POSITIONABLE:GetSize()
local DCSObject = self:GetDCSObject()
if DCSObject then
return 1
else
return 0
end
end
--- Returns the POSITIONABLE heading in degrees.
-- @param Wrapper.Positionable#POSITIONABLE self
@@ -379,7 +391,7 @@ function POSITIONABLE:GetVelocityVec3()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
if DCSPositionable and DCSPositionable:isExist() then
local PositionableVelocityVec3 = DCSPositionable:getVelocity()
self:T3( PositionableVelocityVec3 )
return PositionableVelocityVec3
@@ -421,7 +433,7 @@ function POSITIONABLE:GetVelocityKMH()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
if DCSPositionable and DCSPositionable:isExist() then
local VelocityVec3 = self:GetVelocityVec3()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec
local Velocity = Velocity * 3.6 -- now it is in km/h.
@@ -440,7 +452,7 @@ function POSITIONABLE:GetVelocityMPS()
local DCSPositionable = self:GetDCSObject()
if DCSPositionable then
if DCSPositionable and DCSPositionable:isExist() then
local VelocityVec3 = self:GetVelocityVec3()
local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec
self:T3( Velocity )
@@ -526,10 +538,11 @@ end
-- @param #string Message The message text
-- @param Dcs.DCSTYpes#Duration Duration The duration of the message.
-- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message.
function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition )
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name )
self:F2( { Message, Duration } )
local Name = ""
local Name = Name or ""
local DCSObject = self:GetDCSObject()
if DCSObject then
@@ -546,10 +559,11 @@ end
-- @param #string Message The message text
-- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration.
-- @param Dcs.DCScoalition#coalition MessageCoalition The Coalition receiving the message.
function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition )
-- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable.
function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition, Name )
self:F2( { Message, MessageType } )
local Name = ""
local Name = Name or ""
local DCSObject = self:GetDCSObject()
if DCSObject then
@@ -794,6 +808,15 @@ function POSITIONABLE:AddCargo( Cargo )
return self
end
--- Get all contained cargo.
-- @param #POSITIONABLE self
-- @return #POSITIONABLE
function POSITIONABLE:GetCargo()
return self.__.Cargo
end
--- Remove cargo.
-- @param #POSITIONABLE self
-- @param Core.Cargo#CARGO Cargo

View File

@@ -48,6 +48,24 @@ STATIC = {
}
function STATIC:Register( StaticName )
local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) )
self.StaticName = StaticName
return self
end
--- Finds a STATIC from the _DATABASE using a DCSStatic object.
-- @param #STATIC self
-- @param Dcs.DCSWrapper.Static#Static DCSStatic An existing DCS Static object reference.
-- @return #STATIC self
function STATIC:Find( DCSStatic )
local StaticName = DCSStatic:getName()
local StaticFound = _DATABASE:FindStatic( StaticName )
return StaticFound
end
--- Finds a STATIC from the _DATABASE using the relevant Static Name.
-- As an optional parameter, a briefing text can be given also.
-- @param #STATIC self
@@ -71,12 +89,6 @@ function STATIC:FindByName( StaticName, RaiseError )
return nil
end
function STATIC:Register( StaticName )
local self = BASE:Inherit( self, POSITIONABLE:New( StaticName ) )
self.StaticName = StaticName
return self
end
function STATIC:GetDCSObject()
local DCSStatic = StaticObject.getByName( self.StaticName )
@@ -88,6 +100,27 @@ function STATIC:GetDCSObject()
return nil
end
--- Returns a list of one @{Static}.
-- @param #STATIC self
-- @return #list<Wrapper.Static#STATIC> A list of one @{Static}.
function STATIC:GetUnits()
self:F2( { self.StaticName } )
local DCSStatic = self:GetDCSObject()
local Statics = {}
if DCSStatic then
Statics[1] = STATIC:Find( DCSStatic )
self:T3( Statics )
return Statics
end
return nil
end
function STATIC:GetThreatLevel()
return 1, "Static"
@@ -97,12 +130,32 @@ end
-- @param #UNIT self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
-- @param #number Heading The heading of the unit respawn.
function STATIC:ReSpawn( Coordinate, Heading )
function STATIC:SpawnAt( Coordinate, Heading )
-- todo: need to fix country
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, country.id.USA )
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName )
SpawnStatic:SpawnFromPointVec2( Coordinate, Heading, self.StaticName )
end
--- Respawn the @{Unit} at the same location with the same properties.
-- This is useful to respawn a cargo after it has been destroyed.
-- @param #UNIT self
function STATIC:ReSpawn()
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName )
SpawnStatic:ReSpawn()
end
--- Respawn the @{Unit} at a defined Coordinate with an optional heading.
-- @param #UNIT self
-- @param Core.Point#COORDINATE Coordinate The coordinate where to spawn the new Static.
-- @param #number Heading The heading of the unit respawn.
function STATIC:ReSpawnAt( Coordinate, Heading )
local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName )
SpawnStatic:ReSpawnAt( Coordinate, Heading )
end

View File

@@ -161,18 +161,28 @@ end
--- Destroys the UNIT.
-- @param #UNIT self
-- @param #boolean GenerateEvent (Optional) true if you want to generate a crash or dead event for the unit.
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:Destroy()
function UNIT:Destroy( GenerateEvent )
self:F2( self.ObjectName )
local DCSObject = self:GetDCSObject()
if DCSObject then
local UnitGroup = self:GetGroup()
local UnitGroupName = UnitGroup:GetName()
self:F( { UnitGroupName = UnitGroupName } )
if GenerateEvent and GenerateEvent == true then
if self:IsAir() then
self:CreateEventCrash( timer.getTime(), DCSObject )
else
self:CreateEventDead( timer.getTime(), DCSObject )
end
end
USERFLAG:New( UnitGroupName ):Set( 100 )
--BASE:CreateEventCrash( timer.getTime(), DCSObject )
DCSObject:destroy()
end
@@ -189,20 +199,22 @@ end
-- * Then it will respawn the re-modelled group.
--
-- @param #UNIT self
-- @param Dcs.DCSTypes#Vec3 SpawnVec3 The position where to Spawn the new Unit at.
-- @param Core.Point#COORDINATE Coordinate The position where to Spawn the new Unit at.
-- @param #number Heading The heading of the unit respawn.
function UNIT:ReSpawn( SpawnVec3, Heading )
function UNIT:ReSpawnAt( Coordinate, Heading )
self:T( self:Name() )
local SpawnGroupTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplateFromUnitName( self:Name() ) )
self:T( SpawnGroupTemplate )
local SpawnGroup = self:GetGroup()
self:T( { SpawnGroup = SpawnGroup } )
if SpawnGroup then
local Vec3 = SpawnGroup:GetVec3()
SpawnGroupTemplate.x = SpawnVec3.x
SpawnGroupTemplate.y = SpawnVec3.z
SpawnGroupTemplate.x = Coordinate.x
SpawnGroupTemplate.y = Coordinate.z
self:F( #SpawnGroupTemplate.units )
for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do
@@ -221,12 +233,13 @@ function UNIT:ReSpawn( SpawnVec3, Heading )
end
for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do
self:T( UnitTemplateData.name )
self:T( { UnitTemplateData.name, self:Name() } )
SpawnGroupTemplate.units[UnitTemplateID].unitId = nil
if UnitTemplateData.name == self:Name() then
self:T("Adjusting")
SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y
SpawnGroupTemplate.units[UnitTemplateID].x = SpawnVec3.x
SpawnGroupTemplate.units[UnitTemplateID].y = SpawnVec3.z
SpawnGroupTemplate.units[UnitTemplateID].alt = Coordinate.y
SpawnGroupTemplate.units[UnitTemplateID].x = Coordinate.x
SpawnGroupTemplate.units[UnitTemplateID].y = Coordinate.z
SpawnGroupTemplate.units[UnitTemplateID].heading = Heading
self:F( { UnitTemplateID, SpawnGroupTemplate.units[UnitTemplateID], SpawnGroupTemplate.units[UnitTemplateID] } )
else
@@ -262,6 +275,10 @@ function UNIT:ReSpawn( SpawnVec3, Heading )
end
end
SpawnGroupTemplate.groupId = nil
self:T( SpawnGroupTemplate )
_DATABASE:Spawn( SpawnGroupTemplate )
end
@@ -319,6 +336,9 @@ function UNIT:GetCallsign()
if DCSUnit then
local UnitCallSign = DCSUnit:getCallsign()
if UnitCallSign == "" then
UnitCallSign = DCSUnit:getName()
end
return UnitCallSign
end
@@ -334,18 +354,30 @@ end
function UNIT:GetPlayerName()
self:F2( self.UnitName )
local DCSUnit = self:GetDCSObject()
local DCSUnit = self:GetDCSObject() -- Dcs.DCSUnit#Unit
if DCSUnit then
local PlayerName = DCSUnit:getPlayerName()
if PlayerName == nil then
PlayerName = ""
-- TODO Workaround DCS-BUG-3 - https://github.com/FlightControl-Master/MOOSE/issues/696
if PlayerName == nil or PlayerName == "" then
local PlayerCategory = DCSUnit:getDesc().category
if PlayerCategory == Unit.Category.GROUND_UNIT or PlayerCategory == Unit.Category.SHIP then
PlayerName = "Player" .. DCSUnit:getID()
end
end
-- -- Good code
-- if PlayerName == nil then
-- PlayerName = nil
-- else
-- if PlayerName == "" then
-- PlayerName = "Player" .. DCSUnit:getID()
-- end
-- end
return PlayerName
end
return nil
return nil
end
@@ -379,7 +411,7 @@ function UNIT:GetGroup()
local DCSUnit = self:GetDCSObject()
if DCSUnit then
local UnitGroup = GROUP:Find( DCSUnit:getGroup() )
local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() )
return UnitGroup
end
@@ -524,16 +556,16 @@ function UNIT:GetFuel()
return nil
end
--- Returns the UNIT in a UNIT list of one element.
--- Returns a list of one @{Unit}.
-- @param #UNIT self
-- @return #list<Wrapper.Unit#UNIT> The UNITs wrappers.
-- @return #list<Wrapper.Unit#UNIT> A list of one @{Unit}.
function UNIT:GetUnits()
self:F2( { self.UnitName } )
local DCSUnit = self:GetDCSObject()
local Units = {}
if DCSUnit then
local DCSUnits = DCSUnit:getUnits()
local Units = {}
Units[1] = UNIT:Find( DCSUnit )
self:T3( Units )
return Units
@@ -628,12 +660,9 @@ function UNIT:GetThreatLevel()
if Descriptor then
local Attributes = Descriptor.attributes
self:T( Attributes )
if self:IsGround() then
self:T( "Ground" )
local ThreatLevels = {
"Unarmed",
"Infantry",
@@ -670,8 +699,6 @@ function UNIT:GetThreatLevel()
if self:IsAir() then
self:T( "Air" )
local ThreatLevels = {
"Unarmed",
"Tanker",
@@ -704,8 +731,6 @@ function UNIT:GetThreatLevel()
if self:IsShip() then
self:T( "Ship" )
--["Aircraft Carriers"] = {"Heavy armed ships",},
--["Cruisers"] = {"Heavy armed ships",},
--["Destroyers"] = {"Heavy armed ships",},
@@ -743,7 +768,6 @@ function UNIT:GetThreatLevel()
end
end
self:T2( ThreatLevel )
return ThreatLevel, ThreatText
end

View File

@@ -21,7 +21,6 @@ Core/Radio.lua
Core/Spawn.lua
Core/SpawnStatic.lua
Core/Goal.lua
Core/Cargo.lua
Core/Spot.lua
Wrapper/Object.lua
@@ -35,6 +34,12 @@ Wrapper/Static.lua
Wrapper/Airbase.lua
Wrapper/Scenery.lua
Cargo/Cargo.lua
Cargo/CargoUnit.lua
Cargo/CargoSlingload.lua
Cargo/CargoCrate.lua
Cargo/CargoGroup.lua
Functional/Scoring.lua
Functional/CleanUp.lua
Functional/Movement.lua
@@ -49,6 +54,9 @@ Functional/Range.lua
Functional/ZoneGoal.lua
Functional/ZoneGoalCoalition.lua
Functional/ZoneCaptureCoalition.lua
Functional/Artillery.lua
Functional/Suppression.lua
Functional/PseudoATC.lua
AI/AI_Balancer.lua
AI/AI_A2A.lua
@@ -61,6 +69,13 @@ AI/AI_Cap.lua
AI/AI_Cas.lua
AI/AI_Bai.lua
AI/AI_Formation.lua
AI/AI_Cargo_APC.lua
AI/AI_Cargo_Helicopter.lua
AI/AI_Cargo_Airplane.lua
AI/AI_Cargo_Dispatcher.lua
AI/AI_Cargo_Dispatcher_APC.lua
AI/AI_Cargo_Dispatcher_Helicopter.lua
AI/AI_Cargo_Dispatcher_Airplane.lua
Actions/Act_Assign.lua
Actions/Act_Route.lua
@@ -71,12 +86,16 @@ Tasking/CommandCenter.lua
Tasking/Mission.lua
Tasking/Task.lua
Tasking/TaskInfo.lua
Tasking/Task_Manager.lua
Tasking/DetectionManager.lua
Tasking/Task_A2G_Dispatcher.lua
Tasking/Task_A2G.lua
Tasking/Task_A2A_Dispatcher.lua
Tasking/Task_A2A.lua
Tasking/Task_Cargo.lua
Tasking/Task_Cargo_Transport.lua
Tasking/Task_Cargo_CSAR.lua
Tasking/Task_Cargo_Dispatcher.lua
Tasking/TaskZoneCapture.lua
Moose.lua